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

Js基础知识(二)——原型链与继承精彩讲解

时间:2023-04-02 16:54:30 HTML

作用域、原型链、继承与闭包详解注:本章讲的是es6之前的原型链与继承。ES6引入了类的概念,只是写法不同,但原理是一样的。面试中经常被问到的几个问题,你知道instanceof的原理吗?如何准确判断一个变量的类型?如何写一个原型链继承的例子?小儿科,如果你不知道这些问题的答案或者背后的知识点,那么仔细阅读下面的内容,一定会对你有所帮助。先不说答案,先分析一下涉及到的知识点。什么是构造函数?JavaScript没有类的概念。JavaScript是一种基于对象的语言。除了五种中值类型(numberbooleanstringnullundefined),其他三种引用类型(object,Array,Function)本质上都是一个对象,构造函数其实就是一个普通的函数,但是构造函数可以用来实例化对象。事实上,当任何普通函数被用来创建一个对象类时,它就被称为构造函数。js中的Object、Array、Date等内置函数都是构造函数。构造函数的定义有以下特点:构造函数的定义以大写字母开头,在函数内部设置新对象(this)的属性。返回值必须是this,或者其他非对象类型的值。下面定义了一个简单的标准构造函数:functionObj(){this.name='name'returnthis//Thisline默认有这一行}varfoo=newObj()//使用上面定义的构造函数创建一个objectinstanceprototypefeaturejsprototype有5个记住这5个特性,相信你就会明白困扰你很久的原型关系了。除了null,所有引用类型(Object、Array、Function)都具有对象特性,即可以自由扩展属性。所有引用类型都有一个_proto_属性(又名:隐式属性),而_proto_是一个普通对象。所有的对象都会有一个constructor属性,constructor总是指向创建当前对象的constructor所有的函数都有一个prototype属性(又名:explicit属性),也就是一个普通的对象,这个prototype有一个constructor属性指向函数。从对象中获取时,所有引用类型的_proto_属性都指向其构造函数的prototype属性(例如:obj._proto_指向Object.prototype,obj是定义的普通对象,Object是js的内置函数)对于某个属性,如果对象没有这个属性,就会去它的_proto_(也就是它的构造函数的原型)中去寻找。举个简单的例子,varobj={}obj.name='name'console.log(obj)//{name:'name'}第二项和第三项是javascript规定的,没什么好说的四项可以这样理解。在定义一个引用类型的变量时,varobj={}其实就是varobj=newObject()的语法糖,这样Object就是obj的构造函数。根据第4条,obj._proto_===Object.prototype,不明白的可以看看我们上一章讲的js内置函数和上面说的构造函数。第5条应该很容易理解。从obj中获取一个属性时,如果obj中没有定义该属性,会一步一步去它的_proto_对象中查找,它的_proto_指向Object的原型,即从原型对象中查找的对象。原型链和继承如果你理解了上面的原型,那么原型链就很好理解了。根据原型定义的第4项和第5项,不难发现,通过对象的_proto_和函数的原型,我们的变量和构造函数(Customconstructorsandbuilt-inconstructors)像链条一样链接在一起,所以也叫原型链。有了原型链,就有了继承,这意味着一个对象从它的构造函数中继承了对某些属性的访问,就像继承一样。从下面的小例子中理解:functionAnimal(name){//attributethis.name=name||'动物';//实例方法this.sleep=function(){console.log(this.name+'issleeping!');}}//原型方法Animal.prototype.eat=function(food){console.log(this.name+'eating:'+food);};//原型继承函数Cat(){}Cat.prototype=new动物();Cat.prototype.name='猫';在上面的例子中,在Foo构造函数的原型中自定义了一个somefn函数。然后通过newFoo()创建一个对象实例,赋值给bar变量。此时bar等于{name:'bar'}。然后bar.somefn去bar对象中寻找属性somefn,发现找不到,又在它的_proto_(其实是Foo的原型)中找,发现somefn定义在的原型中Foo,所以你可以很高兴再次调用并执行somefn。这其实就是一个典型的原型链和继承的例子。在开发的时候,构造函数可能会更复杂,属性可能会定义更多,但是原理是一样的。留个问题,按照上面的例子,如果执行bar.stString(),应该去哪里找toString方法呢?(提示:prototype也是普通对象,有自己的_proto_)几种继承方式这些在es5中都是继承,es6中提供了class类,继承更方便。原型继承上面的例子是一个原型继承:functionAnimal(name){//propertythis.name=name||'动物';//实例方法this.sleep=function(){console.log(this.name+'Sleeping!');}}//原型方法Animal.prototype.eat=function(food){console.log(this.name+'iseating:'+food);};//原型继承函数Cat(){}Cat.prototype=newAnimal();Cat.prototype.name='cat';varcat=newCat()console.log(catinstanceofAnimal);//真console.log(catinstanceofCat);//true优点:很纯粹的继承关系,实例是子类的实例,也是父类的实例。,不能放在构造函数中,不能实现多重继承来自原型对象的引用属性为所有实例共享(严重缺点)创建子类实例时,不能向父类构造函数传递参数(严重缺点)构造继承函数Animal(name){//属性this.name=name||'动物';//实例方法this.sleep=function(){console.log(this.name+'Sleeping!');}}//原型方法Animal.prototype.eat=function(food){console.log(this.name+'eating:'+food);};//构造继承函数Cat(name){Animal.call(this);this.name=名称||'Tom';}//测试Codevarcat=newCat();console.log(cat.name);console.log(cat.sleep());//汤姆是睡眠!//console.log(cat.eat('fish'));//cat.eat不是函数console.log(catinstanceofAnimal);//falseconsole.log(catinstanceofCat);//真正的优势解决了1、子类实例共享父类的引用属性。创建子类实例时,可以向父类传递参数,实现多重继承。缺点实例不是父类的实例,而是子类的实例只能继承父类实例的属性和方法,不能继承原型属性/方法不能实现功能重用,每个子类都有一份父类实例函数,这会影响性能InstanceinheritancefunctionAnimal(name){//attributethis.name=name||'动物';//实例方法this.sleep=function(){console.log(this.name+'Sleeping!');}}//原型方法Animal.prototype.eat=function(food){console.log(this.name+'eating:'+food);};//实例继承函数Cat(name){varinstance=newAnimal();instance.name=名称||'汤姆';返回实例;}varcat=newCat();//或者你可以直接varcat=Cat()console.log(cat.name);console.log(cat.sleep());//汤姆在睡觉!console.log(cat.eat('fish'));//Tomiseating:fishconsole.log(catinstanceofAnimal);//trueconsole.log(catinstanceofCat);//false的优点是不限制调用方式,无论是newCat()还是Cat(),返回的对象都是一样的效果。缺点实例是父类的实例,不是子类的实例不支持多重继承和组合继承functionAnimal(name){//attributethis.name=name||'动物';//实例方法this.sleep=function(){console.log(this.name+'Sleeping!');}}//原型方法Animal.prototype.eat=function(food){console.log(this.name+'iseating:'+food);};//组合继承函数Cat(name){Animal.call(这);this.name=名称||'汤姆';}猫。prototype=newAnimal();Cat.prototype.constructor=Cat;varcat=newCat();console.log(cat.name);console.log(cat.sleep());//汤姆在睡觉!console.log(cat.eat('fish'));//Tomiseating:fishconsole.log(catinstanceofAnimal);//trueconsole.log(catinstanceofCat);//真正的优点弥补了方法2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法。它既是子类的实例,又是父类的实例。共享引用属性没有问题。函数可以作为参数传递,函数可以被重用。创建两个实例(子类实例屏蔽了子类原型上的实例)寄生继承varob={name:"Xiaoming",friends:['Xiaohua','Xiaobai']};functionobject(o){functionF(){}//创建一个构造函数FF.prototype=o;returnnewF();}//上面ECMAScript5中有一个新的规范,Object.create(ob)具有相同的效果functioncreateOb(o){varnewob=object(o);//创建对象newob.sayname=function(){//增强对象console.log(this.name);}returnnewob;//指定对象}varob1=createOb(ob);ob1.sayname()寄生继承的原理还没明白寄生组合继承寄生组合继承有两种方式:第一种:使用函数Animal(name)创建一个没有实例方法的函数functionAnimal(name){//attributethis.name=name||'动物';//实例方法this.sleep=function(){console.log(this.name+'Sleeping!');}}//原型方法Animal.prototype.eat=function(food){console.log(this.name+'eating:'+food);};//寄生组合继承函数Cat(name){Animal.call(这);this.name=名称||'Tom';}(function(){//创建一个没有实例方法的类varSuper=function(){};Super.prototype=Animal.prototype;//PrototypewithinstanceassubclassCat.prototype=newSuper();Cat.prototype.constructor=Cat;})();varcat=newCat();console.log(cat.name);console.log(cat.sleep());//汤姆在睡觉!console.log(cat.eat('fish'));//Tomiseating:fishconsole.log(catinstanceofAnimal);//trueconsole.log(catinstanceofCat);//true第二:使用对象。createfunction//寄生继承核心方法functioninheritPrototype(Parent,Children){v??arprototype=Object.create(Parent.prototype);prototype.constructor=孩子;Children.prototype=prototype;}//父类函数Animal(name){//属性this.name=名称||'动物';//实例方法this.sleep=function(){console.log(this.name+'Sleeping!');}}//原型方法Animal.prototype.eat=function(food){console.log(this.name+'eating:'+food);};//子类函数Cat(name){Animal.call(this);this.name=名称||'汤姆';}inheritPrototype(Animal,Cat)varcat=newCat();console.log(cat.name);console.log(cat.sleep());//汤姆在睡觉!console.log(cat.eat('fish'));//Tomiseating:fishconsole.log(catinstanceofAnimal);//trueconsole.log(catinstanceofCat);//trueObject.create其实和下面代码一样Pricefunctionobject(o){functionf(){}f.prototype=o;returnnewf();}优点完美的继承解决方案缺点实现复杂明白了链式和继承的原理,再回头看上面的问题,你也会发现这是小儿科的第一题:instanceof的原理?vararr=[]arrinstanceofArrayinstanceof的原理是利用原型链。执行arrinstanceofArray时,会从arr的_proto_开始逐层查找,看能否找到Array的原型。我们知道vararr=[]其实就是vararr=newArray()的语法糖,所以arr的_proto_指向Array的原型,结果返回true。第二个问题:如何准确判断变量类型?可以用instanceof来帮我们判断,不是typeof第三个问题:原型链继承的例子怎么写?functionFoo(){this.name='name'this.run=function(){console.log(this.name)}}functionBar(){}Bar.prototype=newFoo()//来自构造函数FooInheritancevarbaz=newBar()baz.run()//打印出'name'第四个问题:描述new一个对象的过程创建一个新对象,获取构造函数的prototype属性,并将原型赋值给_proto_新对象的this指向新对象执行构造函数,并返回构造函数的内容