从底层原理一步步剖析JS的原型和继承【40个疑问解答】

644 阅读16分钟

总结原型链怎么记忆 【三大块的关系:Function、Object、构造函数】:

  1. Object/Function/Person(构造函数)/Date/String等是Function的实例 ,所以 Object/Function/Person(构造函数)/Date/String.__proto__=Function.prototype
String.__proto__   //function () {}
Object.__proto__   //function () {}
Person.__proto__    //function () {}
Function.__proto__  //function () {}
Function.prototype  //function () {}
  1. __proto__是每个对象都有的一个属性,而prototype是函数才会有的属性!!!
  • 几乎所有的函数(除了一些内建函数如Function.prototype)都有一个名为prototype(原型)的属性 ,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以有特定类型的所有实例共享的属性和方法。

  • Object.prototype是所有函数的爹,当你声明一个函数的时候也就是相当于对Object的实例化。

prototype是通过调用构造函数而创建的那个对象实例的原型对象

Object.prototype  //Object { } 也就是{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …} 
  • 凡是对象都会有一个属性那就是__proto__,可称为隐式原型,指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。

函数是对象,但是对象不是函数。

  1. Function.__proto__ 和 Function.prototype 值相同 【特殊性,只有Functon成立这种等式】
Function.prototype //function () {}
Function.__proto__==Function.prototype //true
Function.__proto__.__proto__ ==  Object.prototype //true   说明Object.prototype是所有函数的爹

1.构造函数:function Function() { [native code] }
2.原型: function () {}
3.console.log(B instanceof Function) //true

  1. Object.__proto__.__proto__ == Object.prototype 【说明Object.prototype是所有函数的爹,因为Object.__proto__是一个具体函数(function () {})】
Object.__proto__  //function () {}

Object.prototype.__proto__ // null
Object.__proto__.__proto__ == Object.prototype // true    有点像Object.__proto__的Object的实例化对象,但貌似不能这么记忆。
Object.__proto__.__proto__.__proto__ // null
class Person{}

由Object.prototype是所有函数的爹,Person.__proto__是一个具体函数(function () {}),
可以推导出 Person.__proto__.__proto__ ==  Object.prototype  //true 

Person.prototype.__proto__  等于什么呢?

image.png

发现
Person.__proto__.__proto__ === Person.prototype.__proto__  //true

Person.__proto__  // ƒ () { [native code] }

Person.prototype 得到什么呢?

image.png

发现 
Person.__proto__ === Person.prototype  //false   只有Function有这种等式,其他都没有

Person.prototype的结果也得与Object.prototype区分开

发现 
Person.prototype.__proto__ === Object.prototype  //true   向原型链上查找  
let p = new Person() 
p.prototype  //undefined 

因为p.__proto__ === Person.prototype 所以

p.__proto__.__proto__ === Object.prototype  //true

实例对象p可以根据p.__proto__ === Person.prototype 创建跟 Object.prototype的关系

  1. instanceof运算符用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上。

也就是obj instanceof Object 检测Object.prototype是否存在于参数obj的原型链上

StudentPerson都在s的原型链中

function Person(){};
function Student(){};
var p =new Person();
Student.prototype=p;//Student继承P 原型继承
var s=new Student();
console.log(s instanceof Student);//true   s.__proto__ == Student.prototype  肯定成立
console.log(s instanceof Person);//true    s.__proto__ == Person.prototype

s.__proto__   // Person {} 
//s是实例化对象, 沿着原型链查找

Student.prototype.__proto__ 什么呢

image.png

//发现这两个值相等
s.__proto__.__proto__  ===  Student.prototype.__proto__   //trueStudent.prototype.__proto__结果得出Person原型链上,Person.prototypeStudent.prototype的爹,同Object.prototype是函数的爹
console.log(s instanceof Person);//true    s.__proto__ == Person.prototype

继续补充分析:

function B(){}
let b = new B();

console.log(b.constructor)  //function B(){}
console.log(B.constructor)  //function Function() { [native code] }

