什么是原型
原型其实就是一个特殊的对象,在声明函数的时候自动创建的。
<!-- more -->
比如,我们现在声明一个构造函数 A ,除了会申请保存函数的内存空间,还会额外申请一个内存空间,用于存储构造函数 A 的原型对象。所有函数中(Function.prototype.bind 除外)默认都有一个 prototype
的属性,它保存了函数的原型对象的地址(引用)(也就是它指向了原型对象)。
而在原型对象中默认有一个 constructor
属性存储了构造函数的地址(引用)(也就是 constructor
指向了构造函数)。如果不理解上面所说的,那我们看下面的图:
浏览器控制台中:
_ _proto_ _
与 prototype
刚开始接触原型的时候这两个东西很容易就搞混了。
先记住以下两点,就很容易就区分了:
-
prototype
是函数中才有的属性 -
__proto__
是所有对象都有的属性
我们已经知道了函数中的 prototype
属性指向的是它的原型对象,那么对象中的 __proto__
代表什么?
一般情况下,对象中的 __proto__
属性是指向它的构造函数的原型对象的,即和构造函数中的 prototype
属性所指向的对象是同一个对象。
用一段简单的代码:
function A() {}
var a = new A()
上图看着不够简便,我们简化一下:
还有一点,__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 Animal()
var a2 = new Animal()
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') {
Animal.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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。