理解对象

对象的数据属性

  1. [[Configurable]] — 是否可以被删除 和 设置 true
  2. [[Enumerable]] — 是否可以枚举 true
  3. [[Writable]] — 是否可写 true
  4. [[Value]] — 当前属性的值 undefined
  • 以上数据属性的值,均可以通过 Object.defineProperty() 方法进行修改

    1. let person = {};
    2. // 第一个参数为当前对象,第二个参数为属性名字,第三个参数是一个对象,进行属性的赋值操作
    3. Object.defineProperty(person, "name", {
    4. writable: false,
    5. value: "Nicholas"
    6. });
    7. console.log(person.name); // "Nicholas"
    8. person.name = "Greg";
    9. console.log(person.name); // "Nicholas"
  • 需要注意: 在调用 Object.defineProperty()时,configurable、enumerable 和 writable 的值如果不 指定,则都默认为 false。

    对象的访问器属性

  1. [[Configurable]] — 是否可被删除 和 设置
  2. [[Enumerable]] — 是否可枚举
  3. [[Get]] — 获取函数
  4. [[Set]] — 操作函数
  • 同样只能使用 Object.defineProperty() 进行访问和设置

    1. // 定义一个对象,包含伪私有成员 year_和公共成员 edition
    2. let book = {
    3. year_: 2017,
    4. edition: 1,
    5. };
    6. Object.defineProperty(book, "year", {
    7. get() { // 获取year属性的值
    8. return this.year_;
    9. },
    10. set(newValue) { // 得到year属性的值
    11. if (newValue > 2017) {
    12. this.year_ = newValue;
    13. this.edition += newValue - 2017;
    14. }
    15. },
    16. });
    17. book.year = 2018;
    18. console.log(book.edition); // 2
  • 通过 Object.defineProperties() 方法可以同时定义多个属性值,如果不定义其他数据属性, configurable、enumerable 和 writable 特性值都是 false 。

    读取对象的访问器属性和数据属性

  • Object.getOwnPropertyDescriptor() — 返回一个对象,可以读取当前属性的 数据属性和访问器属性是否定义

  • Object.getOwnPropertyDescriptors() — 返回一个包含所有属性的对象。 ```javascript let book = {}; Object.defineProperties(book, { year: { value: 2017, }, edition: { value: 1, }, year: { get: function () { return this.year; }, set: function (newValue) { if (newValue > 2017) {
    1. this.year_ = newValue;
    2. this.edition += newValue - 2017;
    } }, }, }); console.log(Object.getOwnPropertyDescriptors(book)); // { // edition: { // configurable: false, // enumerable: false, // value: 1, // writable: false // }, // year: { // configurable: false, // enumerable: false, // get: f(), // set: f(newValue), // }, // year_: { // configurable: false, // enumerable: false, // value: 2017, // writable: false // } // }
  1. <a name="TFObg"></a>
  2. #### 合并对象
  3. 1. Object.assign(源对象,目标对象,目标对象) -- 相同属性值会覆盖 实际上对每个源对象执行的是浅复制
  4. ```javascript
  5. // 可以通过目标对象上的设置函数观察到覆盖的过程:
  6. dest = {
  7. set id(x) {
  8. console.log(x);
  9. }
  10. };
  11. Object.assign(dest, { id: 'first' }, { id: 'second' }, { id: 'third' });
  12. // first
  13. // second
  14. // third
  1. 用…也可以

    Object.is()

  • 为了解决之前 0 和 -0 相等 NaN 跟自己也不相等的问题 ```javascript // 这些情况在不同 JavaScript 引擎中表现不同,但仍被认为相等 console.log(+0 === -0); // true console.log(+0 === 0); // true console.log(-0 === 0); // true // 要确定 NaN 的相等性,必须使用极为讨厌的 isNaN() console.log(NaN === NaN); // false console.log(isNaN(NaN)); // true

// 使用 Object.is();的正确表现 // 正确的 0、-0、+0 相等/不等判定 console.log(Object.is(+0, -0)); // false console.log(Object.is(+0, 0)); // true console.log(Object.is(-0, 0)); // false // 正确的 NaN 相等判定 console.log(Object.is(NaN, NaN)); // true

  1. ```javascript
  2. // 要检查超过两个值,递归地利用相等性传递即可:
  3. function recursivelyCheckEqual(x, ...rest) {
  4. return Object.is(x, rest[0]) &&
  5. (rest.length < 2 || recursivelyCheckEqual(...rest));
  6. }