console.log(B.prototype)   //[object Object]
/*
要知道B.prototype是指B上面的原型对象,B是一个函数,它原型对象上有构造函数和原型__proto__
{constructor: ƒ} 
constructor: ƒ B()
__proto__: Object
*/

console.log(Object.prototype)  // Object {  }


/*
我觉得:通过Function.prototype得到的结果,prototype不一定都是返回对象,如果有错大神指正下
*/
console.log(Function.prototype)  //function () {[native code]}

/*
Object.prototype是所有函数的爹,所以
b和B是Object的实例对象,B函数也是Function的实例对象
*/

console.log(b instanceof Function) //false
console.log(b instanceof Object)//true

console.log(B instanceof Function)//true
console.log(B instanceof Object)//true

console.log(Object.prototype.isPrototypeOf(B))//true
console.log(B instanceOf Object) //true

console.log(B.__proto__)   // function () {[native code]}
根据函数也是对象,指向构造该对象的构造函数的原型,

也就是指向构造B的构造函数是function Function() { [native code] },
它的prototype是 function () {}

由此得出一直费解的
1.构造函数:function Function() { [native code] }
2.原型: function () {}
3.console.log(B instanceof Function)//true

Object.prototype 又是所有函数的爹,所以得出:
console.log(B instanceOf Object) //true
console.log(Object.prototype.isPrototypeOf(B))//true


console.log(b.__proto__)   //[object Object]
b的构造函数是B,B的原型是对象

因为b对象的__proto__属性指向它构造函数B的prototype属性,很容易理解

console.log(b instanceof Function) //false
console.log(b instanceof Object)//true

由b.__proto__ = B.prototype,根据原型链继续向上查找B.prototype.__proto__ ,而B.prototype.__proto__指向的就是Object.prototype,所以得出console.log(b instanceof Object)//true

因为console.log(Function.prototype)  //function () {},所以console.log(b instanceof Function) //false

最后面得出关系图

未命名文件 (1).png 引用类型的四个规则:

1、引用类型,都具有对象特性,即可自由扩展属性。

const obj = {}
const arr = []
const fn = function () {}

obj.a = 1
arr.a = 1
fn.a = 1

console.log(obj.a) // 1
console.log(arr.a) // 1
console.log(fn.a) // 1

2、引用类型,都有一个隐式原型 __proto__ 属性,属性值是一个普通的对象。

const obj = {};
const arr = [];
const fn = function() {}

console.log('obj.__proto__', obj.__proto__);
console.log('arr.__proto__', arr.__proto__);
console.log('fn.__proto__', fn.__proto__);

3、引用类型,隐式原型 __proto__ 的属性值指向它的构造函数的显式原型 prototype 属性值。

const obj = {};
const arr = [];
const fn = function() {}

obj.__proto__ == Object.prototype // true
arr.__proto__ === Array.prototype // true
fn.__proto__ == Function.prototype // true

4、当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型 __proto__(也就是它的构造函数的显式原型 prototype)中寻找。

const obj = { a:1 }
obj.toString
// ƒ toString() { [native code] }

参考我的另一篇:结合实例对instanceof详解

本文从底层原理一步步剖析JS的原型和继承,学习了这些,我觉得自己对这方面的理解的功底明显加厚,之前也总结过几篇相关文章,但是这篇是方便平时使用的理解。也更系统的一步步递进知识点,形成知识体系。懂得这些思想,对于写代码架构有帮助。

没有原型的对象是存在的

Object.create(null, {name:'12'})

原型对象与对象方法优先级

let obj = {
    render(){
        console.log(1)

    }
}
obj.__proto__.render = function(){
    console.log(2)
}
obj.render() // 1

优先对象方法

函数拥有多个长辈(prototype和_proto__)

函数作为对象使用,调用__proto__上的函数

函数实例化对象,调用prototype上的函数

function User(){}
User.__proto__.view = function(){
    console.log('view')
}
User.view();  //view

User.prototype.show = function(){
    console.log('show')
}
let user = new User();
user.show() // show

console.log(User.prototype == user.__proto__) //true

把构造函数作为对象调用,使用__proto__,服务于它自己。prototype通过new出来无数个对象的时候使用

