本文借鉴medium 中的一篇文章 (Prototypes in JavaScript)
https://medium.com/better-programming/prototypes-in-javascript-5bba2990e04bmedium.com之前看过很多文章,都没有通俗易懂的把原型和原型链介绍清楚,甚至让人看得云里雾里。本人第一次写文章,有错误的地方请指正。
构造器函数(Constructor Function)问题
相信大家对JavaScript 的构造器函数已经很熟悉了,下面代码将声明一个构造器函数以及通过 "new" 表达式创造两个Human实列。
function
![151718ebc6e2d002b25228df89f6ab4b.png](https://img-blog.csdnimg.cn/img_convert/151718ebc6e2d002b25228df89f6ab4b.png)
代码运行后JavaScript 引擎会创建两个的Human实列并分配内存空间,而其实这两个实列中 "name", "age" 的值都是相等的,“sayName" 函数也拥有同样的功能,这样的操作无疑是对内存的浪费。
原型对象(Prototype Object)
了解了构造函数后,我们再来了解一下原型对象。
每当JavaScript 的函数 (注意不是实列,这里为Human函数)被创建后,JavaScript引擎会添加一个名为 "prototype"的属性到这个函数对象中。那这个属性到底有啥用呢?
首先, ptototype属性会指向一个对象,这个对象我们称为原型对象Prototype Object.
这个对象中有一个名为 'constructor'的属性, 这个属性又指向原本的函数对象。
![49da7182cb45e88726001158d8908354.png](https://img-blog.csdnimg.cn/img_convert/49da7182cb45e88726001158d8908354.png)
你可以通过 Human.prototype 来访问原型对象。(__proto__)这里不做讨论
![0bc1d1117ddda6973b4317687da43528.png](https://img-blog.csdnimg.cn/img_convert/0bc1d1117ddda6973b4317687da43528.png)
你可能会奇怪,为啥要创建这个原型对象? 针对之前的构造器函数问题,你是不是有些头绪了呢?
通过构造函数创造对象时,他们三者的关系如下:
![5474d4a085c84ad135c76e7212eb3066.png](https://img-blog.csdnimg.cn/img_convert/5474d4a085c84ad135c76e7212eb3066.png)
可以预想到, person1._proto_和 Human.prototype 的值是相等的,因为他们指向的是同一对象。
Human.prototype === person1.__proto__ //true
创建第二个对象:
![03b71ac38fe6902978d4b11e8b450601.png](https://img-blog.csdnimg.cn/img_convert/03b71ac38fe6902978d4b11e8b450601.png)
所以说, 原型对象是所有实列共享的。
原型对象所存在的问题
我们可以往原型对象里添加,删除,修改属性。 例如
Human.prototype.sayHello= function () {
console.log("hello")
}
Human.prototype.gender = "male"
let person = new Human()
console.log(person.gender) // 输出 "male"
let person1 = new Human()
console.log(person1.gender) //输出 "male"
到这里就碰到了另外一个问题, 既然原型对象是所有实列共享的, 那么当某个实列修改原型属性时会对所有的实列产生影响吗??
答案是: 修改值类型的时候不会产生影响, 修改引用类型的时候会产生影响。
当我们使用JS里对象的属性时,JS引擎会首先搜寻对象本身的属性,如果没有,再按照原型链搜寻原型对象里面的属性。
当属性是值类型时
function Human() {
}
Human.prototype.gender = "male"
let person1 = new Human()
console.log(person.gender) // 输出 "male"
let person2 = new Human()
console.log(person1.gender) //输出 "male"
person.gender = "female"
console.log(person1.gender) // 输出 "female"
console.log(person2.gender) //输出 "male"
当person1修改gender属性时, 会在本身对象内创建一个新的属性 gender, 并将其值设为 "female" 所以不会影响person2。
当属性是引用类型时
function Human() {
}
Human.prototype.friends = ["kelly", "joe", "kian"]
let person1 = new Human()
let person2 = new Human()
console.log(person1.friends) //["kelly", "joe", "kian"]
console.log(person2.friends) //["kelly", "joe", "kian"]
person1.friends.push("yu")
console.log(person1.friends) //["kelly", "joe", "kian","yu"]
console.log(person2.friends) //["kelly", "joe", "kian","yu"]
上面列子中,因为在Human的原型对象中定义了一个数组对象 "friends" ,因为数组是引用类型,所以在其中一个实列中修改引用类型的原型属性会影响其他的实列。
解决方法
先总结一下上面碰到的问题:
- 构造器函数问题:用构造器创建的每个对象都会拥有一份函数对象的实列,造成内存浪费。
- 原型问题:在其中一个对象中修改原型对象的引用类型的属性,会影响其他对象。
解决方法是: 将公用的方法放在原型中。
function Human (name, age) {
this.name = name
this.age = age
this.firends = ['Kian', 'Joe']
}
Human.prototype.sayHello = function () {
console.log(`Hi I am ${this.name} I am ${this.age} years old`)
}
const person1 = new Human("Jeff",22)
const person2 = new Human("Wang",25)
console.log(person1.sayHello === person2.sayHello) //true
person1.firends.push("Xiao")
console.log(person1.firends) //['Kian', 'Joe',"xiao"]
console.log(person2.firends) // ['Kian', 'Joe']