2018-08

博客分类: 江河计划-月总结

2018-08

算法

合并两个有序数组

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。

说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3

输出: [1,2,2,3,5,6]

我的解法:

var merge = function(nums1, m, nums2, n) {
    var j=0;
    for(let i=0; i<n; i++){
        while(nums1[j] < nums2[i] && j < m+i){
            j++;
        }
        console.log(j)
        nums1.splice(j, 0, nums2[i]);
    }
    nums1.length = m+n;
};

最优解法:

var merge = function(nums1, m, nums2, n) {
    let len = nums1.length;
    nums2.forEach((num2, i2) => {
        let index = nums1.findIndex(num1 => num1 > num2);
        index = index === -1 ? m + i2 : index;
        nums1.splice(index, 0, num2);
    });
    nums1.length = len;

};

第一个错误的版本

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

示例:

给定 n = 5,并且 version = 4 是第一个错误的版本。

调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true

所以,4 是第一个错误的版本。 

我的解法:

var solution = function(isBadVersion) {
    /**
     * @param {integer} n Total versions
     * @return {integer} The first bad version
     */
    return function(n) {
        var left = 1;
        var right = n;
        while(left <= right){
            var mid = left + parseInt((right - left)/2, 10);
            if(isBadVersion(mid)){
                right = mid - 1
            }else{
                left = mid + 1
            }
        }
        return left
    };
};

最优解法,在判断right 时多判断一下,mid - 1 是不是正巧是第一个错误的版本;

var solution = function(isBadVersion) {
    /**
     * @param {integer} n Total versions
     * @return {integer} The first bad version
     */
    return function(n) {
        var left = 1;
        var right = n;
        while(left <= right){
            var mid = left + parseInt((right - left)/2, 10);
            if(isBadVersion(mid)){
                if (!isBadVersion(mid - 1)) {
                    return mid;
                } else {
                    right = mid - 1
                }
            }else{
                left = mid + 1
            }
        }
        return left
    };
};

爬楼问题

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 阶 + 1 阶
2.  2 阶

解法和费布那切的解法类似。

var climbStairs = function(n) {
    let arr = new Array(n)
    for(let i = 1; i <= n; i++) {
        if(i < 3) {
            arr[i - 1] = i
        } else {
            arr[i - 1] = arr[i - 2] + arr[i - 3]
        }
    }
    return n <= 0 ? 0 : arr[n - 1]
}

买卖股票的最佳时机

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票。

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
var maxProfit = function(prices) {
    var total = 0;
    var min = prices[0];
    for(let i=1; i<prices.length; i++){
        total = Math.max(total, prices[i] - min);
        min = Math.min(min, prices[i]);
    }
    return total
};

打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:

输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

迭代的方式

var rob = function(nums) {
    let last = 0;
    let total = 0;
    for(let i=0; i<nums.length; i++){
        var temp = last;
        last = total;
        total = Math.max(temp + nums[i], last);
    }
    return total;
};

递归的解法

var rob = function(nums) {
    var cacheRes = {};
    function robInfo(i){
        if(i < 0) return 0;
        if(i === 0) return nums[0];
        if(!cacheRes[i]){
            if(i === 1){
                cacheRes[i] = Math.max(nums[0], nums[1]);
            }else{
                cacheRes[i] = Math.max(nums[i] + robInfo(i - 2), nums[i - 1] + robInfo(i - 3))
            }
        }
        return cacheRes[i]
    }
    return robInfo(nums.length - 1);
};

Shuffle an Array

// 以数字集合 1, 2 和 3 初始化数组。
int[] nums = {1,2,3};
Solution solution = new Solution(nums);

// 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。
solution.shuffle();

// 重设数组到它的初始状态[1,2,3]。
solution.reset();

// 随机返回数组[1,2,3]打乱后的结果。
solution.shuffle();
/**
 * @param {number[]} nums
 */
var Solution = function(nums) {
    this._nums = nums;
};

/**
 * Resets the array to its original configuration and return it.
 * @return {number[]}
 */
Solution.prototype.reset = function() {
    return this._nums;
};

/**
 * Returns a random shuffling of the array.
 * @return {number[]}
 */