原型关系详解与属性继承实例

let obj = new Object()
Object.prototype.show = function(){
    console.log('show')
}

obj.show()  //show

function User(){}
let user = new User();

//Object.prototype的上级是null
console.log(Object.prototype.__proto__)  //null

//User.prototype和`User.__proto__`也是对象,这两个对象一定也有父级,分别取__proto__,但是这两个没有prototype  
console.log(User.prototype.__proto__ == Object.prototype) //true
console.log(User.__proto__.__proto__ == Object.prototype) //true

// User.prototype和(User.__proto__的父级是同一个
console.log(User.prototype.__proto__ == User.__proto__.__proto__)  //true

//函数作为对象使用,调用__proto__上的函数;函数实例化对象,调用prototype上的函数,本级没找到,往上一级查找
user.show()  //show
User.show() //show

User包含prototype和__proto__父级,
prototype和__proto__也是个对象,这个的对象一定也有父级 User.prototype和User.__proto__对象的__proto__都指向Object.prototype

系统构造函数的原型体现

let obj = {} //由构造函数Object创建的实例

let arr = [] //由 new Array()构建的

let str = '' //由 new String()构建的

let reg = /a/i;  // 由 new RegExp()构建的

还有 Boolean  Number等 同理

所以
console.log(obj.__proto__ == Object.prototype) //true
console.log(reg.__proto__ == RegExp.prototype) //true
其他也一样

平常使用的一些定义也是由构造函数创建的

自定义对象的原型设置 (setPropertyOf)

let son = {name:'son'};
let parent = {name:'parent', show(){
    console.log(this.name)
}}

Object.setPrototypeOf(son, parent); //设置原型,这样son没有,parent有,son就可以使用

console.log(son) //得到__proto__是parent的
console.log(son.show()) //son   调用parent的方法,但是此处例子this是执行son的,谁调用,this指向谁

console.log(Object.getPrototypeOf(son));  //查看原型

设置原型:Object.setPrototypeOf(son, parent); //记住 son在前,parent在后

查看原型:Object.getPrototypeOf(son);

原型中的contructor引用

function User(name){
    this.name = name
}
User.prtotype.show = function(){}

原型(prototype)就是个对象,只要是对象就会有原型(Object.prototype),所以

console.log(User.prototype.__proto__ == Object.prototype) //true

通过原型找到构造函数

console.log(User.prototype.constructor == User) //true

所以可以用User.prototype.constructor 创建实例对象

User.prtotype = {
    constructor:User  
    show(){}
}

如果这样添加show(),会丢失constructor,必须加上以下才不会报错,
因为这改变了prtotype,定义成了新对象

let lisi = new User.prototype.constructor();
lisi.show()

User.prototype.constructor的好处:通过原型对象(prototype)找到构造函数来生成无数个新对象

User.prtotype可以定义无数个方法或者属性。

把构造函数作为对象调用,使用__proto__,服务于它自己。prototype通过new出来无数个对象的时候使用,所以prototype功力更强,因为可以加无数个方法和属性。

给我一个对象再生成一个新的对象

通过实例对象可以原型,通过原型的constructor获得构造函数,通过构造函数创建实例对象,所以给我一个对象,再生成一个新的对象。

function User(name){
    this.name = name
}
let user = new User('hannie')
function createByObject(obj,...arg){
    const constructor = Object.getPrototypeOf(obj).constructor
    return new constructor(...arg)
}
let lisi = createByObject(user,'hhy');

以上通过user创建lisi对象

总结一下原型链

let arr = []
console.log(arr.__proto__ )  //这个是非标准找原型方法
 Object.getPrototypeOf(arr) //这个是标准找原型方法
 一个原型,再找另一个原型,这叫做原型链
console.log(arr.__proto__.__proto__ == Object.prototype ) // 往上找
console.log( Object.prototype.__proto__ ) //再往上找不到了

一个原型,再找另一个原型,这叫做原型链。

父级会影响下一级。其实原型链也是个继承的过程,把函数写到父级中,子级可以用。

