当前位置: 首页 > Web前端 > JavaScript

JS继承详解(三)——图解Es6的Extend

时间:2023-03-27 17:00:13 JavaScript

前言距离上一期js继承系列已经四年了,不时有新的读者评论和回复。很开心,想更新一下内容,因为当时的内容没有涉及到es6的扩展实现,所以现在抽空补上。当然,如果你是零基础的同学,或者一些基础继承忘记的同学,可以先复习一下前两篇文章:js中继承详解(一)js中继承详解(二)回顾基础课文&预备知识为了让后面的学习过程更加顺畅。在开始之前,我们先一起回顾一下构造函数-原型对象-实例模型:在访问a的属性时,它会先从a自身的属性(或方法)中寻找。如果找不到的话,会沿着__proto__属性找到原型对象A.prototype,在原型对象上找到对应的属性(或方法);如果再找不到,就继续沿着原型对象的__proto__查找。原型链内容介绍。函数A(){this.type='A'}consta=newA();当然图上的原型链还可以继续找,我们知道A虽然是一个函数,但本质上也是一个Object,沿着__proto__属性向上追踪,最终会返回null;a.__proto__===A。原型;//truea.__proto__.__proto__===Object.prototype;//truea.__proto__.__proto__.__proto__===null;//trueextend实现源码下面进入解析的重点。学过es6的同学都知道,继承可以直接通过关键字extend实现,例如://先创建一个Animal类classAnimal{name:string;constructor(theName:string){this.name=theName;};move(distanceInMeters:number=0){console.log(`动物移动${distanceInMeters}m.`);}}//子类Dog继承自AnimalclassDogextendsAnimal{age:number;构造函数(名称:字符串,年龄:数字){超级(名称);这个。年龄=年龄;}bark(){console.log('汪!汪!');}}constdog=newDog('wangwang',12);dog.bark();//'汪!Woof!'dog.move(10);//`Animalmoved10m.`那么这个扩展到底做了什么?这里我们安装npm包typescript并在本地运行tsc[文件路径],将ts和es6代码转换成原生js代码进行研究,(当然也有一个缺点,转换后的代码有时可能会影响可读性,以求追求代码简单比如undefined写成void0),上面的代码转换后是这样的://第一部分var__extends=(this&&this.__extends)||(function(){varextendStatics=function(d,b){extendStatics=Object.setPrototypeOf||({__proto__:[]}instanceofArray&&function(d,b){d.__proto__=b;})||函数(d,b){for(varpinb)if(Object.prototype.hasOwnProperty.call(b,p))d[p]=b[p];};returnextendStatics(d,b);};returnfunction(d,b){if(typeofb!=="function"&&b!==null)thrownewTypeError("Classextendsvalue"+String(b)+"isnotaconstructorornull");}extendStatics(d,b);函数__(){this.constructor=d;}d.prototype=b===null?Object.create(b):(__.prototype=b.prototype,new__());};})();//第二部分//首先创建一个Animal类varAnimal=/**@class*/(function(){functionAnimal(theName){this.name=theName;};Animal.prototype.move=function(distanceInMeters){if(distanceInMeters===void0){distanceInMeters=0;}console.log("动物移动".concat(distanceInMeters,"m."));};returnAnimal;}());//第三部分//Dog的子类继承自AnimalvarDog=/**@class*/(function(_super){__extends(Dog,_super);functionDog(name,age){var_this=_super.call(this,name)||this;_this.age=age;return_this;}Dog.prototype.bark=function(){console.log('Woof!Woof!');};Dog.prototype.move=function(distanceInMeters){if(distanceInMeters===void0){distanceInMeters=5;}console.log("Dogmoved".concat(distanceInMeters,"m."));};returnDog;}(Animal));//第四部分不需要解析vardog=newDog('wangwang',12);dog.bark();//'哇!Woof!'dog.move(10);//狗移动了10m。代码看起来有点复杂。下面我们根据代码注释从简单到复杂分析一下每个部分的复杂度:先看第二部分。首先,它用匿名立即执行函数(IIFE)包装。关于这一点,我们在讲闭包的时候说过,这样写的好处是避免污染全局命名空间;然后在内部,它是第一个文中提到的构造函数——原型对象的经典模型——属性放在构造函数中,方法绑定在原型对象上,所以这部分其实是原生js写方法对应es6的Class;第三部分,Dog类写法和第二部分大致相同,但还是有几点不同:_super.call(this,name),_super代表父类,所以这一步是使用构造函数父类生成一个对象,然后使用自己的构造函数,修改对象;__extends方法也是本文的核心内容。最后介绍第一部分,就是__extends的具体实现。这部分的外层也是简单的避免重复定义和匿名立即执行函数(IIFE),这里不再详述。核心内容是extendStatics的实现:首先介绍Object.setPrototypeOf方法,用于为一个对象重新指定原型。用法如下:Object.setPrototypeOf(d,b)//等价于d.__proto__=b;在每个后续||之后这里有一个稍微更易读的写法:returnfunction(d,b){//当b不是构造函数或null时,抛出错误if(typeofb!=="function"&&b!==null){thrownewTypeError("Classextendsvalue"+String(b)+"isnotaconstructorornull");}//修改d原型链指向extendStatics(d,b);//模拟原型链的继承函数Temp(){this.constructor=d;}if(b===null){d.prototype={};//Object.create(null)返回一个新的空对象{}}else{Temp.原型=b.原型;vartemp=newTemp();d.原型=温度;}};这里第一个`if`比较好理解,不多解释;接下来还引入了extendStatics(d,b)效果为d.__proto__=b;然后它更有趣。为了方便大家理解,先画一个相关关系图:首先,d和b是独立的(当然)这里请注意!!!,我们用大写字母B和D来表示b和d的构造函数,b和d本身也可能是一个函数,它们也有自己对应的原型对象,只是图中没有标注。(眼力不好或者不太细心的同学一定要认真,不然容易理解出错。)比如上一篇文章中的Animal对应图片上的b,那么B对应的是Function,即,Animal.__proto__=Function.prototype,但与同时Animal也有自己的原型对象Animal.protptype:执行extendStatics(d,b)后原型关系如下(D的构造函数和原型对象变得不可访问,所以它们用灰色表示):然后执行下面的代码:functionTemp(){this.constructor=d;}Temp.prototype=b.prototype;vartemp=newTemp();d.原型=温度;结构图如下:从图中可以看出,这个临时变量temp最终成为了d的原型对象,也是b的一个实例。这其实和我们前面学习的原型链继承类似,不同的是多了一个d.__proto__=b。然后,如果执行vardog=newDog('wangwang',12);其实这里的Dog就是对应上图中的d,dog的原型链其实就是dog.__proto__===temp,然后就是b.prototype,自然可以调用b.prototype中定义的方法.自测部分,完成extend后,回答几个问题来测试你的理解。Q1:首先,属性是如何继承的,和ES5有什么区别?A1:extend是通过调用父类的方法创建一个初始对象,然后根据子类的构造函数调整对象;ES5继承(组合继承)本质上是先创建子类的实例对象this,然后使用call或apply将父类的属性添加到this中。Q2:狗是怎么调用move方法的?A2:这个问题其实就是前面刚刚分析的原型链模型,方法的查找顺序是:dog.move(不存在)>dog.__proto__(临时变量).move(不存在)>dog.__proto__。__proto__.move(Found)Q3:多余的d.__proto__=b的作用是什么?A3:可以继承父类的静态方法,比如添加方法:Animail.sayHello=function(){console.log('hello')};,那么Dog.sayHello()也会生效,可以参考上图理解,找到Sequence:d.hello(不存在)>d.__proto__.hello(找到)总结本文为后续继承系列的文章,主要是对ES6中Extend的简单源码分析和原理介绍,最重要的是原型链部分的图解,希望对读者有所帮助。欢迎大家关注专栏,希望大家对喜欢的文章不吝点赞收藏。如果大家对文笔的风格和内容有什么意见,欢迎私信交流。(想来外企的小伙伴欢迎私信或在首页添加联系方式了解详情~)