上一篇文章已经总结了关于原型的两个属性,那么接下来所有原型和原型链,乃至后面的继承,都与这两个属性有关系。
原型和原型链
理解继承,首先要搞懂什么是原型和原型链。
理解原型和原型链
上面已经介绍了关于原型的两个属性:
__proto__
prototype
那么这里再小小总结一下,
1、什么是原型
原型即一个对象的构造器(
prototype
),可以通过该原型构造器创造无数实例,每一个实例都具有指向该原型的属性(__proto__
)。
2、什么是原型链
在对象中通过原型,一层一层向上查找父级引用,直到没有父级(null)。整个这条引用链,即为原型链。原型链定义了对象可以继承的的属性/方法和相互之间的关系。
理解原型继承
有了这两个概念,尝试理解一下原型继承。
回到最开始的例子
让我们重新来看一开始那个数组的例子:
let arr = [];
我们尝试使用数组的方法:
arr.concat(["jeremyjone"]);
它会成功,但是我们并没有给 arr
添加 concat
的方法。它调用的是其父级 --也就是 Array 构造器-- 中的方法。
这其实就是继承。我们创建的 arr
数组实例,调用了其原型链父级的方法。
一个原型继承的例子
如果是函数呢?再来看一个例子:
function User() {}
这时候我们都知道这个 User 没有方法任何自己的方法。
万物都是从 Object 继承的。此时,User 也是从 Object 继承的。
那么现在给 Object 添加一个方法:
Object.prototype.print = function () {
console.log("Hi jeremyjone, this is Object print.");
};
原型本身也可以继承
我们首先尝试让 User 调用这个 print 方法,要明确 User 是一个原型,但是当我们直接使用 User 的时候,它本身也是一个对象:
没有错,它可以被调用。这说明在原型链上,是可以找到该方法,这说明了 User 确实是从 Object 继承的。
从图中可以看到在继承的原型中有 print
方法,即有如下等式关系:
User.__proto__.__proto__ === Object.prototype; // true
原型创建的实例来继承
同样还是刚才的例子,现在我们实例化一个 user,那么 User 现在作为一个原型,被 user 实例对象继承:
// 接上例
let user = new User();
这样得到一个 user 实例。它也可以使用其原型属性和方法。
该 user 实例中,只有一个 __proto__
属性,它指向了其原型,也就是 User.prototype
,而其原型的父级正是 Object,即:
user.__proto__ === User.prototype; // true
user.__proto__.__proto__ === Object.prototype; // true
这个例子也印证了我们上面提到的 __proto__
属性是作用于对象的。所以,实例和原型的 __proto__
的值是不一样的:
user.__proto__ === User.__proto__; // false
同时也可以得出,最顶级的 Object 一定是一致的(Object 里面确实比较混乱,可以不用考虑),即:
user.__proto__.__proto__ === User.__proto__.__proto__; // true。它们都指向 Object.prototype
针对该例子,可以作如下理解:
原型链中的优先级
现在我们知道,对象的属性/方法都是一层一层向上查找,那么如果遇到相同的内容,它的优先级是如何的呢?
举一个例子:
function User(name) {
this.name = name;
}
let user = new User("jeremyjone");
现在创建了一个对象,并生成一个对象实例,它现在的样子应该是:
现在往 Object 的原型中添加几个属性:
Object.prototype.a = "a";
Object.prototype.b = "b";
Object.prototype.c = "c";
添加之后,这条原型链应该是这样的:
根据上面提过的,现在:
console.log(user.a); // "a"
console.log(user.b); // "b"
console.log(user.c); // "c"
应该是可用的。
现在分别修改值:
user.a = "aa"; // 修改 user 对象的 a 属性
User.b = "bb"; // 修改 User 模型的 b 属性
User.prototype.c = "cc"; // 修改 User 原型的 c 属性
其结果如下:
console.log(user.a); // "aa"
console.log(user.b); // "b"
console.log(user.c); // "cc"
console.log(User.a); // "a"
console.log(User.b); // "bb"
console.log(User.c); // "c"
分析一下三条执行语句:
1、当执行 user.a = "aa"
语句时,user
对象中没有 a
属性,所以添加了一个值为 "aa"
的 a
属性。这样,当读取 a
属性时,就不会从原型中查找。
2、而 User.b = "bb"
语句是给 User
模型添加一个属性,该属性没有添加在原型中,而是添加在了 constructor
中,根据上面我们讲过的内容,constructor
保存私有属性/方法,而 prototype
保存共享的属性/方法,所以 User.b
属于私有,并不被 user
所继承。
3、最后 User.prototype.c = "cc"
语句,是在 User
的原型中添加一个值为 cc
的 c
属性,所以它可以被继承到 user
,也就有了打印的结果。
现在对象 user 的原型如下:
而 User 的原型如下:
图中可以看到和分析的一致。
这与主流语言的继承相似,即当前对象属性/方法的优先级最高。隐约看到了多态的影子~
操作对象的原型关系
前面说不建议直接操作 __proto__
属性,那么我们需要通过更规范的方式进行操作,用到两个方法:
Object.setPrototypeOf
Object.getPrototypeOf
除了名字长一些,其实还是很规范的。
Object.setPrototypeOf
该方法修改对象的原型关系,可以变更当前对象的从属关系。
let a = { a: "-a" };
let b = { b: "-b" };
Object.setPrototypeOf(a, b); // 将 a 从属于 b
// 这等同于 a.__proto__ = b;
得到如下关系:
这样就改变了两个对象之间的从属关系,那么现在 a
已经继承了 b
的属性:
console.log(a.b); // -b
Object.getPrototypeOf
使用该方法可以查看一个对象的原型。
// 续上例
Object.getPrototypeOf(a) === a.__proto__; // true
console.log(Object.getPrototypeOf(a)); // {name: "bb", b: "-b"}
原型链的检测
instanceof 运算符
使用 instanceof
运算符,可以向上查找原型链中的从属关系。
function A() {}
function B() {}
let a = new A();
let b = new B();
console.log(a instanceof A); // true
console.log(a instanceof B); // false
console.log(a instanceof Object); // true
稍微改变一下:
// 接上例
A.prototype.__proto__ = B.prototype; // 修改 A 的原型关系,现在 B 是 A 的父级
console.log(a instanceof A); // true
console.log(a instanceof B); // true
console.log(a instanceof Object); // true
console.log(b instanceof B); // true
console.log(b instanceof A); // false
可以看到,当 A 的原型链发生改变的时候,下面的判断也会发生改变,a 已经从属于 B 了。
甚至我们添加方法,也可以使用了:
a.show(); // 报错,因为没有方法
B.prototype.show = function () {
console.log("B show");
};
// 此时,对象 a 可以调用 show 方法了。它在原型链中
a.show(); // B show
Object.isPrototypeOf 方法
作为与上面 instanceof
运算符的区别,使用 Object.isPrototypeOf
方式,可以明确检测一个对象是否在另一个对象的原型链上。
let a = {};
let b = {};
let c = {};
Object.setPrototypeOf(a, b);
console.log(a.isPrototypeOf(b)); // false
console.log(b.isPrototypeOf(a)); // true
console.log(c.isPrototypeOf(a)); // false
console.log(Object.prototype.isPrototypeOf(a)); // true
console.log(Object.isPrototypeOf(a)); // false
检测是否存在属性/方法
in 运算符检测原型链
使用 in
运算符检测原型链中是否存在属性/方法,该方法会在原型链中逐级检测。
let a = { name: "jeremyjone" };
Object.prototype.url = "jeremyjone.com";
console.log("name" in a); // true
console.log("a" in a); // false
console.log("url" in a); // true
Object.hasOwnProperty 方法检测
使用 Object.hasOwnProperty
方法只检测当前对象的属性。
let a = { name: "jeremyjone" };
Object.prototype.url = "jeremyjone.com";
for (const key in a) {
if (a.hasOwnProperty(key)) {
console.log(a[key]);
}
}
// 只有一条 "jeremyjone"
文章评论