Solution.prototype.shuffle = function() {
    var nums = this._nums.slice(0);
    var rdIdx, swap;
    function rd(n, m){
        var c = m-n+1;	
        return Math.floor(Math.random() * c + n);
    }
    for(var i = nums.length; i--;){
        rdIdx = Math.floor(Math.random() * (i + 1));
        swap = nums[i];
        nums[i] = nums[rdIdx];
        nums[rdIdx] = swap;
    }
    return nums;
};

后评估系统

前面总结各种架构,最终如何衡量各种架构做的结果如何,不能凭借主观性的评价,需要构建一套评估体系去评估最终结果如何。这快目前自己也不是非常清晰。

当然也有很多评估系统是评价不出来的,比如团队潜力,技术能否承担未来业务发展。但是后评估系统也分三方面去评估。

业务架构

业务会有业务的指标,这些指标可能开发不背,但是开发有责任去主动关注一些指标,并且通过技术层面去提出优化建议,目标是为了更好的推进业务。

业务数据常规有 PV、UV、转化率,下单率,下单时长等等,很多都可以通过交互和展现来提升,页面展现的不同侧重来提高转化率。根据具体的业务情况来定和前端技术相关的数据指标,通过交互和页面侧重点来提高相关数据

技术架构

可以通过各种性能指标来衡量技术架构的优劣。比如白屏时间,资源加载时间,页面间切换的时间来衡量技术架构的性能

js 报错的情况,线上 bug 情况,线上的故障的情况来衡量技术架构的稳定性。

上述这些维度网上一搜一大堆,这些维度的统计都是比较常规的,但是数据得从一定量级去衡量才有效,比如硬币问题,至少要达到一万次的实验量级概率才能基本稳定,所以这些数据一定要在一定的量级下才有意义。

团队架构

团队架构的衡量更多是项目管理的衡量,完成需求的情况,有没有 delay,测试 bug 的情况,代码 review 的情况等等

总结

这块自己的实践目前还不足,这些衡量并非为了衡量结果而衡量,而是通过衡量发现问题,然后推动方案去解决问题,最终提升产品品质。

一开始应该有个用户产品期望,然后有个我们自身对于产品的一个期望,我们的期望比用户的期望高,就会给用户带来惊喜,如果我们的期望比用户低就会失望。

后评估系统实际上是评估现状,衡量现状和产品期望的落差,现在高于产品期望就是产品收益高,低于落差就是应该去查找问题,解决问题,将实际产品价值体现出来。

weex-eros

选型

项目在做技术选型的时候,目标是要开发我们公司的 APP,那个时候已经成熟的技术方案有 webView 嵌套,hybrid 应用,RN/Weex 的混生,原生应用,现在还出现了 flutter、WebAssembly。当时团队内部的情况是客户端同学只有两个,而前端同学有八个,业务的量级是一百多个页面,开发周期是一到两个月的时间,首先是开发成本和效率的问题,完全依赖原生开发同学,或者立马招人风险都不太可控,那我们可能需要考虑的是前三种方案,尽管这会牺牲一部分用户体验,而在当时的环境下,RN/Weex 是用户体验最优的,那我们就会考虑优先选用这种方案,当时 RN 从技术成熟度、社区、文档各方面都要优于 Weex,但是我们之前所有的技术栈都是基于 Vue 建立的,所以我还是优先考虑了学习成本和接入成本的问题,也为了完善整体的前端技术架构。

然后很快的了解了一些 Weex 的优缺点,那个时候并没有对源码进行过多的了解,只是从应用层面上看,是否能完成我们的目标,学习成本,接入成本,开发体验,性能监控和容错等问题,了解之后发现并没有多大问题,我很快的尝试了搭建一个 demo,在 demo 跑起来的过程中并没有遇到阻断性问题,通过周末的时间 demo 就跑起来了,然后分别让团队的核心成员完成一个稍微复杂的页面和团队能力较弱的成员完成一个简单页面,大约一个多工作日,两个页面基本都出来了,加上那个时候 Weex 说他们抗过了双十一,至少让我们觉得 weex 是可行的。

定位

Eros 是根据我们业务 APP 解决方案孵化出来的开源项目,基于 Weex,但是 Weex 更像是一个技术方案,提供基础的机制和功能,而我们的解决方案定位是一个业务开发的解决方案,落地到真实开发环境中。

完善功能

框架上设计了 weex 如何在不需要用户做任何改动的情况下直接写业务代码,并且最终打包上线。

