JavaScript Es6-Es11 新语法特性

ES6

ES6 语法简洁 各个框架都需要用到ES6

1,ES5

1.1介绍

ES5 除了正常运行模式(又称为混杂模式),还添加了第二种运行模式:”严格模式“(strict mode)。

严格模式顾名思义,就是使 JavaScript 在更严格的语法条件下运行。

1.2作用

  1. 消除 JavaScript 语法的一些不合理、不严谨之处,减少一些怪异行为
  2. 消除代码运行的一些不安全之处,保证代码运行的安全
  3. 为未来新版本的 JavaScript 做好铺垫

1.3使用

  • **在全局或函数的第一条语句定义为: **'use strict'
  • 如果浏览器不支持,只解析为一条简单的语句, 没有任何副作用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 全局使用严格模式
    'use strict';
    girl = '迪丽热巴';

    // 函数中使用严格模式
    function main(){
    'use strict';
    boy = '吴亦凡';
    }
    main();

1.4语法和行为改变

  • 必须用 var 声明变量,不允许使用未声明的变量
  • 禁止自定义的函数中的 this 指向 window
  • 创建 eval 作用域
  • 对象不能有重名的属性(Chrome 已经修复了这个 Bug,IE 还会出现)
  • 函数不能有重复的形参
  • 新增一些保留字, 如: implements interface private protected public

1.5Object 扩展方法

Object.create(prototype, [descriptors])

Object.create 方法可以以指定对象为原型创建新的对象,同时可以为新的对象设置属性, 并对属性进行描述

  • value : 指定值
  • writable : 标识当前属性值是否是可修改的, 默认为 false
  • configurable:标识当前属性是否可以被删除 默认为 false
  • enumerable:标识当前属性是否能用for in 枚举 默认为 false
  • get: 当获取当前属性时的回调函数
  • set: 当设置当前属性时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//创建一个汽车的对象
var car = {
   name : '汽车',
   run: function(){
       console.log('我可以行驶!!');
  }
};

//以 car 为原型对象创建新对象  
var aodi = Object.create(car, {//car 参数是指的是原型对象 也可以叫父亲对象
   brand: {//在car的基础上 给本对象添加新的属性
       value: '奥迪',
       writable: false,         //是否可修改
       configurable: false,     //是否可以删除
       enumerable: true         //是否可以使用 for...in 遍历
  },
 //价格属性(测试get 和set方法)
   price:{
     //get 是一个对象的方法 当对该属性里面的对象获取时候get就开始执行了
       get:function(){
           console.log('price 被获取了');//当这个属性被使用的时候
      }
       set:function(value){//set 也是一个对象方法 当我们对属性进行修改的时候 这个函数就会被调用
  //它会接收一个参数 这个参数返回的是设置的值
  this.jiage=value;
  console.log('我被修改了');
return 123;
   
}
  }
   color: {
       value : '黑色',
       wriable: false,
       configurable: false,
       enumerable: true
  }
});

console.log(aodi.prict);//当我调用aodi的price属性的时候 它里面的get就会自动的执行 并且返回的值会作为调用它的对象的一个属性
aodi.price=1000;//当我们对属性进行设置的时候 set就会运行 value 接受的就是被修改的值 即10000
//同时该函数也会被运行 里面的console.log();也会被运行
  • Object.defineProperties(object, descriptors)

直接在一个对象上定义新的属性或修改现有属性,并返回该对象。

  • object 要操作的对象
  • descriptors 属性描述
    • get 作为该属性的 getter 函数,如果没有 getter 则为undefined。函数返回值将被用作属性的值。
    • set 作为属性的 setter 函数,如果没有 setter 则为undefined。函数将仅接受参数赋值给该属性的新值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 定义对象
var star = {
   firstName: '刘',
   lastName : '德华'
};

// 为 star 定义额外的属性
Object.defineProperties(star, {
   fullName: {
       get: function(){
           return this.firstName + this.lastName;
      },
       set: function(name){
           var res = name.split('-');
           this.firstName = res[0];
           this.lastName = res[1];
      }
  }
});

