关于ES6的复习

1.解构赋值,ES6允许按照一定的模式从数组和对象中提取值,然后对变量进行赋值,这被称为解构赋值,本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值,如果解构不成功,变量的值就等于undefined,不完全解构也被允许,比如左边不是数组但右边是数组。注意:对于Set结构,也可以使用数组的解构赋值。
let [x,y,z]=new Set(['a','b','c']);
这表明了只要某种数据结构具有Iterator结构,都可以采用数组形式的解构赋值。解构赋值允许指定默认值,但是要注意ES6内部使用严格相等运算符(===)判断一个位置是否有值,所以如果一个数组成员不严格等于undefined,默认值是不会生效的。如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到时才会求值。
let [foo=true]=[];//foo==true;
默认值可以引用解构赋值的其他变量,但前提是该变量必须已经声明。
let [x=1,y=x]=[];//x=1;y=2;
let [x=y,y=1]=[];//报错
对象的解构赋值与数组由一个重要的不同,数组的元素时按次序进行排列的,变量的取值是有它的位置决定的,而对象的属性没有次序,变量必须与属性同名才能取到正确的值。
let {bar,foo}={foo:"aaa",bar:'bbb'};//foo='aaa',bar="bbb"
如果变量名和属性名不一样,必须写成下面这样
let {foo:baz}={foo:"aaa"}//baz='aaa'
解构也可以用来解构嵌套结构的对象
let obj={
    p:[
        'hello',
        {y:'world'}
    ]
};
let {p,p:[x,{y}]}=obj;
//x='hello'
//y="world";
//p=[
        'hello',
        {y:'world'}
    ]
  
如果要将一个已经声明的变量用于解构赋值,则必须非常小心;
let x;
{x}={x=1};//报错
let x;
({x}={x:1});//正确
上面代码会报错的原因是因为js会将{x}理解成为一个代码块,从而发生语法错误,只有不将大括号写在行首,避免js将其解释为代码块,才能解决这个问题。数组的本质是特殊的对象,所以可以对数组进行对象属性的解构。
let arr=[1,2,3];
let {0:first,[arr.length-1]:last}=arr;
//first=1;last=3;
字符串的解构赋值:此时的字符串被转换成了一个类似数组的对象。
const [a,b,c,d,e]='hello';
//a:'h',b:'e'....
数组和布尔值的解构:解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。数值和包装对象都有toString属性,因此变量s都能取到值,解构赋值的规则就是,只要等号右边的值不是对象或数组,就先将其转为对象,由于Null和undefined无法转为对象,所以对它们进行解构赋值时都会报错。
let {toString:s}=123;
s===Number.prototype.toString//true;
let {toString:s}=true;
s===Number.prototype.toString//true;
2.Set和Map数据结构:
    Set是一个类似于数组但是成员值都是唯一的,没有重复,可以通过add方法向Set结构加入成员,但是Set结构不会添加重复的值,Set可以接收一个数组(或者具有iterable接口的其他数据结构)作为参数,用来初始化,向Set中加入值时不会发生类型转换,所以5和“5”是两个不同的值,Set内部判断两个值是否相同使用的算法叫做“Same-value equality”,它类似于精确相等运算符(===),主要的区别是Set认为NaN自等,但精确运算符认为不等。
    Set结构的实例有以下属性:constructor和size,constructor是构造函数,默认是Set函数,size属性返回Set实例的成员总数。Set的实例方法分为两类,操作方法和遍历方法,操作方法有add(value):添加某个值,返回Set结构本身。delete(value):删除某个值,返回一个布尔值,表示删除是否成功。has(value):返回一个布尔值,表示参数是否为Set的成员,clear()表示清除所有成员,没有返回值。
    Set结构的实例有4个遍历方法,可用于遍历成员:keys()返回键名的遍历器,values()返回键值的遍历器,entries():返回键值对的遍历器,forEach:使用回调函数遍历每个成员。Set结构没有键名,只有键值,所以keys和values方法的行为完全一致。
