zoukankan      html  css  js  c++  java
  • 【读书笔记】【深入理解ES6】#4-扩展对象的功能性

    对象类别

    ES6规范清晰定义了每一个类别的对象。

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

    对象字面量语法扩展

    属性初始化的简写

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

    当一个对象的属性与本地变量同名时,不必再写冒号和值,简单的只写属性名即可。

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

    对象方法的简写语法

    var person = {
        name: "JiaJia",
        sayName: function() {
            console.log(this.name);
        }
    }
    

    ES6中语法更简洁,消除了冒号和function关键字。

    var person = {
        name: "JiaJia",
        sayName() {
            console.log(this.name);
        }
    }
    

    简写方法可以使用 super 关键字。

    可计算属性名(Computed Property Name)

    在ES5及早期版本中,如果想要通过计算得到属性名,就需要用方括号代替点标记。

    var person = {},
        lastName = "last name";
    
    person["first name"] = "JiaJia";
    person[lastName] = "Liu";
    
    console.log(person["first name"]); // "JiaJia"
    console.log(person[lastName]); // "Liu"
    

    此外,在对象字面量中,可以直接使用字符串字面量作为属性名称。

    var person = {
        "first name": "JiaJia"
    };
    
    console.log(person["first name"]); // "JiaJia"
    

    这种模式适用于属性名提前已知或可被字符串字面量表示的情况。
    如果属性是通过计算得到的,例如存在变量中,则无法使用在ES5中使用对象字面量定义该属性。

    在ES6中,可在对象字面量中使用可计算属性名称。

    let lastName = "last name";
    let person = {
        "first name": "JiaJia",
        [lastName]: "Liu"
    };
    
    console.log(person["first name"]); // "JiaJia"
    console.log(person[lastName]); // "Liu"
    

    新增方法

    Object.is() 方法

    ES6引入 Object.is() 方法来弥补全等运算符(===)的不准确运算。
    这个方法接受两个参数,如果这两个参数类型相同且具有相同的值,则返回true。

    console.log(+0 == -0); // true
    console.log(+= === -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.is() 方法来说,其运行结果在大部分情况下与 === 运算符相同,唯一的区别在于 +0 和 -0 被识别为不相等并且 NaN 与 NaN 等价。

    Object.assign() 方法

    Object.assign() 方法可以接受任意数量的源对象,并按指定的顺序将属性复制到接收对象中。
    如果多个源对象具有同名属性,则排位靠后的源对象会覆盖排位靠前的。

    var receiver = {};
    
    Object.assign(receiver,
        {
            type: "js",
            name: "file.js"
        },
        {
            type: "css"
        }
    );
    
    console.log(receiver.type); // "css"
    console.log(receiver.name); // "file.js"
    

    访问器属性

    Object.assign() 方法不能将提供者的访问器属性复制到接受对象中。
    提供者的访问器属性最终会转变为接受对象中的一个数据属性。

    var receiver = {},
        supplier = {
            get name() {
                return "file.js";
            }
        };
    
    Object.assign(receiver, supplier);
    
    var supplierDescriptor = Object.getOwnPropertyDescriptor(supplier, "name");
    console.log(supplierDescriptor.value); // undefined
    console.log(supplierDescriptor.get); // ƒ name() { return "file.js"; }
    
    var receiverDescriptor = Object.getOwnPropertyDescriptor(receiver, "name");
    
    console.log(receiverDescriptor.value); // "file.js"
    console.log(receiverDescriptor.get); // undefined
    

    重复的对象字面量属性

    ES5严格模式中加入了对象字面量重复属性的校验,当同时存在多个同名属性时会抛出错误。

    "use strict";
    
    var person = {
        name: "JiaJia",
        name: "DLPH"
    }
    

    ES6中重复属性检查被移除,无论是严格模式还是非严格模式,代码不再检查重复属性,对于每一组重复属性,都会选取最后一个取值。

    "use strict";
    
    var person = {
        name: "JiaJia",
        name: "DLPH"
    }
    
    console.log(person.name); // "DLPH"
    

    自有属性枚举顺序

    ES5中未定义对象属性的枚举顺序,由JS引擎厂商自行决定。
    ES6中严格规定了对象的自有属性被枚举时的返回顺序。

    这会影响到 Object.getOwnPropertyName() 方法及 Reflect.ownKeys 返回属性的方式,Object.assign() 方法处理属性的顺序也将随之改变。

    自有属性枚举顺序的基本规则:

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

    数值键,尽管在对象字面量中的顺序是随意的,但在枚举时会被重新组合和排序;
    字符串键紧随数值键,并按照定义的顺序依次返回;
    随后动态加入的字符串键最后输出。

    增强对象原型

    改变对象的原型

    正常情况下,无论是通过构造函数还是 Object.create() 方法创建对象,其原型是在对象被创建时指定的。对象原型在实例化后保持不变,直到ES5都是JS编程最重要的设定之一。
    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 引用

    如果你想重写对象实例的方法,又需要调用与它同名的原型方法,则ES5中可以这样实现:

    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
    

    主要是这句代码

    Object.getPrototypeOf(this).getGreeting.call(this)
    

    使用 Object.getPrototypeOf(this) 来确保获取对象的原型;
    使用 call(this) 确保原型方法中的 this。

    功能是实现了,但是有些复杂。
    ES6中引入了 super 关键字简化上述操作。
    Super 引用相当于指向对象原型的指针,实际上也就是 Object.getPrototypeOf(this) 的值。

    上述 friend 对象的定义可以简化成这样:

    let friend = {
        getGreeting() {
            return super.getGreeting() + ", hi!";
        }
    };
    

    必须在使用简写方法的对象中使用 Super 引用,在其他方法声明中使用会导致语法错误。

    let friend = {
        getGreeting: function() {
            // 语法错误
            return super.getGreeting() + ", hi!";
            // Uncaught SyntaxError: 'super' keyword unexpected here
        }
    };
    

    Super引用在多重继承的情况下非常有用,因为在这种情况下,使用 Object.getPrototypeOf(this) 方法将会出现问题。

    let person = {
        getGreeting() {
            return "Hello";
        }
    };
    
    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()); // 抛出错误
    // Uncaught RangeError: Maximum call stack size exceeded
    

    执行到 relative.getGreeting() 时抛出栈溢出的错误。
    relative 没有 getGreeting() 方法,所以根据原型链执行的是 friend 中的 getGreeting() 方法。执行到 Object.getPrototypeOf(this) 时,由于当前 this 指向的是 relative,所以这里取到的还是 friend 对象,于是再次调用了 friend 的 getGreeting() 方法,成了一个死循环。最终导致了栈溢出。

    Note by JiaJia

    看到这里突然想到前面在#3函数中的尾调用优化。如果 friend 的 getGreeting() 方法的return 没有加上后面的一段字符串的化,是符合尾调用优化的条件的。这样一来,这里就真的成了一个死循环,还不会导致栈溢出错误。

    正式的方法定义

    ES6中正式将方法定义为一个函数,它会有内部的 [[HomeObject]] 属性来容纳这个方法从属的对象。

    let person = {
        // 是方法
        getGreeting() {
            return "Hello";
        }
    }
    
    // 不是方法
    function shareGreeting() {
        return "Hi!";
    }
    

    getGreeting() 方法的 [[HomeObject]] 属性值为 person;shareGreeting() 方法没有明确定义 [[HomeObject]] 属性。

    Super 的所有引用都是通过 [[HomeObject]] 属性来确定后续的运行过程。

    1. 在 [[HomeObject]] 属性上调用 Object.getPrototypeOf() 方法来检索原型的引用;
    2. 搜寻原型找到同名函数;
    3. 设置 this 绑定并且调用相应的方法。

    多重继承时改成使用 super 引用,就可以得到正确的结果。

    let person = {
        getGreeting() {
            return "Hello";
        }
    };
    
    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!"
    
  • 相关阅读:
    Web Site Administration Tool 文章收集
    Sql中补零方法及其它
    PowerDesigner 教程
    什么是Zend
    什么是CMS系统?
    谈谈今天遇到的编译工具查错的问题
    Lec4快速排序
    堆排序 zz
    强大的Python
    Lec6待学习的堆排序
  • 原文地址:https://www.cnblogs.com/Ryukaka/p/7885757.html
Copyright © 2011-2022 走看看