前言
该部分为书籍 深入理解 ES6 第四章 (扩展的对象功能) 笔记
对象类别
对象类别包括:
普通对象: 拥有 JS 对象所有默认的内部行为
奇异对象: 其内部行为在某些方面有别于默认行为
标准对象: 在 ES6 中被定义的对象, 例如 Array ,Date 等等. 标准对象可以是普通的, 也可以是奇异的
内置对象: 在脚本开始运行时由 JS 运行环境提供的对象. 所有的标准对象都是内置对象
对象字面量语法的扩展
当对象字面量中的属性只有名称时, JS 引擎会在周边作用域查找同名变量
- function createPerson(name, age) {
- return {
- name,
- age
- };
- }
方法简写: 可以省略冒号与 function 关键字
同 ES5 方法的区别: 方法简写能使用 super, 而非简写的方法则不能使用.
- var person = {
- name: "Nicholas",
- // 这个也是标准函数, 不是箭头函数
- sayName() {
- console.log(this.name);
- }
- };
需计算属性名
在 ES5 中, 方括号允许你将变量或字符串字面量指定为属性名, 而在字符串中允许存在作为标识符时会导致语法错误的特殊字符.
缺陷: 不能在对象属性中使用变量, 或者通过计算才能获得属性的
- var person = {},
- lastName = "last name";
- person["first name"] = "Nicholas";
- person[lastName] = "Zakas";
- console.log(person["first name"]); // "Nicholas"
- console.log(person[lastName]); // "Zakas"
- // === ES5 中不允许的 ===
- var person = {
- [lastName]:'Zakas'
- }
- person["first" + suffix];
在 ES6 中改善了其计算属性的缺陷
- var lastName = "last name";
- var suffix = "name";
- var person = {
- "first name": "Nicholas",
- [lastName]: "Zakas",
- ["first" + suffix]: "Nicholas",
- };
- console.log(person["first name"]); // "Nicholas"
- console.log(person[lastName]); // "Zakas"
新增方法
ES 从 ES5 开始就有一个设计意图: 避免创建新的全局函数, 避免在 Object 对象的原型上
添加新方法, 而是尝试寻找哪些对象应该被添加新方法. 因此, 对其他对象不适用的新方法
就被添加到全局的 Object 对象上.
Object.is()方法
严格相等运算符(===) 会认为 +0 与 -0 相等, 也会认为 NaN === NaN 会返回 false.
而 Object.is() 修复了这些特殊表现
- console.log(+0 == -0); // true
- console.log(+0 === -0); // true
- console.log(Object.is(+0, -0)); // false
- console.log(NaN == NaN); // false
- console.log(NaN === NaN); // false
- console.log(Object.is(NaN, NaN)); // true
- console.log(5 == 5); // true
- console.log(5 == "5"); // true
- console.log(5 === 5); // true
- console.log(5 === "5"); // false
- console.log(Object.is(5, 5)); // true
- console.log(Object.is(5, "5")); // false
Object.assign() 方法: 混入, 一个对象会从另一个对象中接收属性与方法
由于方法内部是使用 赋值运算符 (=) 的, 它就无法将访问属性复制到接受者上(会调用访问器属性的 get 的方法)
- var receiver = {};
- Object.assign(receiver,
- {
- type: "js",
- name: "file.js"
- },
- // 同属性, 后面的会覆盖前面的
- {
- type: "CSS"
- }
- );
- console.log(receiver.type); // "css"
- console.log(receiver.name); // "file.js"
- // === 供应者的访问器属性就会转变成接收者的数据属性 ===
- var receiver = {},
- supplier = {
- get name() {
- return "file.js"
- }
- };
- Object.assign(receiver, supplier);
- var descriptor = Object.getOwnPropertyDescriptor(receiver, "name");
- console.log(descriptor.value); // "file.js"
- console.log(descriptor.get); // undefined
重复的对象字面量属性
在 ES5 的严格模式下, 为重复的对象字面量属性引入了一个检查, 若找到重复的属性名, 就会抛出错误
在 ES6 中, 不管严格模式还是非严格模式, 重复定义对象字面量属性不会抛出错误, 当存在重复属性时, 排在后面的属性的值会覆盖后面的值
自有属性的枚举顺序
在 ES5 中, 并没有定义对象属性的枚举顺序, 而在 ES6 中则严格定义了对象自有属性在被枚举时返回的顺序.
自有属性枚举时基本顺序如下:
所有的数字类型键, 按升序排列
所有的字符串类型键, 按被添加到对象的顺序排列
所有的符号类型键, 也按添加顺序排列
- var obj = {
- a: 1,
- 0: 1,
- c: 1,
- 2: 1,
- b: 1,
- 1: 1
- };
- obj.d = 1;
- console.log(Object.getOwnPropertyNames(obj).join("")); //"012acbd"
这对 Object.getOwnPropertyNames() 与 Reflect.ownKeys (详见第十二章)如何返回属性造成了影响, 还同样影响了 Object.assign() 处理属性的顺序.
for-in 循环的枚举顺序仍未被明确规定, 因为并非所有的 JS 引擎都采用相同的方式.
而 Object.keys() 和 JSON.stringify() 也使用了与 for-in 一样的枚举顺序.
更强大的原型
在 ES5 中, 对象的原型在初始化完成后 ( 通过构造器或 Object.create() 方法 ) 会保持不变.
在 ES6 中, 添加了 Object.setPrototypeOf() 方法, 允许修改任意指定对象的原型.
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
在 ES5 中, 若要覆盖对象实例的一个方法, 但依然要调用原型上的同名方法, 可采取的方法如下:
- let person = {
- getGreeting() {
- return "Hello";
- }
- };
- let dog = {
- getGreeting() {
- return "Woof";
- }
- };
- let friend = {
- getGreeting() {
- // 采集到原型对象直接调用原型对象上的方法, 并直接调用, 使用 call 是为了保持 this 指针的正确
- 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
但是多级继承时, 此方法是不适用的
- let person = {
- getGreeting() {
- return "Hello";
- }
- };
- // 原型为 person
- let friend = {
- getGreeting() {
- // 这里会报错, 这是因为此时 this 的值是 relative , 而 relative 的原型是 friend 对象, 这样 friend.getGreeting().call() 调用就会导致进程开始反复进行递归调用, 直到发生堆栈错误.
- 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!
在 ES6 中, super 引用是指向当前对象的原型的一个指针, 并且 super 并非是动态的, 它总是能指向正确的对象
super 引用并不是在调用时才确定的, 而是根据对象 (需要用在对象的方法简写 和 class 语法中) 的原型, 始终指向当前对象的原型
不能在方法简写之外的情况使用 super, 会导致语法错误
- let person = {
- getGreeting() {
- return "Hello";
- }
- };
- // 原型为 person
- let friend = {
- getGreeting() {
- // 这里 super 指向的就是当前对象 (friend) 的原型, 并且 this 指针也是根据调用时的对象决定
- 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]] 内部属性的函数, 此内部属性指向该方法所属的对象
任何对 super 的引用都会使用 [[HomeObject]] 属性来判断要做什么. 第一步是在[[HomeObject]] 上调用 Object.getPrototypeOf() 来获取对原型的引用; 接下来, 在该原型上查找同名函数; 最后, 创建 this 绑定并调用该方法.
- let person = {
- // 方法
- getGreeting() {
- return "Hello";
- }
- };
- // 并非方法
- function shareGreeting() {
- return "Hi!";
- }
来源: http://www.jianshu.com/p/68224ac7416d