原型
要理解原型首先需要明确几个概念:
1、__proto__
每个引用数据类型(如Object、Array、function)的对象实例,都有这个是属性,同时这个属性也是一个对象;
2、prototype
只有函数对象实例,有这个属性,且这个属性也是个对象;每个prototype都有一个constructor指向这个方法;
3、__proto__和 prototype关系
每个对象实例的__proto__都指向其构造函数的 prototype
看下面的代码:
let myObj= {
name:"张三"
}
console.log(myObj.__proto__ === Object.prototype) // true
console.log(myObj.prototype) // undefined
const comd = function comd(){}
console.log(comd.prototype.constructor) // ƒ comd(){}
而 prototype 就是原型,存放属性和方法的最初对象。
原型链
首先看一个普通对象的原型调用关系:
let myObj= {
name:"张三"
}
myObj.showName() // myObj.showName is not a function
Object.prototype.showName = function(){
console.log("我是"+this.name)
}
myObj.showName() // 我是张三
在创建myObj 时并没有声明 showName 方法,所以调用时会提示找不到方法的错误;
当我们给Object原型中声明了showName方法,再次使用myObj调用时就可以打印正确的信息。
既然通过上面第(3)个概念知道:myObj.__proto__ === Object.prototype,所以可以猜测myObj调用showName 时,是通过__proto__调用了 Object.prototype的showName 方法。
既然提到了构造函数,那么我们看看通过构造函数创建的对象实例调用的顺序:
function People(){
this.name = "张三"
}
People.prototype.showName = function(){
console.log("我是"+this.name)
}
let myPeople = new People()
console.log(myPeople.__proto__ === People.prototype) // true
myPeople.showName() // 我是张三
myPeople.showName = function(){
console.log("我不是"+this.name)
}
myPeople.showName() // 我不是张三
可以看到,myPeople 实例在没有声明showName 方法的时候,会调用构造函数People原型中的方法;
当我们给myPeople 声明了showName 方法后,在调用时打印的内容就是新声明的showName 中的内容。
结论:当调用对象实例的方法时,会首先查找自身有无该方法并执行,如果没有会查找其构造函数的原型方法并执行。
根据上面的结论,我们又有疑问了:这种查找什么时候结束?那么看下面这段代码:
function People(){
this.name = "张三"
}
let myPeople = new People()
console.log(myPeople.__proto__ === People.prototype) // true
myPeople.showName() // myPeople.showName is not a function
console.log(People.prototype.__proto__ === Object.prototype) // true 因为prototype本质是个对象
Object.prototype.showName = function(){
console.log("我是"+this.name)
}
myPeople.showName() // 我是张三
这里我们没有给 myPeople 和 People.prototype 声明showName 方法,所以调用会报错找不到方法。
但是当我们给Object的原型中声明 showName 方法,在通过myPeople 调用,就可以正常打印内容。
原因很简单:当myPeople调用方法时,首先查找自身没有这个方法,所以会通过myPeople.__proto__从构造函数的原型中(People.prototype)查找该方法,但是也没有找到。
由于prototype 本身是个对象,所以会通过People.prototype.__proto__从prototype 的构造函数中(即Object)的原型中查找,在Object.prototype找到了我们声明的showName,所以执行了该方法。
那么对象属性是否和方法表现一致呢?
function People(){
this.name = "张三"
}
let myPeople = new People()
Object.prototype.age = 30
console.log(myPeople.age) // 30
通过上面代码可以看到,属性也是可以通过上述递归查找的方式找到的。那么可以得出结论:
原型链就是通过__proto__递归查找prototype原型的过程。
类
在ES6中引入了class的概念,也是创建对象及实例的方法之一,那么它支持原型吗?看下面的代码:
class People {
constructor(name){
this.name = name;
}
}
let myPeople = new People("张三")
People.prototype.age = 30
console.log(myPeople.age) // 30
Object.prototype.sex = "男"
console.log(myPeople.sex) // 男
可以看到上面章节所使用的原型设置方法,还是可以生效的,所以可以肯定:
通过class是构造函数的另外一种写法,同样带有prototype。