ES6学习-扩展对象的功能性

在JavaScript中,几乎每一个值都是某种特定类型的对象,所以ES6加强了对象的功能性。

对象类别

·普通对象:具有JavaScript对象所有默认内部行为。
·特异对象:具有某些与默认行为不符的内部行为。
·标准对象:ECMAScript6规范中定义的对象,例如:Array,Date等。标准对象既可以是普通对象,也可以是特异对象。
·内建对象:脚本开始执行时存在于JavaScript执行环境中的对象,所有标准对象都是内建对象。

对象字面量语法扩展

属性初始值的简写

在ES5中,对象字面量只是简单的键值对集合,这意味着初始化属性值时会有一些重复。

function createPerson(name,age){
    return {
        name:name,
        age:age
    };
}

这段代码中这个函数属性名称与函数的参数相同,在ES6中,通过使用属性初始化的简写语法,就可以消除这种属性名称与局部变量之间的重复书写。

//这是简写
function createPerson(name,age){
    return {
        name,
        age
    };
}

当对象字面量里只有一个属性的名称时,JavaScript引擎会在可访问作用域中查找其同名变量;如果找到,就会把变量的值赋值给对象字面量的同名属性。这样做的好处是:有助于消除命名错误。

对象方法的简写语法

ES6也改进了为对象字面量定义方法的语法。

//  ES6之前
var persoon={
    name:"cc",
    sayName:function(){
        console.log(this.name);
    }
};
//ES6中消除了冒号和function关键字
var person={
    name:"cc",
    sayName(){
        console.log(this.name);
    }
}

简写方法和之前的定义方法唯一不同的是简写方法可以使用super关键字(稍后讨论)。

可计算属性名

在ES6之前,如果属性名称被包含在变量里或者通过计算获得该变量的值,那么ES5并不能为一个对象字面量定义该属性的。
而在ES6中,可在对象字面量中使用可计算属性名称,其语法与引用对象实例的可计算属性名称相同,也是使用方括号。

//例子一
let lastName="last name";
let person={
    "first name":"Nicholas",
    [lastName]:"Zakes"
};
console.log(person["first name"]);//"Nicholas"
console.log(person[lastName]);//"Zakes"
//例子二
var suffix="name";
var person={
    ["first"+suffix]:"Nicholas",
    ["last"+suffix]:"Zakes"
};
console.log(person["firstname"]);//"Nicholas"
console.log(person["lastname"]);//"Zakes"

新增方法

ES6为了让某些任务也更容易完成,在全局对象Object对象中引入了新的方法。

Object.is()方法

ES5前,开发者习惯用===确定比较值,但是+0和-0在JavaScript中是是不同实体,然后+0===-0,同样,NaN===NaN会返回false,而ES6中引入Object.is()弥补全等运算符的不准确性。

console.log(+0===-0);//true
console.log(NaN===NaN);//false
console.log(Object.is(+0,-0));//false
console.log(Object.is(NaN,NaN));//true
Object.assign()方法

混合(Mixin)是JavaScript中实现对象组合最流行的模式,它可以实现一个对象接收来自另一个对象的属性和方法。

function mixin(receiver,supplier){
    Object.keys(supplier).forEach(function(key){//遍历自身属性,并添加到新对象中
        receiver[key]=supplier[key];
    });
    return receiver;
}

这样一来利用这个函数不通过继承就可以获得新属性。

function EventTarget(){/*...*/}
EventTarget.prototype={
    constructor:EventTarget,
    emit:function(){return "cc";},
    on:function(){/*...*/}
};
var myObject={};
mixin(myObject,EventTarget.prototype);
myObject.emit("somethingChanged");//"cc"

这种混合模式非常流行,所以在ES6中添加了Object.assign()方法实现了相同的功能,这个方法接收对象和任意数量的源对象,最终返回对象。值得注意的是不能复制访问器属性。
任何使用mixin()的方法都可以直接使用这个方法。

function EventTarget(){/*...*/}
EventTarget.prototype={
    constructor:EventTarget,
    emit:function(){return "cc";},
    on:function(){/*...*/}
};
var myObject={};
Object.assign(myObject,EventTarget.prototype);
myObject.emit("somethingChanged");//"cc"

Object.assign()可以接收任意数量的源对象,并按指定顺序将属性复制到接收对象中,但是排位靠后的优先。

function first(){/*...*/}
first.prototype={
    constructor:"first",
    emit:function(){return "first";},
    on:function(){/*...*/}
};
function second(){/*...*/}
first.prototype={
    constructor:"second",
    emit:function(){return "second";},
    on:function(){/*...*/}
};
var myObject={};
mixin(myObject,first.prototype,second.prototype);
myObject.emit("somethingChanged");//"second"

重复的对象字面量属性

ES5严格模式中中,对象加入了对象字面量重复性的校检,当多个命名属性时会抛出错误。
而ES6中不会,ES6中会选取最后一个值。