for(let item of set.entries()){
    console.log(item);
}
//['a','b'];
//['c','d'];
    Map:JS的对象本质上是键值对的集合(Hash结构),但是只能用字符串作为键,这给它的使用带来了很大的限制。Map类似于对象,也是键值对的集合,但是“键”的范围不局限与字符串,各种类型的值(包括对象)都可以单做键,Object提供了“字符串-值”的对应,Map结构提供了“值-值”的对应,如果需要“键值对”的数据结构,Map比Object更合适。Map构造函数接受数组作为参数,新建Map实例时就指定了两个键-name和title.但事实上不仅仅是数组,任何具有Iterator接口且每个成员都是一个双元素数组的数据结构都可以作为Map构造函数的参数。如果对同一个键多次赋值,那么后来的值就会覆盖前面的值。
const map=new Map();
map.set(['a'],555);
map.get(['a']);//undefined
    只有对同一个对象的引用,Map结构才将其视为同一个键,上面的Set和get方法表面上针对同一个键,实际上却是两个值,内存地址是不一样的,因为get无法读取该键,返回undefined.如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map就将其视为一个键,包括0,-0,NaN.
    Map的属性和操作方法:size返回Map结构的成员数,set(key,value)添加值,get(key)读取key对应的键值,has(key)返回一个布尔值,表示某个键是否在Map数据结构中,delete(key)删除某个键返回布尔值,clear()清除所有成员,没有返回值。Map方法:keys()返回键名的遍历器,values()返回键值的遍历器,entries():返回键值对的遍历器,forEach:使用回调函数遍历每个成员。

    3.Symbol:ES5的对象属性名都是字符串,这容易造成属性名的冲突,为了从根本上解决属性名的冲突,ES6引入了新的原始数据类型Symbol,表示独一无二的值,Symbol值通过Symbol函数生成,也就是说,对象的属性名可以有两种类型,一种是原来就有的字符串,一种就是新增的Symbol类型,只要属性名属于Symbol类型,就是独一无二的,可以保证不会与其他属性名产生冲突。Symbol函数前面不能使用new命令,否则会报错,这是因为生成的Symbol是一个原始数据类型的值,不是对象。也就是说Symbol值不是对象,所以不能添加属性,基本上它就是一种类似于字符串的数据类型。
  
var s1=Symbol('foo');
var s2=Symbol('bar');
//s1=Symbol(foo);s2=Symbol(bar);
//s1.toString() //"Symbol(foo)"
//s2.toString() //"Symbol(bar)"
    如果Symbol的参数是一个对象,就会调用该对象的toString()方法,将其转为字符串,然后才生成一个Symbol值。
const obj={
    toString(){
        return 'abc';
    }
}
const sym=Symbol(obj);
//sym=Symbol(abc);
    Symbol函数只是对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不相等的。
var s1=Symbol();
var s2=Symbol();
s1===s2//fasle;
    Symbol不能与其他类型的值进行运算,否则会报错,Symbol值可以显式转为字符串,另外,Symbol值可以转为布尔值,但是不能转为数值。由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符用于对象的属性名,保证不会出现同名的属性,这对于一个对象由多个模块构成的情况非常有用,能防止某一个键不小心被覆盖。Symbol作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys(),Object.getOwnPropertyNames()返回,但它也不是私有属性,有一个Object.getOwnPropertySymbols方法可以获取队形的所有Symbol属性名,并返回一个结果数组。
var obj={};
var a=Symbol('a');
var b=Symbol('b');
obj[a]='Hello';
obj[b]='World';
var objectSymbols=Object.getOwnPropertySymbols(obj);
objectSymbols //[Symbol(a),Symbol(b)]
    Symbol.for()和Symbol.keyFor(),有时候希望重新使用一个Symbol值,Symbol.for()方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值,如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串位名称的Symbol值。Symbol.for()与Symbol()这两种写法都会生成新的Symbol.前者会被登记在全局环境中供搜索,而后者不会。Symbol.for不会在每次调用时都返回一个新的Symbol类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。