增强语法

  • 属性值简写 ```javascript let person = { name: name };

let person = { name };

  1. - 可计算属性 -- 也可以写函数要注意函数的副作用,赋值不能回滚
  2. ```javascript
  3. let nameKey = 'name';
  4. let person = {
  5. [nameKey]: 'Matt';
  6. };
  • 方法简写 ``javascript let person = { sayName(name) { console.log(My name is ${name}`); } };

let person = { methodKey { console.log(My name is ${name}); } }

  1. - 解构赋值
  2. ```javascript
  3. let person = {
  4. name: 'Matt',
  5. age: 27
  6. };
  7. let { name, job='Software engineer' } = person;
  8. console.log(name); // Matt
  9. console.log(job); // Software engineer

创建对象

工厂模式

  • 一个工厂,每一个人都一样

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

    构造函数模式

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

  1. 没有显式地创建对象。
  2. 属性和方法直接赋值给了 this
  3. 没有 return。
  4. 行业默认规则,构造函数需要大写开头
  • 创建一个构造函数需要执行一下步骤
  1. 在内存中创建一个新对象。
  2. 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
  3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
  4. 执行构造函数内部的代码(给新对象添加属性)。
  5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
  • 构造函数的调用

    1. // 作为构造函数
    2. let person = new Person("Nicholas", 29, "Software Engineer");
    3. person.sayName(); // "Nicholas"
    4. // 作为函数调用 -- this指向全局,会污染全局作用域
    5. Person("Greg", 27, "Doctor"); // 添加到 window 对象
    6. window.sayName(); // "Greg"
    7. // 在另一个对象的作用域中调用,指向另一个对象。
    8. let o = new Object();
    9. Person.call(o, "Kristen", 25, "Nurse");
    10. o.sayName(); // "Kristen"
  • 存在的问题

  1. 会创造两个相同逻辑的函数实例

原型模式

image.png

  • 确认原型和实例关系的方法
  1. isPrototypeOf() — Person.prototype.isPrototypeOf(person1) 看person1的原型是不是Person
  2. Object.getPrototypeOf() —- Object.getPrototypeOf(person1) == Person.prototype 这个方法可以返回person1原型的pertotype
  3. setPrototypeOf() — 重写对象原型链关系,不推荐使用,因为会影响源对象
  4. Object.create() — 创建一个新的对象,同时为其指定原型
  • 原型层级 —- 先查找实例属性,再查找原型属性
  1. hasOwnProperty() — 判断是否是实例属性
  2. in 操作符 — 实例和原型属性都会返回true
    1. // 判断是原型属性还是实例属性
    2. function hasPrototypeProperty(object, name){
    3. return !object.hasOwnProperty(name) && (name in object);
    4. }
  • 获得可枚举属性
  1. Object.keys() —- 获取所有可以枚举的属性的key
  2. Object.getOwnPropertyNames() — 不管可不可以枚举,都会获得,包括constuctor
  3. Object.getOwnPropertySymbols() — 获取符号属性的对象key
  • 枚举的顺序
  1. for-in 循环和 Object.keys() 的枚举顺序是不确定的取决于 JavaScript 引擎,可能因浏览器而异。
  2. Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()和 Object.assign() 的枚举顺序是确定性的 先以升序枚举数值键,然后以插入顺序枚举字符串和符号键

对象的迭代

  • Object.values() — 返回所有value
  • Object.entries() — 返回key value 为一个二维数组 ```javascript // 符号属性会被忽略: const sym = Symbol(); const o = {

}; console.log(Object.values(o)); // [] console.log(Object.entries((o))); // []

  1. <a name="OIaie"></a>
  2. #### 原型语法及其问题
  3. 1. 用对象定义prototype,产生的问题是constructor被新对象覆盖,需要用definprototype重写
  4. ```javascript
  5. function Person() {}
  6. Person.prototype = {
  7. name: "Nicholas",
  8. age: 29,
  9. job: "Software Engineer",
  10. sayName() {
  11. console.log(this.name);
  12. }
  13. };
  14. // 恢复 constructor 属性
  15. Object.defineProperty(Person.prototype, "constructor", {
  16. enumerable: false,
  17. value: Person
  18. });
  1. 原型创建是动态的,可能会先创建了实例,在重写原型,导致实例上没有新写的原型
  2. 在重写原型方法的时候,可能会覆盖原来的原生方法, 推荐的做法是创建一个自定义的类,继承原生类型。
  3. 在原型对象上创建的对象,是一个引用值,在对象修改的时候,多个实例都会修改因为指向同一个地址。所以不能仅仅通过原型来解决问题。需要通过其他继承来解决。