原型链检测之instanceof

function A(){}
function B(){}
function C(){}
let c = new C();
B.prototype = c;
let b = new B();
A.prototype = b
let a = new A()
console.log(a instanceof A) //true
console.log(a instanceof C) //true
console.log(a instanceof B) //true
console.log(b instanceof A) //false

prototypeObject.isPrototypeOf(object)原型检测

isPrototypeOf() 是 Object 的原型方法(也称实例方法),它定义在 Object.prototype 对象之上,所有 Object 的实例对象都会继承 isPrototypeOf() 方法。

isPrototypeOf() 方法用来检测一个对象是否存在于另一个对象的原型链中,如果存在就返回 true,否则就返回 false。

c.biancheng.net/view/5801.h…

in与hasOwnProperty的属性检测差异

in不仅会检测当前对象,还会检查原型链上的对象

hasOwnProperty仅仅会检测当前对象

let a = {url: '111'}
let b = {name: 'hannie'}
Object.setPrototypeOf(a,b)
console.log('name' in a) // true
console.log(a.hasOwnProperty('name')) // false

使用call或apply借用原型链

max不传参

let obj = {
    data: [1,2,3,34,5,7]
}
Object.setPrototypeOf(obj, {
    max(){
        return this.data.sort((a,b)=>b-a)[0]
    }
})
console.log(obj.max())

let hannie = {
    lessons: {js: 87, php: 63, node:99, linux:88},
    get data() {
        return Object.values(this.lessons);
    }
}
console.log(obj.max.apply(hannie))

max传参

let obj = {
    data: [1,2,3,34,5,7]
}
Object.setPrototypeOf(obj, {
    max(data){
        return data.sort((a,b)=>b-a)[0]
    }
})
console.log(obj.max(obj.data))

let hannie = {
    lessons: {js: 87, php: 63, node:99, linux:88}
}
console.log(obj.max.call(null,Object.values(hannie.lessons);))

以上是借用call和apply调用原型链上得方法max

优化方法借用

求最大值可以有Math.max,所以以上方式可以优化

let obj = {
    data: [1,2,3,34,5,7]
}
console.log(Math.max.apply(null, obj.data))

let hannie = {
    lessons: {js: 87, php: 63, node:99, linux:88}
}
console.log(Math.max.apply(null,Object.values(hannie.lessons);))

总之,如果自己没有此方法,可以求助其他家的方法

DOM节点借用Array原型方法,变成真实数组

let btns = document.querySelectorAll('button')
btns = Array.prototype.filter.call(btns, item => {
//或者 btns = [].filter.call(btns, item => {
    return item.hasAttribute('calss')
})

类似数组调用Array.prototype的方法变成真实数组

合理的构造函数方法声明

function User(name){
    this.name = name;
}
User.prototype.show = function(){
    console.log(this.name)
}
User.prototype.call = function(){
   
}
let lisi = new User('lisi')
let xz = new User('xz')
lisi.show();
xz.show();
function User(name){
    this.name = name;
}
服务于通过构造函数创建实例对象
User.prototype = {
    constructor: User //这个一定要写
    show(){
        console.log(this.name)
    },
    call(){
    }
} 
let lisi = new User('lisi')
let xz = new User('xz')
lisi.show();
xz.show();

最好把show方法放在原型上复用,而不是在函数中定义,这样避免造成额外的内存开销

User.prototype对象中创建方法的使用场景:服务于通过构造函数创建实例对象

this和原型没有关系的

let obj = {
    name: 'xiao'
}
let User = {
    name: 'hhy',
    show() {
        console.log(this.name)
    }
}
Object.setPrototypeOf(obj, User)
obj.show(); // xiao

根据以上实例,this和原型是没有关系

不要滥用原型

以上发现原型功能很大, 但是不能滥用,比如

Object.prototype.hide = function(){
    this.style.display = 'none'
}

this始终指向调用的对象 js会很多用第三方库,如果都这么定义,会覆盖代码,造成代码的互相偶尔,从而导致代码不稳定不健壮,强烈不建议在系统(原生)原型上追加方法

