• 深入理解javascript对象


    理解对象

    对象被定义为一组属性的无序集合,对象就是一组没有特定顺序的值。对象的每个value值都由一个key来标识,一个key映射一个value值。

    1、Object

    创建对象:

    /*
    *创建了一个名为 person 的对象,而且有三个属性(name、age 和 job)和一个方法
    (sayName())
    *sayName()方法会显示 this.name 的值
    */
    let person = new Object(); 
    person.name = "Nicholas"; 
    person.age = 29; 
    person.job = "Software Engineer"; 
    person.sayName = function() { 
     console.log(this.name); 
    };
    
    // 或者
    let person = { 
     name: "Nicholas", 
     age: 29, 
     job: "Software Engineer", 
     sayName() { 
     console.log(this.name); 
     } 
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    2、属性的类型

    属性分两种:数据属性访问器属性

    (1)、数据属性

    数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置。数据属性有 4
    个特性:

    Configurable: 表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特
    性,以及是否可以把它改为访问器属性。默认情况下值为 true。

    Enumerable: 表示属性是否可以通过 for-in 循环返回。默认值为 true。

    Writable: 表示属性的值是否可以被修改。默认值为 true。

    Value: 包含属性实际的值。读取和写入属性值的位置。默认值为undefined。

    要修改属性的默认特性,就必须使用 Object.defineProperty()方法。这个方法接收 3 个参数:要给其添加属性的对象、属性的名称和一个描述符对象。

    let person = {}; 
    Object.defineProperty(person, "name", { 
     writable: false, 
     value: "Nicholas" 
    }); 
    console.log(person.name); // "Nicholas" 
    person.name = "Greg"; 
    console.log(person.name); // "Nicholas"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    (2)、访问器属性

    访问器属性不包含数据值。相反,它们包含一个获取(getter)函数和一个设置(setter)函数

    Configurable: 表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特
    性,以及是否可以把它改为数据属性。默认值为 true。

    Enumerable: 表示属性是否可以通过 for-in 循环返回。 默认值为 true。

    Get: 获取函数,在读取属性时调用。默认值为 undefined。

    Set: 设置函数,在写入属性时调用。默认值为 undefined。

    访问器属性是不能直接定义的,必须使用 Object.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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    Object.define.Properties()

    ECMAScript 提供了 Object.define.Properties()方法。这个方法可以通过多个描述符一次性定义多个属性。

    let book = {};
          Object.defineProperties(book, {
            year_: {
              value: 2017
            },
            edition: {
              value: 1
            },
            year: {
              get() {
                return this.year_;
              },
              set(newValue) {
                if (newValue > 2017) {
                  this.year_ = newValue;
                  this.edition += newValue - 2017;
                }
              }
            }
          })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    Object.getOwnPropertyDescriptor()

    使用 Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。

    let book = {}; 
    Object.defineProperties(book, { 
       year_: { 
         value: 2017 
       }, 
       edition: { 
         value: 1 
       }, 
       year: { 
       get: function() { 
         return this.year_; 
       }, 
       set: function(newValue){ 
         if (newValue > 2017) { 
           this.year_ = newValue; 
           this.edition += newValue - 2017; 
         } 
       } 
     } 
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    合并对象Object.assign()

    Object.assign()实际上对每个源对象执行的是浅复制。如果多个源对象都有相同的属性,则使
    用最后一个复制的值。

    let dest, src, result; 
    /** 
     * 简单复制
     */ 
    dest = {}; 
    src = { id: 'src' }; 
    result = Object.assign(dest, src); 
    // Object.assign 修改目标对象
    // 也会返回修改后的目标对象
    console.log(dest === result); // true 
    console.log(dest !== src); // true 
    console.log(result); // { id: src } 
    console.log(dest); // { id: src }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    属性值简写

    ECMAScript 6 为定义和操作对象新增了很多极其有用的语法糖特性。

    // 原始写法
    let name = 'Matt'; 
    let person = { 
     name: name 
    }; 
    console.log(person); // { name: 'Matt' }
    
    // 等价于
    let name = 'Matt'; 
    let person = { 
     name 
    }; 
    console.log(person); // { name: 'Matt' }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    可计算属性

    可计算属性,在对象字面量中完成动态属性赋值。

    const nameKey = 'name'; 
    const ageKey = 'age'; 
    const jobKey = 'job'; 
    let person = { 
     [nameKey]: 'Matt', 
     [ageKey]: 27, 
     [jobKey]: 'Software engineer' 
    }; 
    console.log(person); // { name: 'Matt', age: 27, job: 'Software engineer' }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    对象解构

    ECMAScript 6 新增了对象解构语法,可以在一条语句中使用嵌套数据实现一个或多个赋值操作。简单地说,对象解构就是使用与对象匹配的结构来实现对象属性赋值。

    let person = { 
     name: 'Matt', 
     age: 27 
    }; 
    let { name, age } = person; 
    console.log(name); // Matt 
    console.log(age); // 27
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    创建对象
    工厂模式

    函数 createPerson()接收 3 个参数,根据这几个参数构建了一个包含 Person 信息的对象。
    可以用不同的参数多次调用这个函数,每次都会返回包含 3 个属性和 1 个方法的对象。这种工厂模式虽
    然可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型)。

     let o = new Object(); 
     o.name = name; 
     o.age = age; 
     o.job = job; 
     o.sayName = function() { 
     console.log(this.name); 
     }; 
     return o; 
    } 
    let person1 = createPerson("Nicholas", 29, "Software Engineer"); 
    let person2 = createPerson("Greg", 27, "Doctor");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    构造函数模式

    以函数的形式为自己的对象类型定义属性和方法。

    function Person(name, age, job){ 
     this.name = name; 
     this.age = age; 
     this.job = job; 
     this.sayName = function() { 
       console.log(this.name); 
     }; 
    } 
    let person1 = new Person("Nicholas", 29, "Software Engineer"); 
    let person2 = new Person("Greg", 27, "Doctor"); 
    person1.sayName(); // Nicholas 
    person2.sayName(); // Greg
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    原型模式

    所有属性和 sayName()方法都直接添加到了 Person 的 prototype 属性上,与构造函数模
    式不同,使用这种原型模式定义的属性和方法是由所有实例共享的。

    function Person() {} 
    Person.prototype.name = "Nicholas"; 
    Person.prototype.age = 29; 
    Person.prototype.job = "Software Engineer"; 
    Person.prototype.sayName = function() { 
     console.log(this.name); 
    }; 
    let person1 = new Person(); 
    person1.sayName(); // "Nicholas" 
    let person2 = new Person(); 
    person2.sayName(); // "Nicholas" 
    console.log(person1.sayName == person2.sayName); // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    原型

    无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向
    原型对象)。默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。对前面的例子而言:

    Person.prototype.constructor 指向 Person。

    Person.prototype.proto 通过这个属性可以访问对象的原型。

     * 构造函数可以是函数表达式
     * 也可以是函数声明,因此以下两种形式都可以:
     * function Person() {} 
     * let Person = function() {} 
     */ 
    function Person() {} 
    /** 
     * 声明之后,构造函数就有了一个
     * 与之关联的原型对象:
     */ 
    console.log(typeof Person.prototype); 
    console.log(Person.prototype); 
    // { 
    // constructor: f Person(), 
    // __proto__: Object
    // }
    
     * 如前所述,构造函数有一个 prototype 属性
     * 引用其原型对象,而这个原型对象也有一个
     * constructor 属性,引用这个构造函数
     * 换句话说,两者循环引用:
     */ 
    console.log(Person.prototype.constructor === Person); // true 
    /** 
     * 正常的原型链都会终止于 Object 的原型对象
     * Object 原型的原型是 null 
     */ 
    console.log(Person.prototype.__proto__ === Object.prototype); // true 
    console.log(Person.prototype.__proto__.constructor === Object); // true 
    console.log(Person.prototype.__proto__.__proto__ === null); // true 
    console.log(Person.prototype.__proto__); 
    // { 
    // constructor: f Object(), 
    // toString: ... 
    // hasOwnProperty: ... 
    // isPrototypeOf: ... 
    // ... 
    // }
    
     person2 = new Person(); 
    /** 
     * 构造函数、原型对象和实例
     * 是 3 个完全不同的对象:
     */ 
    console.log(person1 !== Person); // true 
    console.log(person1 !== Person.prototype); // true 
    console.log(Person.prototype !== Person); // true 
    /** 
     * 实例通过__proto__链接到原型对象,
     * 它实际上指向隐藏特性[[Prototype]] 
     * 
     * 构造函数通过 prototype 属性链接到原型对象
     * 
     * 实例与构造函数没有直接联系,与原型对象有直接联系
     */ 
    console.log(person1.__proto__ === Person.prototype); // true 
    conosle.log(person1.__proto__.constructor === Person); // true 
    /** 
     * 同一个构造函数创建的两个实例
     * 共享同一个原型对象:
     */ 
    console.log(person1.__proto__ === person2.__proto__); // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    原型层级

    在通过对象访问属性时,会按照这个属性的名称开始搜索。搜索开始于对象实例本身。如果在这个
    实例上发现了给定的名称,则返回该名称对应的值。如果没有找到这个属性,则搜索会沿着指针进入原型对象,然后在原型对象上找到属性后,再返回对应的值。

    function Person() {} 
    Person.prototype.name = "Nicholas"; 
    Person.prototype.age = 29; 
    Person.prototype.job = "Software Engineer"; 
    Person.prototype.sayName = function() { 
     console.log(this.name); 
    }; 
    let person1 = new Person(); 
    let person2 = new Person(); 
    person1.name = "Greg"; 
    console.log(person1.name); // "Greg",来自实例
    console.log(person2.name); // "Nicholas",来自原型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    总结:构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型
    继承

    实现继承主要是通过原型链实现的。

    每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。

    简单来说:子类想要继承父类的属性和方法,可以将其原型对象指向父类的实例,根据原型链就可以使用到父类的方法和属性

    // 代码定义了两个类型:SuperType 和 SubType。
    // 父构造函数SuperType
    function SuperType() { 
     this.property = true;
    } 
    // 父构造函数SuperType的原型中,定义个getSuperValue属性
    SuperType.prototype.getSuperValue = function() { 
     return this.property; 
    }; 
    // 定义个子构造函数SubType
    function SubType() { 
     this.subproperty = false; 
    } 
    // 这两个类型的主要区别是 SubType 通过创建 SuperType 的实例并将其赋值给自己的原型 子SubType.prototype 实现了对 父SuperType 的继承。
    SubType.prototype = new SuperType(); 
    // 子构造函数SubType继承父构造函数SuperType 的原型
    SubType.prototype.getSubValue = function () {
         return this.subproperty; 
    }; 
    let instance = new SubType(); 
    console.log(instance.getSuperValue()); // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    这个例子中实现继承的关键,是 SubType 没有使用默认原型,而是将其替换成了一个新的对象。这个新的对象恰好是 SuperType 的实例。这样一来,SubType 的实例不仅能从 SuperType 的实例中继承属性和方法,而且还与 SuperType 的原型挂上了钩。

    原型链虽然是实现继承的强大工具,但它也有问题。主要问题出现在原型中包含引用值的时候,**原型中包含的引用值会在所有实例间共享
    **

    盗用构造函数

    为了解决原型包含引用值导致的继承问题,一种叫作“盗用构造函数”
    基本思想:在子类构造函数中调用父类构造函数。因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()和 call()方法以新创建的对象为上下文执行构造函数。

    function SuperType() { 
     this.colors = ["red", "blue", "green"]; 
    } 
    function SubType() { 
     // 继承 SuperType 
     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"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    通过使用 call()(或 apply())方法,SuperType构造函数在为 SubType 的实例创建的新对象的上下文中执行了。这相当于新的 SubType 对象上运行了SuperType()函数中的所有初始化代码。结果就是每个实例都会有自己的 colors 属性。

    // 盗用构造函数传递参数
    function SuperType(name){ 
     this.name = name; 
    } 
    function SubType() { 
     // 继承 SuperType 并传参
     SuperType.call(this, "Nicholas"); 
     // 实例属性
     this.age = 29; 
    } 
    let instance = new SubType(); 
    console.log(instance.name); // "Nicholas"; 
    console.log(instance.age); // 29
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    盗用构造函数的问题:盗用构造函数的主要缺点,也是使用构造函数模式自定义类型的问题:必须在构造函数中定义方法,因此函数不能重用。此外,子类也不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模
    式。由于存在这些问题,盗用构造函数基本上也不能单独使用。

    组合继承

    组合继承综合了原型链和盗用构造函数。
    **基本的思路:**使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。

    function SuperType(name){ 
     this.name = name; 
     this.colors = ["red", "blue", "green"]; 
    } 
    SuperType.prototype.sayName = function() { 
     console.log(this.name); 
    }; 
    function SubType(name, age){ 
     // 继承属性
     SuperType.call(this, name); 
     this.age = age; 
    } 
    // 继承方法
    SubType.prototype = new SuperType(); 
    SubType.prototype.sayAge = function() { 
     console.log(this.age); 
    }; 
    let instance1 = new SubType("Nicholas", 29); 
    instance1.colors.push("black"); 
    console.log(instance1.colors); // "red,blue,green,black" 
    instance1.sayName(); // "Nicholas"; 
    instance1.sayAge(); // 29
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    原型式继承

    即使不自定义类型也可以通过原型实现对象之间的信息共享。

    function object(o) { 
     function F() {} 
     F.prototype = o; 
     return new F(); 
    } 
    let person = { 
     name: "Nicholas", 
     friends: ["Shelby", "Court", "Van"] 
    }; 
    let anotherPerson = object(person); 
    anotherPerson.name = "Greg"; 
    anotherPerson.friends.push("Rob");
    let yetAnotherPerson = object(person); 
    yetAnotherPerson.name = "Linda"; 
    yetAnotherPerson.friends.push("Barbie"); 
    console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    子类想要继承父类的属性和方法,可以将其原型对象指向父类的实例,根据原型链就可以使用到父类的方法和属性

    参考自《javascript高级程序设计》
    欢迎关注公众号:javascript艺术

  • 相关阅读:
    微信小程序游戏开发│智力测试游戏——button版
    Python 潮流周刊第 46 期(摘要)+ 赠书 7 本
    【luogu P4887】【模板】莫队二次离线(第十四分块(前体))(莫队)
    MyBatis的TypeAliasRegistry
    leetcode算法每天一题029:两数相除
    【基于HTML5的网页设计及应用】——实现个人简历表格和伪类选择器应用
    (MATLAB)第三章-MATLAB基础知识
    gitea的简单介绍
    IDEA创建简单web(servlet)项目(server为tomcat)
    【Python工程师之高性能爬虫】
  • 原文地址:https://blog.csdn.net/zhangjing1019/article/details/126163361