扩展了 weex 的开发能力,配合常规业务需求扩展了 Component 和 Module,扩展了本身 weex 的能力。重新设计了插件化,设计第三方插件接入的方式,让开发者向我们贡献了很多插件,丰富了本身 Eros 的社区,提升 Eros 的业务能力

服务支持上,设计了热更新方案并配备了服务端方案,能支持直接部署,客户端的监控数据接口暴露,使用方能直接接入使用者的监控系统。

梳理了开发规范,重新设计的了配套的脚手架,完善了 weex 文档上的一些不足。

开源收获

混合应用的技术方案核心在于稳定的壳,壳足够稳定就可以减少发布提高业务推进效率,为了稳定壳所以我们开源,作为小公司开源可能和大公司不一样,大公司开源更多的是自己踩了坑之后,将方案开源展示技术实力,树立行业影响力,吸引更好的人员。而我们开源希望吸取到三方面的知识。

一:让社区的开发者来帮我们测试我们底层的壳 bug 以及方案设计是否符合大家的预期,收集外界的需求,我们看我们的业务未来发展是否需要,符合我们未来的业务发展,我们也会提前开发,待有业务需求的时候直接能上线,这方面主要是从技术层面稳定壳。

二:weex 的方案由于多方面的问题一直被社区或者大家诟病,我们期望通过我们的方案让开发者跳过 weex 本身的诸多问题,让更多的开发者通过我们的方案去使用 weex,从而壮大 weex 的社区,推动 weex 本身的发展,从而让我们壳依赖的 weex sdk 更加稳定。

三:通过开源的方案让社区来检验我们的方案设计能力和开发人员的技术能力,通过提升大家的技术能力,同事让社区来检验,并且我们也期望逐渐具备一些行业影响力,并且通过行业影响力吸引更多优秀的人加入。

实际收益

我们的开源方案成了 weex 社区最大的业务性解决方案,壳稳定到业务需求几乎不需要动客户端。开发效率和稳定性都有了很大的提升。开发者上千人,star 上千,上线几十个 APP,有公司将我们的方案纳入招聘 JD,也有很多社区里的同学来我们公司应聘。

typescript

基本类型

接口

interface LabelledValue {
  label: string;
  // 可选
  color?: string;
  // 只读
  readonly x: number;
}

函数接口

interface SearchFunc {
  (source: string, subString: string): boolean;
}

接口实现

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

接口继承

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;

class Person {
    protected name: string;
    protected constructor(theName: string) { this.name = theName; }
}

// Employee 能够继承 Person
class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name);
        this.department = department;
    }

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");

类的修饰符

函数类型

function add(x: number, y: number): number {
    return x + y;
}

declare

声明 ts 中的全局变量,比如在 window,不然使用的时候会报错。

declare var foo: number;
declare function greet(greeting: string): void;

修饰符

功能强大,用于函数包装

SVG

什么是 SVG

使用方式

可以用一些外链的方式引入,也可以是一些在 HTML 中直接写。svg 也能允许嵌套

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     >
    <circle cx="64" cy="64" r="64" style="fill: #00ccff;"></circle>
</svg>

上图如果没有 viewbox,会根据 svg 元素的宽高来固定画布,内部的大小由实际大小决定,如果要进行同比缩放,就可以设定 viewbox,相当于设定画布的大小,这个时候再设定 svg 的宽高,就会根据 svg 的款高和 viewbox 的宽高进行等比缩放

SVG 坐标系

左上角为原点,右为 x 正向,下为 y 正向。如果没有指定单位,那么单位为像素,可以指定 cm 和 mm 的单位。

Unit  Description
em    The default font size - usually the height of a character.
ex    The height of the character x
px    Pixels
pt    Points (1 / 72 of an inch)
pc    Picas (1 / 6 of an inch)
cm    Centimeters
mm    Millimeters
in    Inches

g 元素

SVG中g元素被用来将图形进行分组。一旦分组,你可以把它当作一个单一的形状,对整个图形组进行转换。与嵌套的svg元素相比,将元素作为整体转换是它的一个优点。

g元素的样式由它的子元素继承,但是G元素没有X和Y属性

SVG 形状

SVG 有一些预定义的形状元素,可被开发者使用和操作:

SVG 矩形 rect

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <rect width="300" height="100"
  style="fill:rgb(0,0,255);stroke-width:1;stroke:rgb(0,0,0)"/>