// 修改 fullName 属性值
star.fullName = '张-学友';

// 打印属性
console.log(star.fullName);
  • 小练习
    向对象里面添加属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <script type="text/javascript">
    let banji={
    name:'计算机一班',
    scores:[
    {name:'张三',cj:100},
    {name:'张四',cj:90},
    {name:'张五',cj:80},
    {name:'张六',cj:70},
    ]
    }

    //为班级添加一个新的属性 总成绩 和平均成绩
    Object.defineProperties(banji,{
    pjcj:{
    get:function(){
    let zongcj=0;
    for(let i=0;i<this.scores.length;i++){
    zongcj+=this.scores[i].cj;

    }
    return zongcj;
    }
    }
    });
    console.log(banji.pjcj);
    </script>
  • call、apply 和 bind

  • call 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //函数的普通的调用的方法
    function add(a,b,c){
    console.log(this);//this指向的是window
    console.log(a+b+c);
    }

    //普通调用
    add(10,29,10);

    //使用call调用
    add.call({},1,2,3);//使用call来调用函数 第一个{} 是代表的是this指向的值 后面放参数

  • apply 方法调用一个具有给定 this 值的函数,以及作为一个数组(或类似数组对象)提供的参数

    就是修改函数this的值

    1
    apply.apply({name:'dd'},[1,2,3]);
  • bind 同 call 相似,不过该方法会返回一个新的函数,而不会立即执行

    **会返回一个新的函数 **

1
2
3
4
5
6
7
8
9
10
11
12
13
function main(){
   console.log(this);
}
/*1. 直接调用函数*/
main();// window
/*2. 创建一个对象*/
var company = {name: '尚硅谷', age: 10};
/*使用这个对象调用 main 方法*/
main.call(company);// company
main.apply(company);// company
/*bind 修改 this 的值,返回一个新的函数*/
var fn = main.bind(company);
fn();// company
  • 函数和方法

    function 在对象的内部被称为 方法

    function 在对象的外部被称为 函数

2.ES6使用

2.1 变量的声明

ES6的变量的声明 支持的格式形式 比较多下面的都可以

字母数字下划线 首字母不能使用数字 严格区分大小写 不能使用关键字 驼峰命名法

1
2
3
4
5
let a;
let a,b,c;
let e=100;
let f=12,h=123,g='hhadfasd',k=[];

2.2 let 关键字

变量的声明不能重复

1
2
let a=100;
let a=200;

**块级作用域 只能在当前的块级作用域里面使用 **

1
2
3
4
5
{
let a=100;
//块级作用域 这个变量a 这个块的作用域里面有效 出了这个代码块 这个变量就无效了
}
alert(a);//在这个位置就用不了

不存在变量提升

1
2
3
4
alert(b);//使用var 声明的变量会有变量提升 
alert(a);//使用let 声明的变量就不会有变量提升 使用在这个位置使用这个a 变量的话 就会报错
let a=100;
var b=100;

不影响作用域链 使用let声明的不影响作用域链

1
2
3
4
5
6
functon fn(){
let a=100;
function fn2(){
console.log(a);
}
}

2.3 const 关键字

const 用来声明一个常量 (值不变的量)

  1. 格式
    **声明的时候一定要赋初始值 **

    1
    const Name='UZI';
  2. 声明的时候一定要赋初始值

  3. 常量的名称一般会大写(潜规则)

  4. 不允许重复声明 不允许重复赋值

  5. 块级作用域

  6. 关于数组和对象的元素的修改

    对于数组来说 修改里面的值是不会报错的 因为存的是数据所指向的地址

    同样的对于对象来说这样修改对象的属性也是可以的 和数组一样存的也是所指向的地址

    1
    2
    3
    const TEAM=['A','B'];
    TEAM.push('SSS');
    //这样的是允许的
    1
    2
    3
    4
    const big={
    name:'sddd'
    }
    big.name='aaa'

