原型分为显式原型prototype和隐式原型,基本上所有类型都拥有隐式原型,在一些浏览器里隐式被叫做 __proto__。
可以理解为 显式原型为自身的原型,隐式原型为一个引用,指向的是父级的原型。
可以把自身的原型就叫原型,把__proto__叫做引向,引向父级。
function fn(){}
console.log(Object.getPrototypeOf(fn)===Function.prototype); //true fn的隐式引用Function的原型
console.log(Object.getPrototypeOf(fn.prototype)===Object.prototype) //true fn的原型的隐式指向Object的原型
Object.getPrototypeOf可以获取对象的父级引向,对等的有Object.setPrototypeOf函数可以设置一个对象的父级引向。
设置原型的好处是可以让实例共用方法,不用重复的生成,还可以共享一个变量。
函数原型中默认拥有一个构造参数和一个对父的指向__proto__,构造函数其实就是这个函数本身,这个对父的指向是构成原型链的关键,当别的函数继承这个函数的时候,会把自身的引向指到这个函数的原型(prototype),然后一路通过访问父引用,实现向上查找,这一路查找的过程就是遍历了整个原型链,而这个原型链指的就是不断的调用__proto__的过程。
function fn(){}
console.log(fn,fn.prototype.constructor,fn===fn.prototype.constructor);
// fn(){} ƒ fn(){} true
原型链的作用主要是实现继承机制,方法就是不断的通过__proto__查找父级的原型。
子类.__proto__=父类.prototype,父类.prototype.__proto__=Object.prototype,Object.prototype.__proto__=null
原型链的终点就是null,当一直向上找,结果发现最后找到了null,就会报错。
function People(name,age){
this.name=name;
this.age=age;
}
People.prototype.say=function(){
console.log(`我叫${this.name},今年${this.age}岁了`);
}
People.prototype.info=function(){
console.log('我是人啦!');
}
function Man(name,age,length){
People.call(this,name,age);
this.length=length;
}
Man.prototype.say=function(){
console.log(`我叫${this.name},今年${this.age}岁了,长度是${this.length}cm`);
}
Object.setPrototypeOf(Man.prototype,People.prototype);
//把自己的prototype的__proto__指向People.的prototype,等同于 Man.prototype.__proto__=People.prototype
let tom=new Man('tom',22,18);
tom.say(); // 我叫tom,今年22岁了,长度是18cm
tom.info(); // 我是人啦!
其他文章中经常用 Man.prototype=new People() 这样做的缺点就是说不定会覆盖上之前声明的一些原型方法需要调整顺序,而且还需要修复一下constructor,因为他把自己的原型直接指向了父级,导致里面的构造函数变成了父级的构造函数,因为new出来的对象其实是没有原型的,也就是说在本体的原型上根本找不到constructor只能一路向上找(通过原型链),找到父级的constructor,看一下new的实现
function MyNew(target,...args){ //简单的实现
let obj={}; //因为是普通对象没有原型 prototype=undefined
parent.call(obj,...args);
Object.setPrototypeOf(obj,target.prototype); //把obj的引用指向目标的原型
// obj.__proto__=target.prototype;
return obj;
}
其实可以也看到,真正做到构造的是这个函数本身,并不是原型里的那个构造函数!!也就是说,其实在上面不修复那个constructor好像也没什么大不了的,最终的运行结果看上去也不会有错
function People(name, age) {
this.name = name;
this.age = age;
}
People.prototype.say = function () {
console.log(`我叫${this.name},今年${this.age}岁了`);
}
People.prototype.info = function () {
console.log('我是人啦!');
}
function Man(name, age, length) {
People.call(this, name, age);
this.length = length;
console.log('其实用来构造的还是我啦!!Man!!');
}
Man.prototype = new People();
// Man.prototype.constructor=Man; 就不修复!!!
Man.prototype.say = function () {
console.log(`我叫${this.name},今年${this.age}岁了,长度是${this.length}cm`);
}
let tom = new Man('tom', 22, 18);
tom.say() // 我叫tom,今年22岁了,长度是18cm
tom.info(); // 我是人啦!
输出:
其实用来构造的还是我啦!!Man!!
我叫tom,今年22岁了,长度是18cm
我是人啦!
看吧好像也没什么错,那这个原型里的构造函数用处到底是...
console.log(tom.constructor === People,tom.constructor===Man); true false
上面就是不修复的后果,用来做简单的来源判断的时候会发生错误,谁知道这种错误会发生什么,所以最好还是修复掉,要不然就干脆不这样用,直接上最开始那样用Object.setPrototypeOf把子类的原型的父引向指到父类上,不影响其他函数,也不会因为顺序问题导致把好不容易写的原型方法替换掉,应该是除了class的extend以外最好的继承方式了。
最后看一下ES6给的最简单最好理解的继承实现方式
class People{
constructor(name,age){
this.name=name;
this.age=age;
}
say(){
console.log(`我叫${this.name},今年${this.age}岁了`);
}
info(){
console.log('我是人啦!');
}
}
class Man extends People{
constructor(name,age,length){
super(name,age);
this.length=length;
}
say(){
console.log(`我叫${this.name},今年${this.age}岁了,长度是${this.length}cm`);
}
}
let tom=new Man('tom',20,18)
tom.say()
tom.info()
console.log(Object.getPrototypeOf(tom)===Man.prototype)
输出:
我叫tom,今年20岁了,长度是18cm
我是人啦!
true