前端开发之原型模式
介绍
原型模式本质就是借用一个已有的实例做原型,在这原型基础上快速复制出一个和原型一样的一个对象。
class CloneDemo {
name = 'clone demo'
clone(): CloneDemo {
return new CloneDemo()
}
}
原型原型链
- 函数(class)都有显示原型 prototype
- 对象都有隐式原型__proto__
- 对象__proto__指向其构造函数的 prototype
原型链:当试图访问一个对象的属性或方法时,如果对象本身没有定义这个属性或方法,JavaScript引擎就会沿着这个对象的原型链向上查找,直到找到或者到达原型链的末端。
class 是 function 的语法糖
class Foo {}
const f1 = new Foo()
console.log(f1.__proto__ === Foo.prototype) // true
场景
const obj = {
foo() {
console.log("foo");
},
};
const obj2 = Object.create(obj);
obj2.foo(); // foo
console.log(obj2.__proto__ === obj); // true
Object.create(null)、Object.create({})和 {} 的区别?
const obj1 = Object.create(null)
console.log("obj1", obj1)
const obj2 = {}
console.log("obj2", obj2)
const obj3 = Object.create(Object.prototype)
console.log("obj3", obj3)
const obj4 = Object.create({})
console.log("obj4", obj4)
如上图结果:
- Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法。也就是没有任何属性,显示 No properties 。
- Object.create(Object.prototype) 和 {} 创建的一模一样。
- Object.create({}) 相比于 {} 多了一层 proto 嵌套。
对象属性描述符
const obj = { x: 100 }; // 各项属性默认都是 true, 但是通过 defineProperty 定义的各项属性都是false
const gopd = Object.getOwnPropertyDescriptor(obj, "x");
console.log(gopd); // {value: 100, writable: true, enumerable: true, configurable: true}
Object.defineProperty(obj, "y", {
value: 200,
writable: false,
});
console.log(obj); // {x: 100, y: 200}
obj.y = 201;
console.log(obj.y); // 200
(1)value
它是用来定义属性值,如果没有value,则看不到对象属性值,但可以通过get set 来操作属性值。
const obj = { x: 100 };
let y = 200;
Object.defineProperty(obj, "y", {
get() {
return y;
},
set(newValue) {
y = newValue;
},
});
console.log(obj); // {x: 100} 这里没有 y
console.log(obj.y); // 200 但是这里又能打印出 y
obj.y = 201;
console.log(obj.y); // 201
console.log(obj); // {x: 100} 还是没有 y
const gopd = Object.getOwnPropertyDescriptor(obj, "y");
console.log(gopd); // {enumerable: false, configurable: false, get: ƒ, set: ƒ} 确实没有 value,所以没有显示 y
(2)configurable
是否可以 delete 删除,并重新定义
是否可以修改其他属性描述符
是否可以修改get set
const obj = { x: 100 };
Object.defineProperty(obj, "y", {
value: 200,
configurable: false
});
Object.defineProperty(obj, "z", {
value: 300,
configurable: true
});
console.log(obj) // {x: 100, y: 200, z: 300}
console.log(delete obj.y) // false
// Object.defineProperty(obj, "y", {
// value: 201,
// configurable: false
// });
// Uncaught TypeError: Cannot redefine property: y
Object.defineProperty(obj, "z", {
value: 301,
configurable: true
});
console.log(obj) // {x: 100, y: 200, z: 301}
(3)writable
属性值是否可被修改。对比Object.freeze()"冻结"、Object.seal()"密封"
const obj = { x: 100 };
Object.defineProperty(obj, "y", {
value: 200,
writable: false,
});
obj.x = 101;
console.log(obj); // {x: 101, y: 200}
obj.y = 201;
console.log(obj); // {x: 101, y: 200} 无法修改 y
// Object.freeze() "冻结":现有属性不可被修改;2.不可添加新属性
const obj = { x: 100, y: 200 };
Object.freeze(obj);
console.log(Object.getOwnPropertyDescriptor(obj, 'x')) // {value: 100, writable: false, enumerable: true, configurable: false}
console.log(Object.isFrozen(obj)); // true
obj.x = 101; // Uncaught TypeError: Cannot assign to read only property 'x' of object '#<Object>'
obj.z = 300; // Uncaught TypeError: Cannot add property z, object is not extensible
// Object.seal()"密封":1.现有属性可以被修改;2.不可添加新属性
const obj = { x: 100, y: 200 };
Object.seal(obj);
obj.x = 101;
console.log(obj.x); // 101 修改成功
console.log(Object.getOwnPropertyDescriptor(obj, 'x')) // {value: 101, writable: true, enumerable: true, configurable: false}
console.log(Object.isSealed(obj)); // true
obj.z = 300; // Uncaught TypeError: Cannot add property z, object is not extensible
(4)enumerable
是否可以通过for in 遍历。
const obj = { x: 100 };
Object.defineProperty(obj, "y", {
value: 200,
enumerable: false,
});
Object.defineProperty(obj, "z", {
value: 300,
enumerable: true,
});
for (const key in obj) {
console.log(key);
}
// x z
console.log("x" in obj); // true
console.log("y" in obj); // true
console.log("z" in obj); // true
原型属性的 enumerable:在以前,for in 可以遍历出原型属性。现在全根据 enumerable 来遍历。为什么没有原型属性了呢?
console.log(Object.getOwnPropertyDescriptor(obj.__proto__, 'toString'))
// {writable: true, enumerable: false, configurable: true, value: ƒ}
可见 enumerable 是 false,所以再用for in 遍历就遍历不出来了。之前是怎么做的呢?之前有一个方法是 hasOwnProperty 来判断是否原型属性。
console.log(obj.hasOwnProperty("x")); // true
console.log(obj.hasOwnProperty("toString")); // false
如何遍历Symbol 属性?
const a = Symbol("a");
const obj = { [a]: 10, b: 20 };
console.log(Object.getOwnPropertyDescriptor(obj, a)); // {value: 10, writable: true, enumerable: true, configurable: true}
for (const key in obj) {
console.log(key);
}
// b 说明 for in 的限制不光有 enumerable,还有 Symbol 类型。
// 那么如何把Symbol遍历出来呢?
console.log(Object.keys(obj)); // ['b']
console.log(Object.getOwnPropertyNames(obj)); // ['b']
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(a)]
console.log(Reflect.ownKeys(obj)); // ['b', Symbol(a)]