2.4 变量的解构赋值

ES6 允许按照一定的模式从数组和对象中提取值,对变量进行赋值 这被称为解构赋值

  • 使用方式

    z l w 的值是和arr 里面的三个值是一一对应的

    1
    2
    3
    const arr=['张三','李四','王五'];
    let [z,l,w]=arr;
    console.log(z,l,w);

    对象的解构赋值

    1
    2
    3
    4
    5
    6
    7
    8
    const star={
    name:'于谦',
    tags:['抽烟','喝酒','烫头'],
    say:function(){
    console.log('说相声');
    }
    }
    let {name,tags:[c,h,t],say}=srar;

2.5 模板字符串

增强版的字符串 在原生的js中 使用单引号和双引号来声明字符串

在ES6中 允许我们使用 反引号来声明字符串

  • 字符串中可以出现换行符

    原生的js需要使用+ 把所有的内容连接起来 才不会报错 ES6 直接``里面写就可以

    1
    2
    3
    4
    5
    let str=`<ul>
    <li>张三</li>
    <li>张三</li>
    <li>张三</li>
    </ul>`
  • 字符串中进行变量的拼接

    直接在字符串的中间 加上${变量名}

    1
    2
    let one='张三';
    let two=`做坏事,${one} 这好吗`;

2.6 简化的对象的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let name='张三';
let pos='监狱';
let change=function(){
console.log('知道错了');
}
//原生js的写法
const life={
name:name,
pos:pos,
change:change
}

//ES6写法
const life={
name,
pos,
chchange
}

2.7 箭头函数

ES6 里面允许我们使用箭头函数来代替function

  • 声明格式
    let 函数名=(形式参数)=>{ 函数体 }
    1
    2
    3
    let add=(a,b,c)=>{
    return a+b+c;
    }
  • 调用
    1
    2
    3
    4
    5
    6
    // 直接调用
    add(1,2,3);
    //使用call
    add.call({},1,2,3);
    //使用apply
    add.apply({},[1,2,3]);

2.8 箭头函数的特点

  • 箭头函数的this 是静态的 它是指向外层函数作用域的this的值
    1
    2
    3
    4
    let getName=()=>{
    console.log(this);
    }
    //这个this是指向它外层函数的this的值 在这个里面指向的是window
  • 箭头函数不能作为构造函数使用
    1
    2
    3
    4
    const getname=()=>{}

    //这样是万万不可的
    let name=new getanme();
  • 不能使用arguments

    arguments 里面放的是所有的实参

2.9 箭头函数的简写

当形参有且只有一个的时候 小括号可以不写

1
2
3
4
5
6
7
let pow1=(num)=>{return num*num};
//上面的可以简写为:
let pow=num=>{
return num*num;
}
pow(3);
console.log(pow(3));

当代体只有一句的时候花括号也可以不写 return 也可以不写 默认返回的就是表达式的结果

1
2
3
4
5
6
7
8
let pow1=(num)=>{return num*num};
//上面的可以简写为:
let pow=num=>{
return num*num;
}
//简写掉花括号和return

let pow=num=>num*num;

如果回调与this相关 的 就不能使用箭头函数

如果回调与this无关的话 建议使用箭头函数

  • 定时器
  • **数组方法的回调函数 **

2.10 参数的默认值

在ES6 中是允许使用参数默认值的

1
2
3
4
5
function fu(a,b,c=10){
return a+b+c;
}
fn(10,29);
//当第三个参数没有给值的时候就会使用参数的默认值

**与结解构赋值联合使用 **

1
2
3
4
5
6
7
function fn(a,b,c){
console.log(a);
}
congs{(
a:999,
b:333
)}

2.11 rest 参数

arguments 参数就是我们传递入函数的参数的值

1
2
3
4
5
function fun(){
console.log(arguments);
}
//arguments 的参数就是我们所传递进函数的值
fun('tom',[1,2,3],{name:'Janny'});

