📕 重学JavaScript:你理解原型链吗?
嗨,大家好!这里是道长王jj
~ 🎩🧙♂️
你知道我们常说的JavaScript中的”对象“是什么吗?
我们在编码过程中经常使用存储很多信息📦。比如说,你可以用一个对象来描述一个人,他有什么名字,多大年纪,会做什么事情等等。
在 JavaScript 中,我们整个编码过程都离不开”对象“,这个看起来简单的且基础的东西,却蕴含着着很多的知识点。也是我们理解原型链非常重要的一个辅助工具🔧。
❓ 原型对象和构造函数有何关系?
我们先来举个例子:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log("Hello, I'm " + this.name + ", and I'm " + this.age + " years old.");
};
var alice = new Person("Alice", 18);
alice.sayHello(); // Hello, I'm Alice, and I'm 18 years old.
var bob = new Person("Bob", 20);
bob.sayHello(); // Hello, I'm Bob, and I'm 20 years old.
// alice.sayHello === bob.sayHello
首先,我们定义了一个构造函数👷♂️,叫做 Person。构造函数就是一个特殊的函数,它可以用来创建对象。这个构造函数接受两个参数:name 和 age。当我们用 new 关键字来调用这个构造函数时,它会给新创建的对象设置两个属性:name 和 age。这两个属性的值就是我们传入的参数。
然后,我们在 Person 的原型对象👥上定义了一个方法,叫做 sayHello。原型对象就是一个包含了很多属性和方法的东西,它可以被所有由 Person 构造函数创建的对象所共享。这个方法会打印出对象的名字和年龄,并且说一句你好。我们用 this 关键字来指代当前调用这个方法的对象。
所以,我们首先就得到了这样一组关系:
graph LR;
A[Person构造函数] --prototype--> B[原型对象];
B--constructor--> A;
接着,我们用 new 关键字来实例化👶 Person 构造函数,传入两个参数,创建了两个新的对象,分别叫做 Alice 和 bob。new 关键字会让 JavaScript 帮我们做以下几件事情:
- 创建一个空的对象;
- 把这个空对象的原型对象设置为 Person 的原型对象;
- 把这个空对象作为 this 的值传给 Person 构造函数,并执行它;
- 返回这个空对象(如果 Person 构造函数没有返回其他对象的话)。
所以,Alice 和 bob 这两个对象就有了 name 和 age 这两个属性,也有了 sayHello 这个方法。我们可以调用他们各自的 sayHello 方法,让它打印出自己的名字和年龄。注意,他们调用的是同一个方法。
我们就可以完善这一组关系:
graph LR;
A[Person构造函数] --prototype--> B[原型对象];
B--constructor--> A;
A--new-->C[实例对象];
C--_proto_-->B
这组关系说明,每个函数都有一个特殊的属性,叫做 prototype。这个属性指向了一个对象,叫做函数的原型对象👥。这个原型对象里面有一些属性和方法,它们可以被所有由这个函数创建的对象所共享。
当你用 new 关键字来调用一个函数时,这个函数就变成了一个构造函数👷♂️。它会返回一个全新的对象,叫做实例对象👶。这个实例对象有一个隐藏的属性,叫做 _proto_。这个属性指向了构造函数的原型对象。
❓ 什么又是原型链呢?
原型链是 JavaScript 中的一个非常重要的概念,它可以被形象地理解成由一些原型对象连起来的一条链子,用于表示对象之间的继承关系👩👧。在 JavaScript 中,每个对象都有一个隐藏的属性,叫做 _proto_
。这个属性指向了这个对象的原型对象👥。如果我们在这个对象上找一个属性或方法,而这个对象本身没有这个属性或方法,那么就会沿着原型链向上找,直到找到这个属性或方法为止😊。
让我们来看一个简单的例子吧😉:
classDiagram
Object <|-- Person
Person <|-- p1
Object : +__proto__
Object : +constructor
Person : +__proto__
Person : +constructor
p1 : +__proto__
p1 : +constructor
Object ..> Object: __proto__
Person ..> Object: __proto__
p1 ..> Person: __proto__
在这个例子中,Object 是所有对象的老祖宗👴,Person 是 Object 的孩子👶,p1 是 Person 的孩子👶。当我们创建一个 Person 实例时,它会继承 Object 的所有属性和方法。如果我们在 Person 实例上找一个属性或方法,而这个实例本身没有这个属性或方法,那么就会沿着原型链向上找,直到找到这个属性或方法为止。原型链最后会的原型对象是Object,Object最终会指向null 即原型链的终点。
💌 番外:如何判断原型上有没有这个属性
- 对象的 hasOwnProperty() 来检查对象自身中是否含有该属性
- 使用 in 检查对象中是否含有某个属性时,如果对象中没有但是原型链中有,也会返回 true
// 定义一个对象
var obj = {
name: "Bing",
age: 10
};
// 定义一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 给Person的原型添加一个属性
Person.prototype.gender = "male";
// 创建一个Person的实例
var p = new Person("Tom", 20);
// 使用hasOwnProperty()来检查对象自身中是否含有该属性
console.log(obj.hasOwnProperty("name")); // true
console.log(obj.hasOwnProperty("gender")); // false
console.log(p.hasOwnProperty("name")); // true
console.log(p.hasOwnProperty("gender")); // false
// 使用in检查对象中是否含有某个属性时,如果对象中没有但是原型链中有,也会返回true
console.log("name" in obj); // true
console.log("gender" in obj); // false
console.log("name" in p); // true
console.log("gender" in p); // true
🎉 你觉得怎么样?这篇文章可以给你带来帮助吗?如果你有任何疑问或者想进一步讨论相关话题,请随时发表评论分享您的想法,让其他人从中受益。🚀✨