Object.create与__proto__设置对象的原型

__proto__除了设置,还可以获取,但是不是官方的

Object.create只能设置,不能获取

let User = {
    name: 'hhy',
    show() {
        console.log(this.name)
    }
}
let obj = Object.create(User, {
    name: {
        value: 'hannie'
    }
})
obj.show()

let xz = {name: 'xz'};
xz.__proto__ = User  //设置
console.log(xz.__proto__)  //获取

使用setPropertyOf代替__proto__设置对象的原型

__proto__不是官方的,setPropertyOf是官方的

let xz = {name: 'xz'};
Object.setPropertyOf(xz,User)  //设置
console.log( Object.getPrototypeOf(xz))  //获取

__proto__原来是属性访问器

智能判断,是对象就执行,不是对象不执行

let obj = {name: 'hannie'};
obj.__proto__ = {
    show(){
        console.log(this.name)
    }
}
obj.__proto__ = 99; //不是对象不执行
obj.show() // hannie

怎么做到的,其原理?

let obj = {
    action: {},
    get proto(){
        return this.action;
    }
    set proto(obj){
        if(obj instanceof(Object)){
            this.action = obj;
        }
    }
}
obj.proto = 99; //不是对象不执行

可以输出代码看__proto__对象,其中包含get和set

get set这不是严格意义的属性,是getter和setter,会对设置的值自动判断

但是想设置呢,怎么操作? 只要让原型为null let obj = Object.create(null) ,否则必须是对象

let obj = Object.create(null)
obj.__proto__ = 99; //不是对象不执行
console.log(obj.__proto__)   // 99

改变构造函数原型并不是继承

继承是原型的继承,而不是改变构造函数的原型

Son.prototype = Parent.prototype //这不是继承

继承是原型的继承

继承会保留本身的方法

方法一:改变原来原型对象的原型
Son.prototype.__proto__ = Parent.prototype 

新建的方法前后都可以

方法二:新建原型对象
Son.prototype = Object.create(Parent.prototype)   //会改变构造函数,应该加上构造函数

新建的方法必须放在下面

两种方法的比较?

function User(){}
User.prototype.name = function() {
    console.log('user.name')
}

function Admin(){}

Admin.prototype.__proto__ = User.prototype
Admin.prototype.role = function() {
    console.log('admin.role')
}


/*  继承写在方法下面也没关系,依旧是输出admin.role
Admin.prototype.role = function() {
    console.log('admin.role')
}
Admin.prototype.__proto__ = User.prototype
*/

/* 
//使用Object.create(User.prototype) 定义继承,Admin.prototype.role写在继承上面,找不到role(),必须写在继承下面
Admin.prototype.role = function() {
    console.log('admin.role')
}
Admin.prototype = Object.create(User.prototype) 
*/

//这是对的
Admin.prototype = Object.create(User.prototype)
Admin.prototype.role = function() {
    console.log('admin.role')
}


let a = new Admin();
a.role() //admin.role

function Member(){}
Member.prototype.__proto__ = User.prototype
Member.prototype.role = function() {
    console.log('member.role')
}

继承对新增对象的影响,注意Object.create和Son.prototype.__proto__两者实现继承的区别

function Admin(){}
let a = new Admin();  // 立刻实例化,还是指向旧的原型对象
a.role()  //此处报错:找不到role

//创建新的原型对象
Admin.prototype = Object.create(User.prototype)
Admin.prototype.role = function() {
    console.log('admin.role')
}

a.role()  //在继承之前实例化admin,此处也报错:找不到role
function Admin(){}
let a = new Admin();  // 立刻实例化,还是指向旧的原型对象
a.role()  //此处报错:找不到role

//改变原来原型对象的原型
Admin.prototype.__proto__ = User.prototype
Admin.prototype.role = function() {
    console.log('admin.role')
}

a.role()  //此处不报错,  admin.role

继承对constructor属性的影响(Object.create会丢失constructor)

Admin.prototype = Object.create(User.prototype)
Admin.prototype.role = function() {
    console.log('admin.role')
}
console.dir(Admin.prototype.constructor)  //User  这是因为Admin本身没有,但是往父级找有

