原型
原型:是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法(原型也是对象)。
| 属性 | 用法 | 描述 |
|---|---|---|
| prototype | 构造函数名.prototype | 可以理解为函数的原型对象 |
| proto | 对象名.proto | 指向该对象的原型 |
| constructor | 对象名.constructor | 指向关联的构造函数,实例原型指向构造函数 |
原型链
原型链:当对象找不到需要的属性时,它会到这个对象的父对象上去找,以此类推,这就构成了对象的原型链。并且Object.prototype是对象的最终原型,绝大多数对象最终都会继承自Object.prototype,而Object.prototype的原型是null。
理解概念
- JavaScript分为函数对象和普通对象,每个对象都有__proto__属性,但是只有函数对象才有prototype属性;(准则一)
- Object、Function都是JavaScript内置的函数,类似的还有我们常用的Array、RegExp、Date、Boolean、Number、String;(准则一)
- 属性__proto__是一个对象,它有两个属性。contructor和__proto__;(准则二)
- 原型对象prototype有一个默认的constructor属性,用于记录实例是由哪个构造函数创建。(准则二)
-
不管有没有
__proto__属性,JavaScript标准规范定义了prototype原型属性,所有对象都使用由构造函数所指向的原型对象。原型对象,默认情况下,都会有一个constructor属性,重新指回构造函数。 -
对象的原型仅用于属性继承。函数本身并不使用其关联的原型对象,但是因为函数本身也是一个对象,它会继承它的构造函数的原型,
JavaScript中函数的构造函数,是Function对象。 -
每个原型对象自身,默认都是通过
new Object()构造函数创建的,所以原型对象自身(比如Foo.prototype)的原型对象(其__proto__指向)就是Object.prototype。因此,不管任意类型的实例,最后都会从Object.prototype中继承属性。Object.prototype的原型对象(__proto__)是null。常见错误:Cannot read properties of null,是不是似曾相识,因为null没有原型对象,就不能继续通过原型链往上查找属性了,直接异常。
-
所以对象会自动从原型链中读取属性,就好像属性定义在对象自身上。直接在实例自身上设置原型链中同名的属性,会隐藏/遮蔽原型链中的同名的那个属性。
// 从上方 function Foo() 开始分析这一张经典之图
function Foo()
let f1 = new Foo();
let f2 = new Foo();
f1.__proto__ = Foo.prototype; // 准则2
f2.__proto__ = Foo.prototype; // 准则2
Foo.prototype.__proto__ = Object.prototype; // 准则2 (Foo.prototype本质也是普通对象,可适用准则2)
Object.prototype.__proto__ = null; // 原型链到此停止
Foo.prototype.constructor = Foo; // 准则1
Foo.__proto__ = Function.prototype; // 准则2
Function.prototype.__proto__ = Object.prototype; // 准则2 (Function.prototype本质也是普通对象,可适用准则2)
Object.prototype.__proto__ = null; // 原型链到此停止
// **此处注意Foo 和 Function的区别, Foo是 Function的实例**
// 从中间 function Object()开始分析这一张经典之图
function Object()
let o1 = new Object();
let o2 = new Object();
o1.__proto__ = Object.prototype; // 准则2
o2.__proto__ = Object.prototype; // 准则2
Object.prototype.__proto__ = null; // 原型链到此停止
Object.prototype.constructor = Object; // 准则1
// 所有函数的__proto__ 都和 Function.prototype指向同一个地方
Object.__proto__ = Function.prototype // 准则2 (Object本质也是函数);
// 此处有点绕
Function.prototype.__proto__ = Object.prototype; // 准则2 (Function.prototype本质也是普通对象,可适用准则2)
Object.prototype.__proto__ = null; // 原型链到此停止
// 从下方 function Function()开始分析这一张经典之图
function Function()
Function.__proto__ = Function.prototype // 准则2
Function.prototype.constructor = Function; // 准则1
案例
案例一
let user = {
name: 'John',
surname: 'Smith',
set fullName(value) {
// 理解这一步是关键
[this.name, this.surname] = value.split(' ');
},
get fullName() {
console.log('get', this);
return `${this.name} ${this.surname}`;
}
};
let admin = {
__proto__: user,
isAdmin: true
};
console.log(admin.fullName); // John Smith (*)
// setter triggers!
admin.fullName = 'Alice Cooper'; // (**)
console.log(admin.fullName); // Alice Cooper,admin 的内容被修改了
console.log(user.fullName); // John Smith,user 的内容被保护了
上述的输出结果,感觉主要是去考察自身属性和原型上的属性的处理过程,在上述代码中的setter方法中,通过直接赋值的方式给this对象绑定属性值,这一步是直接去赋值,而不需要去查找原型上是否有对应的属性,然后才去赋值。同时这里也需要去理解this的指向。
案例二
let hamster = {
stomach: [],
eat(food) {
console.log(this);
this.abc = '123';
// this.stomach = '123';
this.stomach.push(food);
}
};
let speedy = {
__proto__: hamster,
hello: ''
};
let lazy = {
__proto__: hamster
};
// 这只仓鼠找到了食物
speedy.eat('apple');
console.log(speedy.stomach); // apple
// 这只仓鼠也找到了食物,为什么?请修复它。
console.log(lazy.stomach); // apple
console.log(lazy.stomach); // apple
这个案例不仅是考察了给原型对象上的属性赋值后,会影响所有实例对象访问该原型属性,还考察了什么时候会去修改原型上的属性值。在这里的
this.stomach.push(food);,它首先会去查找this对象上是否有这个stomach属性,如果有就会去看属性值是否有push方法,然后就进行后续操作;如果没有,那么就要去递归原型链,查看是否有对应的属性,如果找到就判读是否有push方法,然后进行后续操作,如果没有就报错;所以这里最终会影响原型上的属性值,故而两个都输出相同的结果。(这里叙述得比较泛,读者需要去仔细理解访问对象属性和修改对象属性的过程)
原型链继承
在ES5的时候,JavaScript还没有Class类的使用,要实现对象属性的继承就可以通过原型链的方式来实现。
这里可以参考个人文章:JavaScript原型链继承