//ES6
var person={
    name:"cc",
    name:"ccg"
}
console.log(person.name);//"ccg"

自有属性枚举顺序

ES5中未定义对象属性的枚举顺序,由JavaScript引擎厂商自行决定,然而ES6中严格规定了对象的自有属性被枚举的返回顺序。
自有属性的枚举顺序的基本规则是:

  1. 所有的数字键按升序排序。
  2. 所有的字符串键按照它们被加入对象的顺序排序。
  3. 所有symbol键按照它们被加入对象的顺序排序。
var obj={
    a:1,
    0:1,
    c:1,
    2:1,
    b:1,
    1:1
};
obj.d=1;
console.log(Object.getOwnPropertyNames(obj).join(""));//012acbd

增强对象原型

ES6对原型进行了改进。

改变对象的原型

正常情况下,对象原型在实例化后就无法改变了,但是ES6中添加了Object.setPrototypeOf()方法来改变这一现状。

let person={
    getGreeting(){
        return "Hello";
    }
};
let dog={
    getGreeting(){
        return "Woof";
    }
};
//以person对象为原型
let friend=Object.create(person);
console.log(friend.getGreeting());//Hello
console.log(Object.getPrototypeOf(friend)===person);//true
//将原型设置为dog
Object.setPrototypeOf(friend,dog);
console.log(friend.getGreeting());//Woof
console.log(Object.getPrototypeOf(friend)===dog);//true
简化原型访问的Super引用

Super可以更快捷的访问原型,请看例子。

let person={
    getGreeting(){
        return "Hello";
    }
};
let dog={
    getGreeting(){
        return "Woof";
    }
};
let friend={
    getGreeting(){
        return Object.getPrototypeOf(this).getGreeting.call(this)+",hi";
    }
};
//将原型设置为person
Object.setPrototypeOf(friend,person);
console.log(friend.getGreeting());//"Hello,hi"
console.log(Object.getPrototypeOf(friend)===person);//true
//将原型设置为dog
Object.setPrototypeOf(friend,dog);
console.log(friend.getGreeting());//"Woof,hi"
console.log(Object.getPrototypeOf(friend)===dog);//true
//用super可以准确找到指向当前对象的原型
let friend={
    getGreeting(){
        return super().getGreeting()",hi";
    }
};

Super在多重继承非常有用,请看例子

let person={
    getGreeting(){
        return "Hello";
    }
};
//以person对象为原型
let friend={
    getGreeting(){
        return Object.getPrototypeOf(this).getGreeting.call(this)+",hi";
    }
};
Object.setPrototypeOf(friend,person)
//原型是friend
let relative=Object.create(friend);
console.log(person.getGreeting());//"Hello"
console.log(friend.getGreeting());//"Hello,hi"
console.log(relative.getGreeting());//error!
//call(this)的定位会使程序进入递归调用,直到触发栈溢出报错

如果不使用call(this),程序会一级一级向上找,直到找到最大的构造函数Object。

let person={
    getGreeting(){
        return "Hello";
    }
};
//以person对象为原型
let friend={
    getGreeting(){
        return Object.getPrototypeOf(this).getGreeting()+",hi";
    }
};
Object.setPrototypeOf(friend,person);
//原型是friend
let relative=Object.create(friend);
console.log(person.getGreeting());//"Hello"
console.log(friend.getGreeting());//"Hello,hi"
console.log(relative.getGreeting());//"Hello,hi,hi"

最佳实践是使用Super,因为它不是动态变化的,总会指向正确的对象。

let person={
    getGreeting(){
        return "Hello";
    }
};
//以person对象为原型
let friend={
    getGreeting(){
        return super.getGreeting()+",hi";
    }
};
Object.setPrototypeOf(friend,person);
//原型是friend
let relative=Object.create(friend);
console.log(person.getGreeting());//"Hello"
console.log(friend.getGreeting());//"Hello,hi"
console.log(relative.getGreeting());//"Hello,hi"

正式的定义方法

ES6以前从未正式定义“方法”的概念,而在ES6中被正式定义为一个函数,这个函数内部包含[HomeObject]属性容纳这个方法从属的对象。

let person={
    //是方法
    getGreenting(){
        return "Hello";//明确赋值person,[HomeObject]的属性值为person
    }
};
//不是方法
function shareGreeting(){
    return "Hi";//没有明确赋值给一个对象,[HomeObject]无法定义
}

值得注意的点是,Super的所有引用都是要通过[HomeObject]属性来确定后续的进程。第一步是在[HomeObject]属性值上调用Object.getPrototypeOf()来检索原型的引用率;然后搜索原型寻找同名函数;最后,设置this值并且调用相应的方法。

let person={
    getGreenting(){
        return "Hello";
    }
};
let friend={
    getGreenting(){
        return super.getGreenting()+",hi!";
    }
};
//以person对象为原型
Object.setPrototypeOf(friend,person);
console.log(friend.getGreenting());//Hello,hi!

你可能感兴趣的:(ES6学习-扩展对象的功能性)