使用rest函数

书写的格式是 … 名称

但是如果我们传递多个参数 rest函数只能是写在最后一个 否则会报错出现问题

rest 函数会把你传入的参数形成一个数组

1
2
3
4
5
6
7
8
function sumRest (...m) {
var total = 0;
for(var i of m){
total += i;
}
return total;
}
console.log(sumRest(1,2,3));//6

spread 扩展运算符 可以对数组进行展开

1
2
3
4
5
let arr=['d','s','d'];
function fn(){
console.log(arguments);
}
fn(...arr);//解构函数会对数组进行一个解析 拆解成'd','s','d' 这样的三个参数

2.12 rest 参数的应用

  • 数组的合并
    1
    2
    3
    4
    const arr=[1,2,3];
    const arr3=[4,5,6];
    //把两个数组合并为一个数组
    const shuzu=[...arr,...arr2];
  • 数组的克隆
    1
    2
    3
    4
    const arr=[1,2,3];
    //数组的克隆
    const arr2=[...arr,122];
    //数组后面也可以添加新的元素
  • 伪数组转化成为真的数组
    1
    2
    3
    const divs=document.getelementbytagbane('div');
    //
    const reslist=[...divs];

2.13 symbol 数据类型

symbol 是JavaScript 里面的一种独特的数据类型 类似于字符串 但是值不能改变

Symbol 类型的值不能与其他类型的值进行运算

  • 创建symbol

    1
    2
    3
    let s1=Symbol();
    //直接调用Symbol 就可以
    let s2=Symbol('品牌');

    Symbol 的合理的使用的方法就是作为创建对象的属性的值来使用 单纯的使用没有价值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let game={
    up: '上升'
    };
    //为game添加新的属性
    let methed={
    down:Symbol();
    }
    //添加down属性
    game[methed.down]=function(){
    }
    //调用
    game[methed.down]();

    Symbol 在函数的内部添加方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    let math={
    up:Symbol(),
    down:Symbol()
    }

    let game={
    name='俄罗斯',
    gameover:function(){
    alert('游戏结束');
    }

    //添加新的方法
    [math.up]:function(){
    aelrt('ddd');
    }

    //添加新的方法二
    [str]:function(){
    alert('dddddddd');
    }
    }

    Symbol 的内部值

    除了定义自己使用的 Symbol 值以外,ES6 还提供了11个内置的Symbol值,指向语言内部使用的方法。

    Symbol.hasInstance 当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法
    Symbol.isConcatSpreadable 对象的Symbol.isConcatSpreadable属性等于的是一个布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开。
    Symbol. unscopables 该对象指定了使用with关键字时,哪些属性会被with环境排除。
    Symbol.match 当执行str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。
    Symbol.replace 当该对象被str.replace(myObject)方法调用时,会返回该方法的返回值。
    Symbol.search 当该对象被str. search (myObject)方法调用时,会返回该方法的返回值。
    Symbol.split 当该对象被str. split (myObject)方法调用时,会返回该方法的返回值。
    Symbol.iterator 对象进行for…of循环时,会调用Symbol.iterator方法,返回该对象的默认遍历器
    Symbol.toPrimitive 该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
    Symbol. toStringTag 在该对象上面调用toString方法时,返回该方法的返回值
    Symbol.species 创建衍生对象时,会使用该属性

2.14 迭代器

迭代器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。

\1) ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of消费

\2) 原生具备iterator接口的数据(可用for of遍历)

Array Arguments ** Set Map String TypedArray NodeList**

  • fior in 和 for of

    for in 遍历后得到的结果是数组元素的下标

    for of 遍历后得到的结果是元素的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const list=['a','b','c'];
    for(let i in list){
    console.log(i);
    }

    //for of
    for(let i of list){
    console.log(i)
    }

2.15 类内部属性的getter 和 setter (了解)

