本文从底层原理一步步剖析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。
复制代码
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__
两者实现继承的区别
-
原理:Object.create是创建新的原型对象;Son.prototype.
__proto__
是改变原来原型对象的原型 -
定义原型方法的位置的区别
-
先实例化导致的后果
-
Object.create会丢失constructor
-
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大集合