JavaScript中的原型和原型链

提问

  • 原型是用来干什么的?
  • 为何原型那么重要?
  • 如何合理的使用原型?

多是以前开发和思考的层次比较浅,我的感受多数的开发工做与原型和原型链的关系不大,甚至不知道的状况下也可以把任务完成。网上也有很多讲解原型的文章,更有很多面经中提到这个考题,若是不是为了炫技那必是开发中必需要掌握的一个知识点。最近也看了这方面的资料,就上面的三个问题总结一下本身的想法,其实学以至用才是最棒的,目前开发经验少不能体会原型的好处,只作记录方便以后遇到问题可以知道该去查找哪方面的资料。 javascript

prototype、proto

prototype

几乎全部函数都有一个显示的 prototype 属性,除了下面的这种状况java

let fun = Function.prototype.bind()
复制代码

prototype 属性是一个对象,在函数声明的时候就会自动建立,并且默认状况下只有一个属性 constructor 用来指向函数自己。
git

image.png

proto

每一个对象都会有的一个隐式原型属性,指向了建立该对象的构造函数的原型。
es6

image.png

__proto__ 是在对象建立的时候添加属性,连接到构造调用的函数的原型上的。当访问对象上不存在的属性的时候能够顺着这条链查找属性。
new 一个对象的过程能够解析为一下4步:

  • 建立一个空对象 obj
  • obj 的 __proto__ 连接到函数的 prototype
  • 执行构造调用的函数,初始化属性
  • 判断函数返回值是否为一个对象类型,是则返回该返回值,不然返回 obj
// new 语法的简单模拟
var creatObject = function(constructor,...args) {
	var obj = Object.create(null)
  // 连接原型
	Object.setPrototypeOf(obj,constructor.prototype)
	res = constructor.apply(obj,args)
	return typeof res === 'object' ? res : obj
}
复制代码

image.png

理解原型

在JavaScript中没有类这个概念,所谓的继承也是经过原型链来实现的,包括加入的 class 语法糖也是为了更好的理解JavaScript中的“继承”并无改变JavaScript中对象的工做机制,底层仍是原型。那么很天然的想到原型链中也有相似继承的好处。
个人理解上原型有两个主要的做用:github

  • 用来理解对象上属性访问的过程
  • 用来判断对象实例是否由某个函数建立

对象上属性访问的过程

在对象上查找属性的时候,若对象自己不具有该属性,则被查找的属性会被委托在整个原型链上,只有当没有更多的原型能够查找的时候,才中止查找。
咱们定义一个对象 var o = {a:1,b:2} 咱们知道可使用 hasOwnProperty 来判断某属性是否对象自身拥有的属性,可是在建立对象的时候并无建立 hasOwnProperty 方法,这个是如何经过 . 运算调用的呢?
app

image.png

咱们经过 o.__proto__===Object.prototype 能够知道经过字面量的方式建立对象实例 o 的时候的函数是 Object 且如今 o 的原型链上有 Object.prototype 
image.png

咱们再查看一下 Object.prototype 发现这个对象中含有属性 hasOwnProperty 
image.png

也就是说,在 o 中没有找到 hasOwnProperty ,就顺着原型链往上找,首先找 o.__proto__ 也就是 Object.prototype ,很巧在这里就找到了(若是没有找到就会一直顺着这条链往上找),因而拿过来使用。
image.png

判断对象实例是否由某个函数建立

constructor

前面略带的提过这个属性 constructor ,经过这个属性能够访问建立该对象时所用的函数,经过这个咱们能够判断两个实例是不是同一个函数建立而来的,我想这个属性的做用差很少也应该是这样了。
函数

image.png

instanceof

obj instanceof Foo 应该被理解为检测函数 Foo 是否出如今 obj 的原型链中,并非单纯的理解过对象实例是否由某个函数构造调用建立。只要经过 __proto__ 一层一层的往上找可以找到这个函数的 prototype 就说明是这个函数建立的。
ui

image.png

image.png

可是当修改了原型链的时候可能检查不许,如今 dog.__proto__!==Dog.prototype 并且顺着 __proto__ 也找不到 Dog.prototype 因此这里返回 false 
image.png

原型的一些注意点

遮蔽效应

当实例属性和原型属性中有同名属性或者方法的时候优先使用实例属性。下面实例属性中和原型属性中都有 say 函数,这里使用实例属性。顺着原型链查找如今实例属性中找到了就中止查找
this

image.png

函数原型之间的引用关系

对象与函数原型之间的引用关系是在对象建立的时候创建的,新建立的对象会根据当前的函数原型创建引用。
es5

image.png

继承的正确实现方式

上面例子中用一个对象字面量重写了 Dog.prototype 此时 constructor 属性不见了,这个在实习继承效果的时候也须要注意,咱们在修改完原型以后须要补上 constructor

// es5
function Animal() {}
Animal.prototype.say = function() {
  console.log('动物叫')
}
function Dog() {}
let dog = new Dog()
// 连接原型并加上constructor
Dog.prototype = Object.create(Animal.prototype, {
  constructor: {
    value: Dog, // 指向Dog
    enumerable: false,
    writable: true,
    configurable: true
  }
})
//es6 引入了class 和 extends 减小对原型覆盖的反作用
class Animal{
  constructor(name) {
    this.name = name
  }
  say(){
    console.log('动物叫')
  }
}
class Dog extends Animal{
  constructor(name,type){
    super(name) // 调用父类构造函数
    this.type = type
  }
	say(){
    console.log('汪汪')
  }
}
var dog = new Dog('道格','中华田园犬')
dog.say() // 汪汪
console.log(dog instanceof Dog) // true
console.log(dog instanceof Animal) // true
复制代码

抽离公共方法到函数原型

一些公共方法若是做为实例方法建立在每建立一个实例的时候都会产生一个副本,不只占内存并且修改起来麻烦。能够在函数的原型对象上建立对象方法,这样可使得一个方法被全部对象实例共享(能够经过原型链访问)。

// 翻转字符串为例
String.prototype.reversed = function(){
  return Array.from(this).reverse().join('')
}
"abcdef".reversed() // "fedcba"
复制代码

参考资料

相关文章
相关标签/搜索