目录
1. 对象
1.1. 对象描述符
1.1.1. 数据描述符
1.1.2. 存取描述符
1.2. 不变性
1.2.1. 常量属性
1.2.2. 禁止扩展
1.2.3. 密封
1.2.4. 冻结
1.3. 属性访问[[Get]]
1.4. 属性赋值[[Set]]
2. 原型 [[Prototype]]
2.1. Object.prototype
2.3. prototype、[[Prototype]]、__proto__
2.3. .constructor 属性不可靠
2.4. 运算符 new
2.5. .constructor 属性不可靠
2.6. instanceof 的本质是什么?
3. 模拟类式继承的常见方法
3.1. 原型链继承
3.2. 借用构造函数
3.3. 组合继承(原型链继承+借用构造函数)
3.4. 共享原型
3.5. 临时构造函数
4. 几道笔试题
1. 对象
1.1. 属性描述符
在 ES5 之前,JavaScript 语言本身并没有提供可以直接检测属性特性的方法,比如判断属性是否是只读。但是从 ES 开始,所有的属性都具备了属性描述符。
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一,不能同时是两者。
图1-1 数据描述符
图1-2 存取描述符
1.1.1. 数据描述符
图1-3 writable=false示例,非strict模式
图1-4 writable=false示例,strict模式
图1-5 configurable=false,仍可以转换writable为false
图1-6 enumerable=false 示例
图1-7 writeable 出现在原型上,会怎么样
图1-8 configurable=false 示例
1.1.2. 存取描述符
总结:
1.2. 不变性
有时候你会希望属性或者对象是不可改变的,在 ES5 中可以通过很多种方法来实现。
1.2.1. 常量属性(不能改动属性)
结合 configuration:false 和 writable:false就可以创建一个真正的常量属性(不可修改、重定义或者删除)。
1.2.2. 禁止扩展(不能新增属性)
如果一个对象可以添加新的属性,则这个对象是可扩展的。Object.preventExtensions()将对象标记为不再可扩展,因此它将永远不会具有超出它被标记为不可扩展的属性。注意,一般来说,不可扩展对象的属性可能仍然可被删除。
1.2.3. 密封
Object.seal(...) 会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions(...) 并把所有现有属性标记为 configurable: false。所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以修改属性的值)。
var obj = {
prop: function() {},
foo: 'bar'
};
// New properties may be added, existing properties
// may be changed or removed.
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;
var o = Object.seal(obj);
o === obj; // true
Object.isSealed(obj); // === true
// Changing property values on a sealed object
// still works.
obj.foo = 'quux';
// But you can't convert data properties to accessors,
// or vice versa.
Object.defineProperty(obj, 'foo', {
get: function() { return 'g'; }
}); // throws a TypeError
// Now any changes, other than to property values,
// will fail.
obj.quaxxor = 'the friendly duck';
// silently doesn't add the property
delete obj.foo;
// silently doesn't delete the property
// ...and in strict mode such attempts
// will throw TypeErrors.
function fail() {
'use strict';
delete obj.foo; // throws a TypeError
obj.sparky = 'arf'; // throws a TypeError
}
fail();
// Attempted additions through
// Object.defineProperty will also throw.
Object.defineProperty(obj, 'ohai', {
value: 17
}); // throws a TypeError
Object.defineProperty(obj, 'foo', {
value: 'eit'
}); // changes existing property value
1.2.4. 冻结
Object.freeze(....) 会创建一个冻结对象,这个方法会在一个现有对象上调用 Object.seal(...) 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们值。
var obj = {
prop: function() {},
foo: 'bar'
};
// 新的属性会被添加, 已存在的属性可能
// 会被修改或移除
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;
// 作为参数传递的对象与返回的对象都被冻结
// 所以不必保存返回的对象(因为两个对象全等)
var o = Object.freeze(obj);
o === obj; // true
Object.isFrozen(obj); // === true
// 现在任何改变都会失效
obj.foo = 'quux'; // 静默地不做任何事
// 静默地不添加此属性
obj.quaxxor = 'the friendly duck';
// 在严格模式,如此行为将抛出 TypeErrors
function fail(){
'use strict';
obj.foo = 'sparky'; // throws a TypeError
delete obj.quaxxor; // 返回true,因为quaxxor属性从来未被添加
obj.sparky = 'arf'; // throws a TypeError
}
fail();
// 试图通过 Object.defineProperty 更改属性
// 下面两个语句都会抛出 TypeError.
Object.defineProperty(obj, 'ohai', { value: 17 });
Object.defineProperty(obj, 'foo', { value: 'eit' });
// 也不能更改原型
// 下面两个语句都会抛出 TypeError.
Object.setPrototypeOf(obj, { x: 20 })
obj.__proto__ = { x: 20 }
1.3. 属性访问[[Get]]
1.4. 属性赋值[[Set]]
2. 原型 [[Prototype]]
JavaScript 中的对象都有一个特殊的 [[Prototype]] 内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值。
2.1. Object.prototype
[[Prototype]]的尽头是哪里?
2.2. 属性设置和屏蔽
别小看这一句代码,很多套路
myObject.foo = "bar";
再祭出这张神图
2.3. prototype、[[Prototype]]、__proto__
2.4. 运算符 new
图4:演示构造函数与 new
实际上,NothingSpecial 和你程序中的其他函数没有任何区别。函数本身并不是构造函数,然而,当你在普通的函数调用前面加上 new 关键字之后,就会把这个函数调用变成一个“构造函数调用”。实际上,new 会劫持所有普通函数并用构造对象的形式来调用它。
——《你不知道的 JavaScript (上卷)》p150
换句话说,在 JavaScript 中对于“构造函数”最准确的解释是,所有带 new 的函数调用。
——《你不知道的 JavaScript (上卷)》p150
2.5. .constructor 属性不可靠
.constructor 从哪来?
.constructor 为啥不可靠?
Car.prototype 的 .constructor 属性只是 Car 函数在声明时的默认属性。如果你创建了一个新对象并替换了函数默认的 .prototype 对象引用,那么新对象并不会自动获得 .constructor 属性。
.constructor 并不是一个不可变属性。它是不可枚举的,但是它的值是可写的。你可以任意对其赋值。所以 .constructor 是一个非常不可靠并且不安全的引用。
.constructor 该如何利用?
虽然 .constructor 属性不可靠也不安全,但是它可以很方便的用于运行时对象的内省。可以重置 .constructor 属性使其指向期望的构造函数而不会影响其功能,这是由于该属性主要是用于提供对象的信息。
——《JavaScript 模式》
2.6. instanceof 的本质是什么?
示例1:
示例2:
3. 模拟类式继承的常见方法
3.1. 原型链继承
3.2. 借用构造函数
3.3. 组合继承(原型链继承+借用构造函数)
3.4. 共享原型
3.5. 临时构造函数
4. 几道笔试题
题目01:
题目02:
参考:
《你不知道的 JavaScript (上卷)》 Object.defineProperty(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty Object prototypes: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes new 运算符: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new ECMA-262-10th: http://www.ecma-international.org/ecma-262/10.0/index.html#sec-get-o-p http://www.ecma-international.org/ecma-262/10.0/index.html#sec-getv http://www.ecma-international.org/ecma-262/10.0/index.html#sec-set-o-p-v-throw http://www.ecma-international.org/ecma-262/10.0/index.html#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver