JavaScript 中的原型原来是这样的

3次阅读

共计 2886 个字符,预计需要花费 8 分钟才能阅读完成。

什么是原型
原型其实就是一个特殊的对象,在声明函数的时候自动创建的。
<!– more –>
比如,我们现在声明一个构造函数 A,除了会申请保存函数的内存空间,还会额外申请一个内存空间,用于存储构造函数 A 的原型对象。所有函数中 (Function.prototype.bind 除外) 默认都有一个 prototype 的属性,它保存了函数的原型对象的地址(引用)(也就是它指向了原型对象)。而在原型对象中默认有一个 constructor 属性存储了构造函数的地址(引用)(也就是 constructor 指向了构造函数)。如果不理解上面所说的,那我们看下面的图:

浏览器控制台中:

_ _proto_ _ 与 prototype

刚开始接触原型的时候这两个东西很容易就搞混了。
先记住以下两点,就很容易就区分了:

prototype 是函数中才有的属性

__proto__ 是所有对象都有的属性

我们已经知道了函数中的 prototype 属性指向的是它的原型对象,那么对象中的 __proto__ 代表什么?
一般情况下,对象中的 __proto__ 属性是指向它的构造函数的原型对象 (prototype) 的。
用一段简单的代码:
function A() {}
var a = new A()
.png)
上图看着不够简便,我们简化一下:

还有一点,__proto__ 不是一个规范属性,ie(除了 ie10) 不支持。对应的标准属性是 [[Prototype]],但是这个属性我们没法直接访问到。开发者尽量不要用这种方式去访问,因为操作不慎会改变这个对象的继承原型链。
在使用 Object.create(参数) 方式创建对象时,对象的 __proto__ 属性指向的是传入的参数。

原型链
由于 __proto__ 是所有对象都具有的属性,而 __proto__ 本身指向的原型 (函数.prototype) 也是一个对象,它也有 __proto__ 属性。所以这样会形成由 __proto__ 将对象和原型连起来的链条。这就是原型链。原型链的顶端是 Object.prototype(Object 是所有对象的祖宗),Object.prototype.__proto__的值为 null。
还是看之前的代码:
function A() {}
var a = new A()
它的原型链如下:
构造函数 A 其实也是一个对象。所有函数都是由 Function 函数构造的。(声明函数 function A() {} 等价于 var A = new Function())。所以所有函数的 __proto__ 指向的都是 Function.prototype。更新上图:
Function 也是一个函数,它的 __proto__ 指向的也是 Functon.prototype 即 Funtion.__proto__ === Function.prototype。继更新上图:

Object 同样是一个函数,所以 Object.__proto__ === Function.prototype
到了这里,我们应该可以看懂下面这张图了:

原型的作用
当 JS 引擎查找对象属性时,先查找对象本身是否存在该属性,如果不存在,会在对象的 __proto__ 里找,还找不到就会沿着原型链一直找到原型链顶端(Object.prototype) 直到找到属性为止,最后在原型链顶端都没找到就返回 undefined。
由于上面的机制,原型的作用就很明显了——共享属性,节省内存空间。
function Animal() {
this.name = ‘ 动物 ’
this.eat = function() {
console.log(‘ 在吃···’)
}
}
var a1 = new Animal()
var a2 = new Animal()

console.log(a1.eat === a2.eat) // false
// 每个对象的 eat 方法不是同一个,但方法类容一样,浪费内存
使用 原型解决:
function Animal(name) {
this.name = ‘ 动物 ’
}
Animal.prototype.eat = function() {
console.log(‘ 吃 ’)
}

var a1 = new A()
var a2 = new A()

console.log(a1.eat === a2.eat) //true
// a1.eat 和 a2.eat 都同一个方法(Animal.prototype.eat)
原型非常适合封装共享的方法。但是上面的代码把构造函数和原型分开写了。封装不到位。使用动态类型模式解决。
function Animal() {
this.name = ‘ 动物 ’

/*
判断 this.eat 是不是 函数类型,
如果不是,则表示是第一次创建对象或者调用 Animal 函数,
会将 eat 添加到原型中去。
如果是,则表示原型中存在了 eat 方法,不需要再添加。
*/
if(typeof this.eat !== ‘function’) {
Person.prototype.eat = function() {
console.log(‘ 吃 ’)
}
}
}

var a = new Animal()
a.eat()
原型基于之前的共享属性和方法,是实现 JS 中继承的基础。
与原型有关的方法
hasOwnProperty()
通过之前的学习,我们知道了去访问一个对象的属性时,会在原型链上查找。所以我们并不知道这个属性来自哪里。
hasOwnProperty() 方法返回一个布尔值,可以判断一个属性是否来自对象本身。
function Animal() {}
Animal.prototype.name = ‘ 动物 ’
var a = new Animal()
a.age = 3

console.log(a.hasOwnProperty(‘name’)) // false
console.log(a.hasOwnProperty(‘age’) // true

in 操作符

in 操作符用返回一个布尔值,用来判断一个属性能否在对象上找到。在对象的原型链上找到也返回 true。
function Animal() {}
Animal.prototype.name = ‘ 动物 ’
var a = new Animal()
a.age = 3

console.log(‘name’ in a) // true
console.log(‘age’ in a) // true
console.log(‘sex’ in a) // false

总结

原型就是一个对象,声明函数就会创建原型对象

prototype 只存在于函数中
所有对象都有一个 __proto__ 属性,它指向对象的构造函数的原型
原型 也是对象,也有 __proto__ 属性,__proto__ 将对象和原型连接起来,形成原型链

Object.prototype 是原型链的顶端
访问对象的属性会沿着对象的原型链找下去
原型可以共享属性和方法,是继承的基础

阅读原文
参考资料:
https://juejin.im/post/5835853f570c35005e413b19
https://blog.csdn.net/u012468376/article/details/53121081
https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5bed40d951882545f73004f6

正文完
 0