</svg>

属性

SVG 圆 circle

<svg xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
    <circle cx="40" cy="40" r="24" style="stroke:#006600; fill:#00cc00"/>
</svg>

属性

SVG 椭圆 ellipse

<svg xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">

  <ellipse cx="40" cy="40" rx="30" ry="15"
           style="stroke:#006600; fill:#00cc00"/>

</svg>

属性

SVG 线 line

<svg xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
    <line x1="0"  y1="10" x2="0"   y2="100" style="stroke:#006600;"/>
        <line x1="10" y1="10" x2="100" y2="100" style="stroke:#006600;"/>
        <line x1="20" y1="10" x2="100" y2="50"  style="stroke:#006600;"/>
        <line x1="30" y1="10" x2="110" y2="10"  style="stroke:#006600;"/>
</svg>

属性

SVG 折线 polyline

<svg xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
    <polyline points="0,0  30,0  15,30"
        style="stroke:#006600;"/>
</svg>

SVG 多边形 polygon

<svg xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
  <polygon points="10,0  60,0  35,50"
         style="stroke:#660000; fill:#cc3333;"/>

</svg>

属性

SVG 路径 path

<svg xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
    <path d="M50,50
             A30,30 0 0,1 35,20
             L100,100
             M110,110
             L100,0"
          style="stroke:#660000; fill:none;"/>    
</svg>

命令 | 参数 | 名称 | 描述 —|—|—|—| M | x,y | 移至 | 将虚拟画笔移动到指定点x,y处而不绘制图。 m | x,y | 移至 | 将虚拟画笔移动至相对于其当前坐标的x,y处而不进行绘图操作。 L | x,y | 线路 | 从虚拟画笔当前位置绘制一条到x,y点的直线。 l | x,y | 线路 | 从虚拟画笔当前位置绘制一条到相对于画笔当前位置的x,y点的直线。 H | x | 水平线 | 绘制一条到指定点(x坐标由参数指定,y坐标为虚拟画笔当前的纵坐标)的水平线。 h | x | 水平线 | 与H相同,但是坐标点为相对于画笔坐标的位置。 V | y | 垂直线 | 绘制一条到指定点(x坐标为虚拟画笔当前横坐标,y坐标由参数指定)的垂直线。 v | y | 垂直线 | 与V相同,但是坐标点为相对于画笔坐标的位置。 C | x1,y1 x2,y2 x,y | 曲线 | 从画笔当前点到x,y点绘制一条三次贝塞尔曲线。x1,y1和x2,y2是曲线的开始和结束控制点,控制其如何弯曲。 c | x1,y1 x2,y2 x,y | 曲线 | 与C相同,但是坐标点为相对于画笔坐标的位置。 S | x2,y2 x,y | 平滑曲线缩写 | 从画笔位置到点x,y绘制一条三次贝塞尔曲线。x2,y2为结束控制点。开始控制点与前一条曲线的结束控制点相同。 s | x2,y2 x,y | 平滑曲线缩写 | 与S相同,但是坐标点为相对于画笔坐标的位置。 Q | x1,y1 x,y | 二次贝塞尔曲线 | 从画笔当前坐标到x,y点绘制一条二次贝塞尔曲线。x1,y1是控制曲线如何弯曲的控制点。 q | x1,y1 x,y | 二次贝塞尔曲线 | 与Q相同,但是坐标点为相对于画笔坐标的位置。 T | x,y | 平滑二次贝塞尔曲线缩写 | 从画笔位置到点x,y绘制一条三次贝塞尔曲线。控制点与所使用的的最后一个控制点相同。 t | x,y | 平滑二次贝塞尔曲线缩写 | 与T相同,但是坐标点为相对于画笔坐标的位置。 A | rx,ry x-axis-rotation large-arc-flag,sweepflag x,y | 椭圆弧 | 从当前点到x,y点绘制一条椭圆弧。rx和ry为椭圆在x和y方向上的半径。x-rotation确定圆弧围绕x轴旋转的角度。当rx和ry的值不同时,它才会有效果。large-arc-flag似乎没有被使用(可以为0或1)。值(0或1)都不会改变圆弧。 a | rx,ry x-axis-rotation large-arc-flag,sweepflag x,y | 椭圆弧 | 与A相同,但是坐标点为相对于画笔坐标的位置。 Z | | 闭合路径 | 通过从当前点到第一个点绘制一条线来关闭路径。 z | | 闭合路径 | 通过从当前点到第一个点绘制一条线来关闭路径。

