摘要:引言上篇文章介绍原型,这篇文章接着讲继承,呕心沥血之作,大哥们点个赞呀明确一点并不是真正的面向对象语言,没有真正的类,所以我们也没有类继承实现继承有且仅有两种方式,和原型链在介绍继承前我们先介绍下其他概念函数的三种角色一个函数,有三种角色。
引言
上篇文章介绍原型,这篇文章接着讲继承,呕心沥血之作,大哥们点个赞呀
明确一点:JavaScript并不是真正的面向对象语言,没有真正的类,所以我们也没有类继承
实现继承==有且仅有两种方式,call和原型链==
在介绍继承前我们先介绍下其他概念
函数的三种角色一个函数,有三种角色。
当成普通函数,当成构造函数(类),当成对象
function Person (nickname) { var age = 15 //当普通函数使 **重点:称为私有属性** this.age = 30 //当构造函数使 **重点:称为实例属性** } Person.prototype.age = 50 //当构造函数使 **重点:称为原型属性** Person.age =100 //当对象使 **重点:称为静态属性**
==我个人把属性和方法统一称为广义上的属性,所以上面说法其实不严谨==
大家猜一猜Array.isArray是静态方法还是原型方法,为啥?Array.push呢?
继承的方式继承原则:
使用call继承实例上的属性
使用原型链继承原型上的属性
子类需要有自己的原型,父类也必须要有自己的原型,子实例在自己的原型上找不到属性的时候才会到父原型上去找(这也就是子类.prototype = 父类.prototype不行的原因,因为它们中间没有==“缓冲”==,改了子类原型相当于就改了父类原型!)
const Person = function (name) { this.name = name } Person.prototype.introduce = function(){ Object.entries(this).forEach((item)=>{ console.log(`my ${item[0]} is ${item[1]}`) }) } const Student = function (name,age) { Person.call(this,name) this.age = age } Student.prototype = new Person() //这里new了父类一次,增加了额外开销 Student.prototype.constructor = Student //这一句可以让student.constructor.name由Person变为Student 方便确认构造函数 let student = new Student("小明",15) student.introduce() 继承父类原型方法的同时继承父类实例上的属性 //my name is 小明 //my age is 15
组合继承有一个缺点,会额外new父类一次,增加了额外开销(想一想如果父类特别大这消耗会有多大)
Object.create继承(原型式继承)从名字也看出来了,借用原型链继承,实例能通过__proto__追踪到传入到参数obj,所以源码如下
//Oject.create(obj)
Object.create = function(obj){
var fn = funcion(){} fn.prototype = obj reurturn new fn()
}
var A = Object.create(B) //A能找到B
=再次强调一遍,A通过原型链最终到B=
我们再看一下组合继承的原型链 Student-->Person的实例-->Person
还记得我们在最开始继承原则中说的缓冲吗,Person的实例就是这么一个缓冲 但是缺点就是构造这么一个缓冲开销大了
所以我们有一个优化手段
Student.prototype = new Person() //未优化的时候 Person实例充当原型链的中间对象(缓冲) ------------------------- Student.prototype = Object.create(Person.prototype) //优化后 一个继承Person的空对象充当中间对象(缓冲) ------------------------ Student.prototype.__proto__ = Person.prototype //当然也有人这么写 道理都是一样,Student.prototype.__proto__做缓冲new干了啥
既然new在“类”的创建里面必须使用,那么我们就说一下new到底干了啥事情
1.创建一个对象o继承构造函数
2.让构造函数的this变为o,并执行构造函数,将返回值设置为k
3.如果k
//仿写new function new1(func) { var o = Object.create(func.prototype) var k = func.apply(o,arguments[1]) return typeof k === "object"? k: o } const x = new1(Student,["张三"]) x.name //"张三" x.eat //"i am hungry,i want to eat!"
我们回过头再分析一下构造函数模式继承
const Person = function (name) { this.name = name } const Students = function (name) { Person.call(this,name) //this是student实例 } const xm = new Students("小明") //分析这里干了什么 console.log(xm) //Students {name: "小明"}
1.让空对象o继承Students(o能访问Students的原型)
2.student执行,执行Person的代码,this是o,并且传入name, o.name="小明"返回的k是undefined
3.返回o,也就是返回{name:"小明"}
class Person { } class Student extends person{ }
在babel es2015-loose模式下编译后的源码如下
"use strict"; function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } var Person = function Person() { }; var Student = /*#__PURE__*/ function (_person) { _inheritsLoose(Student, _person); function Student() { return _person.apply(this, arguments) || this; } return Student; }(person);
严格模式下,高级单例模式返回一个Student, 可以看到Person的实例属性用的Person的构造函数+apply继承的
原型属性用的_inheritsLoose这个方法继承的
_inheritsLoose方法貌似就是我们之前说的寄生组合继承
我们知道vue里面的数组有变异方法,变异方法有啥功能呢,就拿push来说,一方面数组会变,另外一方面有响应式(假设触发render方法)
思路:APO编程思想
数组之所以有push方法,是因为Array.prototype上有push方法
我们需要实现自己的push方法,挂在Array.prototype上对原型链追踪进行拦截,但是呢又不能改变原型链上对非变异方法
原型链示意:
Vue里面添加过监控的数组实例--->我们自己实现的变异方法-->原来的Array.prototype
const arrList = ["push","pop","shift","unshfit","reverse","sort","splice"] const render = ()=>{console.log("响应式,渲染视图")} const proto = Object.create(Array) arrList.forEach((method)=>{ proto[method] = function(){ render() Array.prototype[method].call(this,...arguments) } }) var data = [1,2,3] data.__proto__ = proto //mvvm响应式原理,如果添加响应式的目标是数组,我就执行这个操作 data.push(4) // 响应式,渲染视图,(data[1,2,3,4])总结
本节详细介绍了继承的原理以及优化,对于es6的继承语法糖也做了剖析。同时介绍了一下mvvm下数组借用继承实现响应式的用法,由于本人水平有限,如果有什么不对的地方,欢迎留言指出。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/109760.html
摘要:同理,原型链也是实现继承的主要方式的只是语法糖。原型对象也可能拥有原型,并从中继承方法和属性,一层一层以此类推。利用构造函数小明张三张三小明缺点每次实例化都需要复制一遍函数到实例里面。寄生构造函数模式只有被类出来的才能用。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 最近又攀登了一下JS三座大山中的第二...
摘要:核心知识点梳理数据篇看了一些资料,结合高程和对核心知识点进行了梳理。所以,一共有种声明变量的方法。凡是在声明之前就使用这些变量,就会报错。还是那句话,建议大家掌握核心知识点,细枝末节的东西就随意啦。 JS核心知识点梳理——数据篇 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 看了一些资料,结合ES6、高程和MD...
摘要:引言满满的干货,面试必系列,参考大量资料,并集合自己的理解以及相关的面试题,对核心知识点中的作用域闭包上下文进行了梳理。本篇重点介绍闭包和。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 满满的干货,面试必bei系列,参考大量资料,并集...
摘要:而且在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。在主要考虑对象而不是自定义类型和构造函数的情况下,这个模式也不错。 写在前面 注:这个系列是本人对js知识的一些梳理,其中不少内容来自书籍:Javascript高级程序设计第三版和JavaScript权威指南第六版,感谢它们的作者和译者。有发现什么问题的,欢迎留言指出。 1.原型链 将原型链作...
摘要:英文原文中本来是,而翻译成第一类公民其实就是一种比喻。所以,通过上述的结果,我们发现在中不管我们是用构造函数创建的对象还是用本身提供的数据类型创建的对象都源自于。使用可以解除函数体内代码和函数名的耦合状态。 作为一个Jser,不光要会用js,还要明白它的运行原理,不然就会一直停留在表面。 函数在JavaScript中被称作第一等公民,这个第一等公民是什么鬼?看看知乎上是怎么回答的。就像...
阅读 1761·2021-11-24 09:39
阅读 1550·2021-11-16 11:54
阅读 3496·2021-11-11 16:55
阅读 1653·2021-10-14 09:43
阅读 1444·2019-08-30 15:55
阅读 1232·2019-08-30 15:54
阅读 3419·2019-08-30 15:53
阅读 1337·2019-08-30 14:18