//所以需要加这句
Admin.prototype.constructor = Admin

禁止constructor被遍历(Son.prototype.constructor = Son这样写会使得constructor可遍历,也就是for in能遍历出来)

其实constructor没必要遍历。

直接Son.prototype.constructor = Son 这么写,用for in循环,constructor会被遍历,所以这样定义constructor

Object.defineProperty(Son.prototype, 'constructor', {
    value: Son,
    enumerable:false
})

Object.create和Son.prototype.__proto__两者实现继承的区别

  1. 原理:Object.create是创建新的原型对象;Son.prototype.__proto__是改变原来原型对象的原型

  2. 定义原型方法的位置的区别

  3. 先实例化导致的后果

  4. Object.create会丢失constructor

  5. Object.create中写Son.prototype.constructor = Son会使得constructor可遍历

例子 看前4部分,便于理解

发现这么多问题,怎么合理实现继承呢?继续看下面,比如原型工厂封装继承等

备注:以下实例中为了快些,就还是用Son.prototype.constructor = Son设置构造函数

子级方法重写与访问父级属性

父类方法不够用,可以在子类重写同样的方法,父子存在相同方法,会调用子类的方法

父类的方法协助子类完成方法调用,可以使用Parent.prototype.site()

function Parent(){}
Parent.prototype.show = function(){console.log('parent name')}
Parent.prototype.site = function(){return 'hannie'}
function Son(){}
Son.prototype = Object.create(Parent.prototype) 
Son.prototype.constructor = Son
Son.prototype.show = function(){
    console.log(Parent.prototype.site() + 'son name') //hannie son name   父类方法协助子类,这么使用
}

let son = new Son()
son.show()

面向对象的多态(使用原型的继承实现)

不同的形态响应出不同的结果

function User(){}
User.prototype.show = function() {
    console.log('user.name')
}

function Admin(){}
Admin.prototype = Object.create(User.prototype) 
Admin.prototype.constructor = Admin
Admin.prototype.description = function(){
    return '管理员在此'
}


function Member(){}
Member.prototype = Object.create(User.prototype) 
Member.prototype.constructor = Member
Member.prototype.description = function(){
    return '我是会员'
}

for(const obj of [new Admin(),new Member()]){
    obj.show()  //都会调用自身的方法,响应不同的结果
}

使用父类构造函数初始属性(Parent.apply(this.args))

不用在每个构造函数中写,统一使用父级中的

function User(name, age){
    this.name = name;
    this.age = age;
}

User.prototype.show = function() {
    console.log(this.name, this.age)
}

function Admin(name, age){
   // User(name. age)  //这样写this指向window
   User.call(this,name, age)
}

//如果参数多,可以用apply传参
/*
   function Admin(...args){
       User.apply(this, args)
    } 
*/

Admin.prototype = Object.create(User.prototype) 
Admin.prototype.constructor = Admin
let hannie = new Admin('hannie', 18)
hannie.show()


function Admin1(name, age){
   User.call(this,name, age)
}
Admin1.prototype = Object.create(User.prototype) 
Admin1.prototype.constructor = Admin1
let hannie1 = new Admin1('hannie', 18)
hannie1.show()

这样不用在买个实例中申明类似于show的函数。

使用原型工厂封装继承

以上实例明显看出步骤多,重复工作多,所以进行封装

//封装继承通用方式
function extend(Sub,Sup){
    Sub.prototype = Object.create(Sup.prototype)
    Object.defineProperty(Sub.prototype, 'constructor', {
        value: Sub,
        enumerable:false
    }) 
}
function Admin(...args){
     User.apply(this, args)
 } 
    
extend(Admin, User) // 调用封装的函数
let hannie = new Admin('hannie', 18)
hannie.show()

对象工厂派生对象并实现继承

通过工厂不断产生对象

//对象工厂
function admin(name, age){
    const instance = Object.create(User.prototype);
    User.call(instance, name, age)
    //可以添加函数
    User.prototype.role = function() {
        console.log('role')
    }
    return instance  
} 
使用的时候不用new,直接创建对象
let hannie = admin('hannie', 19)
hannie.show()