get 和 set 可以让我们在获取和设置 属性的时候 能有反馈 让程序拥有更多的可能性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class phone{
get name(){
console.log('我被获取了');
}

//使用set 必须要有一个参数 这个参数就是用来接受设置的值的
set name(v){
console.log('我被设置了');
console.log(v);
}
}
let xiaomi =new phone();
//当我们想获取对象的属性的时候 我们希望在获取的时候能做一些事 就需要设置get
console.log(xiaomi.name);

xiaomi.name='小米手机';

2.16 ES6 的数值扩展

**二进制 ob开头 ob1111 **

八进制 0o开头 0o6666

**十进制 **

十六进制 ox开头 oxfff

1.检查一个数是不是有限数

1
2
Number.isFiniter(10);//检测10是不是一个有限数
Number.isFiniter(Math.PI);

**2.检测一个数是不是NaN (NaN 就是是不是数字的意思) **

1
Number.isNaN(NaN);

3.parseInt() 字符串转换为数字

1
Nmuber.parseInt('1112121');

4.Math.trunc() 抹除数字的小数部分

1
Math.trunc(3.123);

5.isInteger() 检查一个数是不是整数

1
NUmber.isinteger(2.1)

6.数字的幂运算

1
2
3**n
//3的n次方

2.17 Set 集合

Set 只允许存放不同的值 我们可以把数组转换成集合来达到数组去重对效果

  • 声明一个集合
    1
    2
    const s2=new Set(); 
    const s1=new Set([1,2,3,4]);

集合的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//声明集合
const s1=new Set();
const s2=new Set([1,2,4,6]);


//添加元素
s1.add(1);
s1.add(s2);

//查看元素的个数
console.log(s1.size);

//删除元素 要删除哪个元素就把那个元素放进去
s2.delete(2);
console.log(s2);

//检查集合中是否包含某元素 has方法
//查看s1 中是否包含2 这个元素
console.log(s1.has(2));

//clear清空
s2.clear();

for (let v of s1) {
console.log(v);
}

集合练习

对象中的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中

  • 数组转集合

    利用集合中不允许有同样的值的这个特性来达到数组去重的效果

    1
    2
    3
    4
    5
    6
    //集合转数组
    const arr=['aaa','bbb','ccc','aaa','ddd'];
    const s=new Set(arr);
    //集合转数组
    const math=[...s];
    console.log(math);

2.18 Map

本身是地图的意思 但是也是有映射的意思 键值对

  • 声明map

    1
    const m=new Map();
  • 添加元素 元素,set( 键,值 )

    键可以是字符串也可以是对象

    1
    2
    m.set('name','张三');
    m.set({},'ahahhahah');
  • 获取元素

    1
    2
    3
    const m=new Map();
    m.set('name','张三');
    console.log(m.get('name'));
  • 删除元素

    1
    m.dlete('name');
  • 检测

    1
    m.has('name');
  • 元素个数

    1
    m.size();
  • 清空

    1
    m.clear();

Map的主要作用是用作缓存

3,ES6

3.1 class介绍与类的创建