SVG

SVG marker

<defs>
    <marker id="markerSquare" markerWidth="7" markerHeight="7" refX="4" refY="4" orient="auto">
        <rect x="1" y="1" width="5" height="5" style="stroke: none; fill:#000000;" />
    </marker>

    <marker id="markerArrow" markerWidth="13" markerHeight="13" refX="2" refY="7" orient="auto">
        <path d="M2,2 L2,13 L8,7 L2,2" style="fill: #000000;" />
    </marker>
</defs>

<path d="M100,20 l50,0 l0,50 l0,50 l50,0" style="stroke: #0000cc; stroke-width: 1px; fill: none;
   marker-start: url(#markerSquare);
   marker-mid: url(#markerArrow);
   marker-end: url(#markerArrow);
" />

SVG marker被用来标记线段或路径的开始、中间和结束。例如,你可以使用圆形或正方形标记作为路径的开始,并使用一个箭头标记路径的结束。

这个例子定义了一个宽度(markerWidth=”8”)和高度(markerHeight=”8”)都为8的标记。宽度和高度需要显示设置,因为标记是一个独立的图形元素。

SVG text

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
    <text x="20"  y="40">Example SVG text 1</text>
    <line x1="10" y1="40" x2="150" y2="40" style="stroke: #000000"/>
</svg>

属性

SVG tspan

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<text x="10" y="20">
    Here is a text with 
    <tspan style="baseline-shift:super;">superscript</tspan>
    and 
    <tspan style="baseline-shift:sub;">subscript</tspan> mixed with normal text.</text>
</svg>

根据前一行文字进行定位

SVG textpath

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
        <path id="myTextPath2" d="M75,20 l100,0 l100,30 q0,100 150,100" />
    </defs>
    <text x="10" y="100" style="stroke: #000000;">
        <textPath xlink:href="#myTextPath2">
            Text along a more advanced path with lines and curves.
        </textPath>
    </text>
</svg>

元素用于沿着路径(例如圆上)布局文本。这看起来非常的酷。不同浏览器沿着路径呈现文本的方式有一点区别,因此请务必检查你的文本在你计划支持的所有浏览器中的效果。

SVG switch

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <switch>
        <g systemLanguage="en-UK">
            <text x="10" y="20">UK English</text>
        </g>
        <g systemLanguage="en">
            <text x="10" y="20">English</text>
        </g>
        <g systemLanguage="zh-cn">
            <text x="10" y="20">中文</text>
        </g>
    </switch>
</svg>
元素可以根据用户使用的SVG查看器的语言展示不同的图形。通常,你可以使用元素显示不同的文本,但也可以显示不同的形状。 ### SVG image 图片 ``` ``` 使用SVG元素可以在SVG图片中嵌套位图图片 ### SVG a 链接 ``` <rect x="10" y="20" width="75" height="30"style="stroke: #333366; fill: #6666cc"/> ``` 你可以将元素上的xlink:show属性设置为new或replace,来告诉浏览器是在新窗口中打开链接还是替换当前窗口。 注意:如果你使用replace并且在iframe中显示SVG图片,iframe将是链接的目标,而不是浏览器窗口。如果你想让浏览器窗口替代iframe,可以将target属性的值改为_top。 你也可以设置target属性告诉浏览器在指定的框架或指定的窗口中打开链接。这就像HTML中链接的target属性一样(至少在理论上)。注意浏览器以不同的方式解释target属性。有关详细信息,请参阅本页最后一节。 ### SVG defs ``` ``` SVG元素中嵌套了在SVG图片中可重用的定义。例如,你可以将多个SVG图形组织在一起,将其当作一个可重用的图形。 ### SVG symbol ``` ``` SVG元素用来定义可重用的标记。嵌套在中的形状不会显示,除非其被元素引用。 SVG元素用来定义可重用的标记。嵌套在中的形状不会显示,除非其被元素引用。 ### SVG use ``` ``` SVG元素在SVG文档的任何位置复用图形,包括元素和元素。复用的图形可以被定义在元素(使用前图形不可见)内或者外面。