最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Javascript基础深入: 对象 (Object)

    正文概述 掘金(式溪)   2021-01-16   382

    最近阅读 JavaScript高级程序设计(第4版),复习一下JS的基础知识点,发现好多JS知识没有深入了解,写个笔记,记录一下从书中学到的知识。

    defineProperty

    故名思义,defineProperty是用来定义属性,平时直接定义属性只能定义值,而它能定义数据属性的特性。JS数据属性的特性有:

    Configurable: 表示属性是否可修改特性或删除属性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特 性都是true。

    Enumerable: 表示属性可否遍历,即可否用for…in返回,默认是true。

    Writable: 设置可否修改属性的值,默认是true。

    Value: 属性实际的值,即平时读写的值,默认是undefined。

    用一个例子来说明defineProperty的用法:

    let person = {};
    Object.defineProperty(person, 'name', {
      configurable: true,
      writable: false,
      value: "tom"
    });
    
    console.log(person.name); // tom
    person.name = "greg";
    console.log(person.name); // tom
    

    defineProperty接收三个参数,第一个是对象本身,第二个是属性名,第三名是设定,要用对象表示。

    访问器属性

    defineProperty还可以修改对象的访问器,所谓访问器可以理解为读写属性时调用的函数。用一个例子说明用法:

    let book = {
    year_: 2017,
    edition: 1
    };
    
    Object.defineProperty(book, "year", {
    get() {
    return this.year_;
    },
    set(newValue) {
    if (newValue > 2017) {
    this.year_ = newValue;
    this.edition += newValue - 2017;
    }
    }
    });
    book.year = 2018;
    console.log(book.edition);  // 2
    

    当修改year属性时,执行year的set访问器。

    注意一点,使用defineProperty是修改对象本身,如果希望不修改对象,又想为它增加特性,可以使用代理 (Proxy)。

    合并对象

    如果想合并一个或多个对象为一个,可以使用assign,它的第一个参数为最后合并返回的对象,其余参数为需要合并的对象。合并从第二个参数直到最后一个。举个例:

    dest = { id: 'dest' };
    result = Object.assign(dest, { id: 'src1', a: 'foo' }, { id: 'src2', b: 'bar' });
    
    // Object.assign 会覆盖重复的属性
    console.log(result); // { id: src2, a: foo, b: bar }
    

    如果只是简单想合并对象,也可以使解构来实现:

    result = { id: src2, a: foo, b: bar }
    const obj = {a:45,b:"hello",...result};
    console.log(obj) // {a: "foo", b: "bar", id: "src2"}  会覆盖重复的属性
    

    最后要注意的是,assign只能浅复制,即如果合并对象中的属性是对象或数组,返回的对象的相应属性的指针也是指向该对象或数组。如果想实现深复制,要自己实现或使用lodash等工具函数。

    属性值简写

    平时创建对象,需要键/值定义,如果希望用变量名为属性名,变量值为属性值,可以使用简写形式:

    const name = "Mary";
    const o = {name};
    console.log(o);  // {name: "Mary"}
    

    可计算属性

    ES6后,属性名不仅可以静态定义,还可以动态定义:

    const nameKey = 'name';
    const ageKey = 'age';
    const jobKey = 'job';
    let uniqueToken = 0;
    function getUniqueKey(key) {
    return `${key}_${uniqueToken++}`;
    }
    let person = {
    [getUniqueKey(nameKey)]: 'Matt',
    [getUniqueKey(ageKey)]: 27,
    [getUniqueKey(jobKey)]: 'Software engineer'
    };
    console.log(person); // { name_0: 'Matt', age_1: 27, job_2: 'Software engineer' }
    

    我们可以用中括号引用函数或变量,动态定义属性名。

    对象解构

    如果只是想要读取对象某个属性,可以使用解构:

    const obj = {a: 5, b: 6};
    let {a} = obj;
    console.log(a); // 5
    

    如果解构的属性不是在解构对象中,其值为undefined。为了防止undefined出现,可以设定默认值:

    const obj = {a: 5, b: 6};
    let {b,f = "hello"} = obj;
    console.log(f); // hello
    

    解构可以是嵌套对象:

    let person = {
    name: 'Matt',
    age: 27,
    job: {
    title: 'Software engineer'
    }
    };
    // 声明title 变量并将person.job.title 的值赋给它
    let { job: { title } } = person;
    console.log(title); // Software engineer
    

    或是在参数中解构:

    let person = {
    name: 'Matt',
    age: 27
    };
    
    function printPerson(foo, {name, age}, bar) {
    console.log(arguments);
    console.log(name, age);
    }
    function printPerson2(foo, {name: personName, age: personAge}, bar) {
    console.log(arguments);
    console.log(personName, personAge);
    }
    
    printPerson('1st', person, '2nd');
    // ['1st', { name: 'Matt', age: 27 }, '2nd']
    // 'Matt', 27
    
    printPerson2('1st', person, '2nd');
    // ['1st', { name: 'Matt', age: 27 }, '2nd']
    // 'Matt', 27
    

    判断属性存在

    如果想判断属性是否存在,可以用in:

    "a" in {a:1,b:5} // true
    "c" in {a:1,b:5}; // false
    

    不过in的判定包含prototype的属性,如果只是想判断类的属性,应该使用Object.hasOwnProperty:

    const Person = function (name, age) {
      this.name = name;
      this.age = age;
    };
    
    Person.prototype.a = "Hello World";
    
    const person1 = new Person("Tom", 56);
    
    console.log("a" in person1); // true
    console.log(person1.hasOwnProperty("a")); // false
    

    因此如果只想判断prototype的属性,可以封装以下函数判断:

    function hasProtoProperty (obj, property) {
      return (property in obj) && (!obj.hasOwnProperty(property));
    }
    

    送代

    送代是对象里的一个大话题。对象是可以用for循环的,把可以枚举的属性遍历(Enumerable为true):

    const Person = function (name, age) {
      this.name = name;
      this.age = age;
    };
    
    Person.prototype.a = "Hello World";
    
    const person1 = new Person("Tom", 56);
    
    for (const item in person1) {
      console.log(item,person1[item]);
    }
    // name Tom
    age 56
    a Hello World
    

    注意for遍历是会把prototype的属性也遍历,如果不希望遍历prototype的属性,可以利用hasOwnProperty:

    ...
    for (const item in person1) {
      if (person1.hasOwnProperty(item)){
        console.log(item,person1[item]);
      }
    }
    // name Tom
    age 56
    

    另外ES6后Object新增两个方法可以把对象的可枚举属性,而且不是prototype的属性变成数组,从而可以用数组方式送代:values和entries,前者把对象的可枚举属性值转变为数组,后者把属性名与属性值用数组返回,以键值方式表示:

    ...
    console.log(person1.values()) // ["Tom", 56]
    console.log(person1.entries()) // [["name", "Tom"], ["age", 56]]
    

    创建对象与原型

    儘管JS是面向对象语言,但不同于一般的面向对象语言,它是没有类 (class),ES6加入的class也只是语法糖,JS中通常类是由函数创建,函数才是一等公民。

    const Person = function (name, age) {
      this.name = name;
      this.age = age;
      this.sayHello = function () {
        console.log(`say hi to ${this.name}`);
      }
    }
    
    const person1 = new Person("Tom", 11);
    

    创建对象通常要先定义一个函数,然后用new来调用它,表示它是构造对象函数。当new的时候,JS做了以下的事:

    (1) 在内存中创建一个新对象。

    (2) 这个新对象内部的[[Prototype]]特性被 值为构造函数的 prototype 属性。

    (3) 构造函数内部的 this 被 值为这个新对象(即 this 指向新对象)。

    (4) 执行构造函数内部的代码(给新对象 加属性)。

    (5) 如果构造函数 回非 对象,则 回该对象; 则, 回刚创建的新对象。

    如果想更深入了解new,可以查阅手撕JS原生代码。

    回来再看一下上述的例子,当再创建一个实例时,sayHello方法又创建一次,事实上我们只希望创建一次,所有实例分享它,原型 (prototype)就是解决这问题的。

    function Person (name, age) {
        this.name = name;
        this.age = age;
      }
      
      Person.prototype.sayHello = function () {
        console.log(`say hi to ${this.name}`);
      }
      
      const person1 = new Person("Tom", 11);
      const person2 = new Person("Mary", 11);
      
      console.log(person1.sayHello === person2.sayHello);  // true
    

    当实例调用sayHello时,JS会先查找实例有没有这方法,如果没有就去原型里找。

    JavaScript高级程序设计(第4版) 概括一下:

    Javascript基础深入: 对象 (Object)

    继承

    原型式继承

    利用原型,可以实现类的继承,例如上面的Person实例可以使用Object原型方法,原因是当调用Object方法,JS首先找实例有没有该方法,没有利用 __proto__去找原型方法,如果都没有,则调用Person.proto,它是指向Object.prototype,在里面找相应方法:

    function Person (name, age) {
        this.name = name;
        this.age = age;
    }
    
    const person1 = new Person("Tom", 11);
    console.log(person1.hasOwnProperty('name')); // true
    

    JS就是通过原型链实现继承。

    不过原型链继承有些问题,先看下面的例子:

    function SuperType() {
      this.colors = ["red", "blue", "green"];
    }
    
    function SubType() {}
    
    // 继承SuperType
    SubType.prototype = new SuperType();
    
    let instance1 = new SubType();
    instance1.colors.push("black");
    console.log(instance1.colors); // "red,blue,green,black"
    let instance2 = new SubType();
    console.log(instance2.colors); // "red,blue,green,black"
    

    原型中包含的引用值会在所有实例间共享,这也是为什么属性通常会 在构造函数中定义而不会定义在原型上的原因。在使用原型实现继承时,原型实际上变成了另一个类型的实例。这意味着原先的实例属性 身一变成为了原型属性。

    原型的第二个问题是,子类型在实例化时不能给类型的构造函数传参。事实上,我们无法在不影响所有对象实例的情况下把参数传进类的构造函数。再加上之前提到的原型中包含引用值的问题,就导致原型基本不会被单独使用。

    盗用构造函数 (constructor stealing)

    为了解决原型共享问题,盗用构造函数兴起,它的实现很简单:把在子类构造函数中调用类构造函数。因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()和call()方法以新创建的对象为上下文执行构造函数。来看下面的例子:

    function SuperType() {
        this.colors = ["red", "blue", "green"];
    }
    function SubType() {
      SuperType.call(this);
    }
    
    let instance1 = new SubType();
    instance1.colors.push("black");
    console.log(instance1.colors); // "red,blue,green,black"
    let instance2 = new SubType();
    console.log(instance2.colors); // "red,blue,green"
    

    现在解决了父类属性共享的问题,也可以往父类的构造函数传参,但不能使用父类的原型方法。因此盗用构造函数不能单独使用。因此,自然想到把原型链和盗用构造函数结合使用。

    组合继承

    组合继承把上述两者方法结合:

    function SuperType() {
        this.colors = ["red", "blue", "green"];
    }
    
    SuperType.prototype.showColor = function () {
        this.colors.forEach (item => console.log(`color: ${item}`))
    }
    
    function SubType() {
      SuperType.call(this);
    }
    
    SubType.prototype = new SuperType();
    
    let instance1 = new SubType();
    instance1.colors.push("black");
    instance1.showColor();
    /*
    color: red
    color: blue
    color: green
    color: black
    */
    
    let instance2 = new SubType();
    instance2.showColor();
    /*
    color: red
    color: blue
    color: green
    */
    

    问题是解决了,但存在效率问题,其实我们只是第二次创建父类,只是希望使用它的原型方法,没有必要创建两次。 有没有更好的方法?

    寄生式组合继承

    其实所谓继承希望做的是子类有父类的属性,而且可以调用父类的原型方法。子类继承父类属性可以直接在子类的构造函数调用父类的构造函数。调用父类的原型方法可以把子类的prototype指向父类的prototype,但如果直接指向,则会把constructor指向父类构造函数,而不是子类的,所以还要把constructor指向子类。这问题可以用Object.create解决,它接收对象原型,返回一个新的类,原型指向接收的原型。

    因此可以这样实现:

    // Shape - 父类(superclass)
    function Shape() {
      this.x = 0;
      this.y = 0;
    }
    
    // 父类的方法
    Shape.prototype.move = function(x, y) {
      this.x += x;
      this.y += y;
      console.info('Shape moved.');
    };
    
    // Rectangle - 子类(subclass)
    function Rectangle() {
      Shape.call(this); // call super constructor.
    }
    
    // 子类续承父类
    Rectangle.prototype = Object.create(Shape.prototype);
    Rectangle.prototype.constructor = Rectangle;
    
    var rect = new Rectangle();
    
    console.log('Is rect an instance of Rectangle?',
      rect instanceof Rectangle); // true
    console.log('Is rect an instance of Shape?',
      rect instanceof Shape); // true
    rect.move(1, 1); // Outputs, 'Shape moved.'
    

    寄生式组合继承可以算是引用类型继承的最佳模式。

    当然事实上做项目很少会用到上述的继承,因为ES6后,已经用class语法糖,一个extends已经在JS内部把继承问题解决,但内部依然是利用上述提到的原理解决。了解ES6前的继承方法,可以更好地了解JS的类运行机制。


    起源地下载网 » Javascript基础深入: 对象 (Object)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元