var s1=Symbol.for('foo');
var s2=Symbol.for('foo');
s1===s2//true
    Symbol.keyFor()方法返回一个已登记的Symbol类型值的key,未登记的值返回undefined.
    
var s1=Symbol.for("foo");
Symbol.keyFor(s1)//"foo"
var s2=Symbol("foo");
Symbol.keyFor(s2)//undefined
    Iterator接口与for...of循环:遍历器就是这样一种机制,它是一种接口,为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署Iterator接口,就可以完成遍历操作。Iterator的作用有三个,一是为各种数据结构提供一个统一的、简便的访问接口,二是使得数据结构的成员能够按某种次序排列,三是ES6创造了一种新的遍历命令,for...of循环,Iterartor接口主要供for...of消费。Iterator创建一个指针对象,指向当前数据结构的起始位置,也就是说遍历器对象本质上就是一个指针对象,通过每一次调用指针对象的next方法完成遍历,每一次返回一个包含value和done两个属性的对象,其中value属性是当前成员的值,done属性是一个布尔值。value返回当前位置的成员,done属性是一个布尔值,表示遍历是否结束。
    Iterator接口的目的是为所有的数据结构提供一种统一的访问机制,即for...of循环,当使用for...of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。数据结构只要部署了Iterator接口,这种数据结构就可以称为可遍历的。目前原生具有Iterator接口的数据结构如下:Array,Map,Set,String,TypedArray,函数的arguments对象,NodeList对象。


4.关于Es6中的类:ES6中的类定义方法时,前面不需要加上function这个保留字,直接把函数的定义放进去就可以了,方法之间不需要逗号分隔,加了会报错。
  
class Point{
    //
}
typeOf Point//"function"
Ponit===Point.prototype.constructor//true
类的数据类型就是函数,类本身就指向构造函数,使用的时候也是直接对类使用new命令,跟构造函数的用法完全一致。类的所有方法都定义在类的prototype属性上,在类的实例上调用方法,其实就是调用原型上的方法。
class Point{
    constructor(){
        //...
    }
    toString(){
        //...
    }
}
//等同于
Ponit.prototype={
    constructor(){},
    toString(),
}
let point=new Point();
point.constructor===Point.prototype.constructor
可以使用Object.assign一次向类添加多个方法,prototype对象的constructor属性直接指向“类”本身,这与ES5的行为是一致的,另外类里面的方法是不可枚举的。
class Point{
    constructor(){
        //...
    }
}
Object.assign(Point.prototype,{
    toString(){},
    toValue(){}
})
Point.prototype.constructor===Point//true
    constructor方法是类的默认方法,通过new命令生成对象实例时自动调用该方法。一个类必须要有constructor方法,如果没有显式定义,一个空的constructor方***被默认添加。类必须使用new来调用,否则会报错,这是它跟普通函数的一个主要区别,普通函数不用new也可以执行。关于类的实例对象,与ES5一样,实例的属性除非显式定义在其本身(即this对象上),否则都是定义在原型。即class上。
class Point{
    constructor(x,y){
        this.x=x;
        this.y=y;
    }
    toString(){
        return 'yoxi'
    }
}
var point=new Point(2,3);
ponit.hasOwnProperty('x')//true
point.hasOwnProperty('toString')//false
point.__proto__.hasOwnProperty('toString')//true
    上面的代码中,x和y都是实例对象point自身的属性(因为是定义在this变量上),所以返回true,而toString是原型对象的属性,所以返回false,这些都与ES5的行为保持一致。甚至与ES5一样,类的所有实例共享一个原型对象。生产环境中可以使用Object.getPrototypeOf方法来获取实例对象的原型,然后再来为原型添加方法和属性。
    与函数一样,Class也可以使用表达式的形式进行定义:但是类的名字是声明的那个名字,不是后面那个名字,后面那个名字也可以省略。
