系列文章:
对象原型
内容
对象的内容是由一些存储在特定命名位置(任意类型的)值组成的,我们称之为属性。
⚠️注意:即使在对象的文字形式中声明一个函数表达式,这个函数都不会“属于”一个对象,他们只是对于相同函数的多个引用。
属性描述符
从 ES5 开始,所有的属性都具备属性描述符:
-
writable:是否可以修改属性的值。值为false时,相当于定义了一个空操作setter。
-
configurable:如果属性是可配置的,就可以使用 defineProperty(...) 进行修改属性描述符。configurable的值设置为false是单向操作,无法撤销!
⚠️注意:即使 configurable:false,还是可以把 writable 的值从 true 改成 false,但不可以由 false 改成 true。
-
enumerable:控制的是属性是否会出现在对象的属性枚举中。
⚠️注意:属性不一定包含值——也可能是包含getter/setter的“访问描述符”
[[Get]] 和 [[Put]]
[[Get]]
属性访问时,对象默认的内置 [[Get]] 操作首先在对象中查找是否有名称相同的属性,遍历可能存在的[[prototype]]链,如果无论如何也没找到名称相同的属性,则返回值 undefined。
⚠️注意:访问变量时,没找到会抛出一个 ReferenceError 异常。刚问对象属性时,没找到会返回 undefined。
[[Put]]
当给属性赋值:
- 先检查对象中是否存在这个属性,如果有这个属性, [[Put]]算法大致会检查一下内容:
- 属性是否有setter,如果有的话,调用 setter。
- 属性的描述符中 writable 的值如果是false,以下两种情况:
- 严格模式下,抛出 TypeError 异常。
- 非严格模式下,静默失败。
- 如果都不是,则将该值设置为属性的值。
- 如果对象没有这个属性,[[prototype]]链会被遍历,如果 [[prototype]] 上找到了这个属性。
- 若属性的 writable: true,就会在对象里添加一个新属性。
- 若属性的 writable: false,
- 严格模式下,会抛出错误
- 非严格模式下,会静默失败
- 若属性有setter,那么就会调用这个setter,属性不会被添加到对象上。
- 如果 [[prototype]] 上找不到这个属性,这个属性会直接添加到对象上。
原型
JavaScript 中的对象有一个特殊的 [[prototype]] 内置属性,是对其他对象的引用。所有普通的 [[prototype]] 最终都会指向内置的 Object.prototype。
所有的函数默认都会拥有一个名为 prototype 的公有且不可枚举的属性,它会指向另一个对象,这个对象被称为原型,这个对象默认有一个公有且不可枚举的属性.constructor,这个属性引用的是对象关联的函数。
[[prototype]]机制就是指对象中的一个内部链接引用另一个对象。
原型继承
JavaScript 会在两个对象之间创建一个关联,这样一个对象就可以通过委托来访问另一个对象的属性和函数。
var bar = Object.create(foo);
会创建一个对象 bar 并把它关联到 foo。
调用 Object.create(...)会凭空创建一个“新”对象,并把新对象内部的[[prototype]] 关联到指定的对象...,这样会直接把原始的关联对象抛弃掉。
有以下几种方法检查两个对象之间的关系:
- instanceof:用于测试函数的 prototype 属性是否出现在对象的原型链中任何位置。这个方法只能处理对象和函数之间的关系。
- prototypeObj.isPrototypeOf(object): 检查 prototypeObj 是否在 object 的原型链中出现。
- getPrototypeOf: 返回指定对象的原型
⚠️注意:绝大多数(不是所有)的浏览器支持__proto__ 这个非标准的方法访问内部的[[prototype]],__proto__存在于内置 Object.prototype 中,看起来像个属性,其实更像一个 getter/setter。
好题练习
练习题1:
function Test() {
Test.prototype.add = num => {
this.number = num
}
Test.prototype.number = 0
}
t1 = new Test()
t2 = new Test()
t1.add(12)
var a = t1.number
var b = t2.number
console.log(a, b) // => 0,12
每实例化一次Test,Test.prototype.add都会被重新赋值一次,并且赋值的函数为箭头函数,箭头函数无自身的上下文,其中的this指向Test上下文中的this,即每次实例化的实例对象。所以,t2 实例化后 Test.prototype.add 里的 this 指向t2,t1 调用 add 函数时,给 t2 的number 赋值。
练习题2:
var A = function() {};
A.prototype.n = 1;
var b = new A();
A.prototype = {
n: 2,
m: 3
}
var c = new A();
console.log(b.n); // 1
console.log(b.m); // undefined
console.log(c.n); // 2
console.log(c.m); // 3
⚠️注意:JavaScript 中的引用和其他语言中的引用/指针不同,不能指向别的变量或者引用,只能指向值。
b 在实例化过程中,b.__proto__ = A.prototype,此时 A.prototype = {n: 1},当 A.prototype = { n: 2, m: 3 }时,打破原型链,A.prototype 引用了新内存地址的对象,b.__proto__指向的依旧是旧内存地址的对象。如果是 A.prototype.n = 2; A.prototype.m = 3; 就不会指向新的引用。
练习题3:
var F = function() {};
Object.prototype.a = function() {
console.log('a');
};
Function.prototype.b = function() {
console.log('b');
}
var f = new F();
f.a(); // a
f.b(); // TypeError: f.b is not a function
F.a(); // a
F.b(); // b
f的原型链:f => f.__proto__ => F.prototype => F.prototype.__proto__ => Object.prototype => Object.prototype.__proto__ => null
F的原型链:F => F.__proto__ => Function.prototype => Function.prototype.__proto__ => Object.prototype => Object.prototype.__proto__ => null
练习题4:
function Person(name) {
this.name = name
}
let p = new Person('Tom');
question1: p.__proto__等于什么? // p.__proto__ === Person.prototype
question2: Person.__proto__等于什么? // Person.__proto__ === Function.prototype
觉得有收获的,点个赞再走吧~