理解对象
对象的数据属性
- [[Configurable]] — 是否可以被删除 和 设置 true
- [[Enumerable]] — 是否可以枚举 true
- [[Writable]] — 是否可写 true
- [[Value]] — 当前属性的值 undefined
以上数据属性的值,均可以通过 Object.defineProperty() 方法进行修改
let person = {};
// 第一个参数为当前对象,第二个参数为属性名字,第三个参数是一个对象,进行属性的赋值操作
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
console.log(person.name); // "Nicholas"
person.name = "Greg";
console.log(person.name); // "Nicholas"
需要注意: 在调用 Object.defineProperty()时,configurable、enumerable 和 writable 的值如果不 指定,则都默认为 false。
对象的访问器属性
- [[Configurable]] — 是否可被删除 和 设置
- [[Enumerable]] — 是否可枚举
- [[Get]] — 获取函数
- [[Set]] — 操作函数
同样只能使用 Object.defineProperty() 进行访问和设置
// 定义一个对象,包含伪私有成员 year_和公共成员 edition
let book = {
year_: 2017,
edition: 1,
};
Object.defineProperty(book, "year", {
get() { // 获取year属性的值
return this.year_;
},
set(newValue) { // 得到year属性的值
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
},
});
book.year = 2018;
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) {
} }, }, }); 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 // } // }this.year_ = newValue;
this.edition += newValue - 2017;
<a name="TFObg"></a>
#### 合并对象
1. Object.assign(源对象,目标对象,目标对象) -- 相同属性值会覆盖 实际上对每个源对象执行的是浅复制
```javascript
// 可以通过目标对象上的设置函数观察到覆盖的过程:
dest = {
set id(x) {
console.log(x);
}
};
Object.assign(dest, { id: 'first' }, { id: 'second' }, { id: 'third' });
// first
// second
// third
- 为了解决之前 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
```javascript
// 要检查超过两个值,递归地利用相等性传递即可:
function recursivelyCheckEqual(x, ...rest) {
return Object.is(x, rest[0]) &&
(rest.length < 2 || recursivelyCheckEqual(...rest));
}
增强语法
- 属性值简写 ```javascript let person = { name: name };
let person = { name };
- 可计算属性 -- 也可以写函数要注意函数的副作用,赋值不能回滚
```javascript
let nameKey = 'name';
let person = {
[nameKey]: 'Matt';
};
- 方法简写
``javascript let person = { sayName(name) { console.log(
My name is ${name}`); } };
let person = {
methodKey {
console.log(My name is ${name}
);
}
}
- 解构赋值
```javascript
let person = {
name: 'Matt',
age: 27
};
let { name, job='Software engineer' } = person;
console.log(name); // Matt
console.log(job); // Software engineer
创建对象
工厂模式
一个工厂,每一个人都一样
function createPerson(name, age, job) {
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");
构造函数模式
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
与工厂模式的区别
- 没有显式地创建对象。
- 属性和方法直接赋值给了 this
- 没有 return。
- 行业默认规则,构造函数需要大写开头
- 创建一个构造函数需要执行一下步骤
- 在内存中创建一个新对象。
- 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
- 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
- 执行构造函数内部的代码(给新对象添加属性)。
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
构造函数的调用
// 作为构造函数
let person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); // "Nicholas"
// 作为函数调用 -- this指向全局,会污染全局作用域
Person("Greg", 27, "Doctor"); // 添加到 window 对象
window.sayName(); // "Greg"
// 在另一个对象的作用域中调用,指向另一个对象。
let o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); // "Kristen"
存在的问题
- 会创造两个相同逻辑的函数实例
原型模式
- 先放一下若川大佬的图,看这张图可以很好的理解原型链
- 原文地址:https://juejin.cn/post/6844903780035592205
- 确认原型和实例关系的方法
- isPrototypeOf() — Person.prototype.isPrototypeOf(person1) 看person1的原型是不是Person
- Object.getPrototypeOf() —- Object.getPrototypeOf(person1) == Person.prototype 这个方法可以返回person1原型的pertotype
- setPrototypeOf() — 重写对象原型链关系,不推荐使用,因为会影响源对象
- Object.create() — 创建一个新的对象,同时为其指定原型
- 原型层级 —- 先查找实例属性,再查找原型属性
- hasOwnProperty() — 判断是否是实例属性
- in 操作符 — 实例和原型属性都会返回true
// 判断是原型属性还是实例属性
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
}
- 获得可枚举属性
- Object.keys() —- 获取所有可以枚举的属性的key
- Object.getOwnPropertyNames() — 不管可不可以枚举,都会获得,包括constuctor
- Object.getOwnPropertySymbols() — 获取符号属性的对象key
- 枚举的顺序
- for-in 循环和 Object.keys() 的枚举顺序是不确定的取决于 JavaScript 引擎,可能因浏览器而异。
- 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))); // []
<a name="OIaie"></a>
#### 原型语法及其问题
1. 用对象定义prototype,产生的问题是constructor被新对象覆盖,需要用definprototype重写
```javascript
function Person() {}
Person.prototype = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() {
console.log(this.name);
}
};
// 恢复 constructor 属性
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
- 原型创建是动态的,可能会先创建了实例,在重写原型,导致实例上没有新写的原型
- 在重写原型方法的时候,可能会覆盖原来的原生方法, 推荐的做法是创建一个自定义的类,继承原生类型。
- 在原型对象上创建的对象,是一个引用值,在对象修改的时候,多个实例都会修改因为指向同一个地址。所以不能仅仅通过原型来解决问题。需要通过其他继承来解决。