类似于java的类 通过class可以创建类

  • 语法

    **class 类名{ **

    ** //构造方法**

    ** **constructor( 属性1, 属性2){

    ** **this.属性1=属性1;

    ** **this.属性2=属性2;

    ** **}

    //方法

    coll( 形式参数){

    ** **方法体;

    ** **}

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Phone{
    constructor(name,price) {
    this.name=name;
    this.price=price;
    }
    call(person){
    console.log(`我可以给 ${person} 打电话`);
    }
    }
    let xiaomi=new Phone('小米',1999);
    xiaomi.call('张三');
  • 注意事项

    1. 构造方法不是必须的
    2. 构造方法只能写一个 constructor 只能写一个

3.2 静态成员

静态成员 属于类 而 不属于实例对象

1
2
3
4
5
6
7
8
9
class Phone{
static name='手机';
static change(){
console.log('我可以改变世界');
}
}
//name 和 change 都是属于类 所以使用类名就可以直接调用
Phone.name;
phone.change();

3.3 继承

class 子类 extends 父类{

** **子类的属性和方法

}

注意 如果要继承父类的属性的话 在construction 里面也写上父类的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script type="text/javascript">
class Phone{
constructor(name,xitong) {
this.name=name;
this.xitong=xitong;
}
call(number){
console.log(`打电话给 ${number}`);
}
}
class xiaomi extends Phone{
constructor(name,xitong,pinpai) {
super(name,xitong);
this.pinpai=pinpai;
}
playGame(aaa){
console.log(aaa);
}
}
const xiaomi1=new xiaomi('小米','安卓','小米手机');
xiaomi1.call(112212);
console.log(xiaomi1.name, xiaomi1.pinpai, xiaomi1.pinpai);
</script>

3.4 类内部的set 和 get

set 和 get 可以让我们对代码的控制更加的灵活和多样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class phone{
get price(){
return 10000;
}
//必须要有一个形参 来接收返回值
set price(value){
this.jiage=value;
}

//静态属性的get和set
static get size(){
return '4英寸';
}

static set size(value){
this.chicun=size;
}
}

3.5 ES6 的数值扩展

**二进制 ob开头 ob1111 **

八进制 0o开头 0o6666

**十进制 **

十六进制 ox开头 oxfff

1.检查一个数是不是有限数

1
2
Number.isFiniter(10);//检测10是不是一个有限数
Number.isFiniter(Math.PI);

**2.检测一个数是不是NaN (NaN 就是是不是数字的意思) **

1
Number.isNaN(NaN);

3.parseInt() 字符串转换为数字

1
Nmuber.parseInt('1112121');

4.Math.trunc() 抹除数字的小数部分

1
Math.trunc(3.123);

5.isInteger() 检查一个数是不是整数

1
NUmber.isinteger(2.1)

6.数字的幂运算

1
2
3**n
//3的n次方

3.6 对象的扩展

  1. 判断两个值是否相等

    object.is(数值1,数值2)

    1
    2
    3
    let n=100;
    let n2-200;
    console.log(Object.is(n1,n2));
  2. 对象的合并

    Object.assign( 对象A ,对象B)

    把B对象的合并到A对象

    返回的是原对象

    1
    2
    3
    4
    5
    6
    7
    8
    const a={
    name:'dddd'
    }
    const b={
    aa:['北京','上海']
    }
    const c=Object.assign(a,b);
    console.log(c);

    对象的合并主要是用在项目的配置上

3.7 数据的浅拷贝

如果复制出来的新的数据 发生了变化 原数据也会发生变化叫做浅拷贝

  1. 直接复制

    arr2的数据改变 arr1的数据也会跟着改变

    1
    2
    3
    let arr=[1,2,3];
    const arr2=arr;
    arr2[1]=0;
  2. 数组

    • **concat() **

      concat() 方法用于连接两个或多个数组。

      concat() 方法不会更改现有数组,而是返回一个新数组,其中包含已连接数组的值。

    1
    2
    3
    let arr=[1,2,3];
    let arr2=[].concat(arr);
    arr2[1]=2222;
    • slice(a,b)

      slice 是截取参数是数组的下标 从a开始到b结束

    1
    2
    let arr=[1,2,3,4];
    let arr2=arr.slice(0,1);
    • 扩展运算符
    1
    let arr=[...oldarr];
  3. 对象
    assign: 是object中的一个成员方法 它的作用是能够进行对象的合并

    1
    2
    3
    4
    5
    6
    7
    const school={
    name:'wyq',
    pos:['武汉']
    }
    const newSchool=Object.assign({},school);
    newSchool.name='dddddddddd';
    console.log(newSchool.name,school.name);

3.8 JSON 实现深拷贝

stringify 能将js对象转换为json格式的字符串

**parse 能将json 格式的字符串转换为js对象 **