多继承造成的困扰

JS语言没用多继承

需要一个个往上继承,导致很混乱,也导致代码量增加

function Request(){}
Request.prototype.ajax = function() {
    console.log('请求后台')
}

extend(User, Request)

function Credit(){}
Credit.prototype.total = function() {
    console.log('积分统计')
}
extend(User, Credit)

function Admin(...args){
     User.apply(this, args)
 } 
    
extend(Admin, User) // 调用封装的函数

let hannie = new Admin('hannie', 18)
hannie.show()

暴露这个问题,下面给出解决方案

使用mixin实现多继承

定义一些功能的定向,当使用的时候,直接合并到原型中

// mixin混合的方式
const Credit = {
    total(){
        console.log('积分统计')
    }
}
const Request = {
    ajax(){
        console.log('请求后台')
    }
}

function Admin(...args){
     User.apply(this, args)
}     
extend(Admin, User) 

Admin.prototype = Object.assign(Admin.prototype, Request, Credit ) // 这是关键
let hannie = new Admin('hannie', 18)
hannie.show();  // hannie 19
hannie.ajax();  // 积分统计
hannie.total(); // 请求后台

mixin的内部继承与super关键字

super是当前类的原型

是为了合并到类型

const Request = {
    ajax(){
        console.log('请求后台')
    }
}
const Credit = {
    __proto__: Request,  // Credit也可以实现继承Request
    total(){
        //super = this.__proto__   this是当前对象,不是调用对象,也就是不是hannie
        console.log(super.ajax() + '积分统计')
    }
}

...其他同上

以下根据原型知识,写一个应用实例:Tab切换改变颜色

TAB选项卡显示效果基类开发

function Animation(){}
Animation.prototype.hide = function(){
    this.style.display = 'none'
}
Animation.prototype.show = function(){
    this.style.display = 'block'
}
Animation.prototype.background = function(color){
    this.style.backgroundColor = color
}
let tab = document.querySelector('.tab2')
Animation.prototype.background.call(tab, 'red')

好用的TAB业务管理类

function Tab(el){
    this.tab = document.querySelector(el)
    this.links = this.tab.querySelectorAll('a')
}
//继承Animation
extend(Tab, Animation)
Tab.prototype.run = function(){
    this.bindEvent()
}
Tab.prototype.bindEvent = function(){
    this.links.forEach((el,i)=>{
        el.addEventListener('click',()=>{
            this.action(i)
        })
    })
}
Tab.prototype.action = function(){
    this.background.call(this.links[i], 'red')
}
new Tab('.tab2').run()

生成的每个对象是独立的

当然还可以继续扩展功能,得到更多灵活的API

总结

  • prototypeObject.isPrototypeOf(object)原型检测
  • this和原型没有关系的
  • 最好不要在原生对象中扩展方法
  • 获取构造函数的方法
  • 继承是原型的继承,而不是改变构造函数的原型
  • 继承实现方式Object.create 和 Parent.prototype,这2种方式的区别
  • 父类方法不够用,可以在子类重写同样的方法,父子存在相同方法,会调用子类的方法;父类的方法协助子类完成方法调用,可以使用Parent.prototype.site()
  • 面向对象的多态实现(使用原型的继承实现):不同的形态响应出不同的结果
  • 使用父类构造函数初始属性(Parent.apply(this.args))
  • 怎么使用原型工厂封装继承
  • 怎么用对象工厂派生对象并实现继承
  • JS语言没用多继承,但可以用mixin实现多继承(定义一些功能的定向,当使用的时候,直接合并到原型中)
  • mixin的内部继承,super = this.__proto__, 比如__proto__: Request

详细可以往上查找,结合实例理解。

原型强大的功能。结合this,可以得到更完美的解答问题。 可以看我的另一篇变量对象、执行上下文、作用域和this大集合

附上其他: 面不面试的,你都得懂原型和原型链 - 掘金 (juejin.cn)