const myClass=class Me(){
    
}
let ins=new myClass();
ins.getClassName()//Me
const myClass=class Me(){
    constructor(name){
        this.name=name;
    }
    sayName(){
        console.log(this.name)
    }
}('yoxi')//这是一个立即执行类
myClass.sayName();//"yoxi"
类不存在变量声明提升:这可以和继承相关联,必须保证子类在父类之后定义
new Foo();//报错
class Foo(){}
类的私有方法:1.方法前面加_,不严谨。2.将私有方法移出模块 3.利用Symbol值的唯一性将私有方法的名字命名为一个Symbol值。
类的私有属性:1.方法在属性名之前,用#来表示。
类的this指向:类的方法内部如果含有this,它将默认指向类的实例,但是如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境,因为找不到方法而奥错,
类的取值函数和存值函数:赋值和读取行为都被自定义了
class MyClass{
    constructor(){
        //...
    }
    get prop(){
        return 'getter';
    }
    set prop(value){
        console.log('setter:'+=value)
    }
}
let inst=new MyClass();
inst.prop=123;//setter:123
inst.prop//'getter'
所有在类中定义的方法都会被实例继承,如果在一个方法前面加上static关键字,就表示该方法不会被实例继承,而是直接通过类调用,称为“静态方法”,该方法可以直接在类上进行调用,如果在实例上调用该方法,则会抛出一个错误,表示不存在该方法,同时父类的静态方法可以被子类继承。静态属性指的是类本身的属性,而不是定义在实例对象上的属性。静态属性指的是类本身的属性,而不是定义在实例对象上的属性,也是前面加static,类的实例属性可以就这么写,不用一定定义在constructor方法里了。
class Foo(){
    myProp=42;
}


5.关于类的继承
Class可以通过extends关键字实现继承,在如下代码constructor方法和toString()方法之中都出现了super关键字,它表示父类的构造函数,用来新建父类的this对象,子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象。ES5的继承实质是先创造子类的实例对象this,然后将父类的方法添加到this上,ES6的继承机制完全不同,实质是先创造父类的实例对象this,然后再用子类的构造函数修改this.如果子类没有定义constructor方法,那么这个方法就会被默认添加。而且千万要记住,在子类的构造函数中,只有调用super之后才可以使用this关键字,否则会报错,这是因为子类实例的构建时基于对父类的实例进行加工,只有super方法才能返回父类实例。
class Point(){
    //
}
class ColorPoint extends Point{
    constructor(x,y,color){
        super(x,y);//调用父类的constructor(x,y)
        this.color=color;
    }
    toString(){
        return this.color+''+super.toString();
        //调用父类的toString()
    }
}
Object.getPropertyOf方法可以用来从子类上获取父类。
Object.getPrototypeOf(ColorPoint)===Point;//true
super这个关键字既可以当做函数使用,也可以当做对象使用,在这两种情况下,它的用法完全不同。第一种情况下,super作为函数调用代表父类的构造函数,ES6要求,子类的构造函数必须执行一次super函数,super虽然代表了父类A的构造函数,但是返回的是子类B的实例,且super内部的this指向的是子类,所以super()在这里相当于父类.prototype.constructor.call(this),作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。而super作为对象在普通方法中指向父类的原型对象:在静态方法中指向父类。由于super指向父类的原型对象,所以定义在父类实例上的方法或属性是无法通过super调用的。通过super调用父类的方法时,super会绑定子类的this.
    Class作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。
    1.子类的__proto__属性表示构造函数的继承,总是指向父类。
    2.子类的prototype属性的__proto__属性表示方法的继承,总是指向父类的prototype属性
class A{
    
}
class B extends A{
    
}
B.__proto__===A//true
B.prototype.__proto__===A.prototype//true
    extends关键字后面可以跟多种类型的值,class B extends A{},A只要是有一个prototype属性的函数,就能被B继承,由于函数都有prototype属性,因此A可以是任意函数。有以下三种特殊情况
    1.子类继承Object类,这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例。2.不存在任何继承,此时A作为一个基类就是一个普通函数,所以A.__proto__==Function.prototype. 3.子类继承null,这种情况和第二种情况很像。


















    




全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务