1.JS中没有真正的类!与面向类的语言不同,JavaScript没有类作为对象的抽象模型。JavaScript中只有对象,没有真正的类。JS只是利用了函数的一个特殊特性——所有的函数默认都会有一个公共的、不可枚举的属性,叫做prototype,它会指向另一个对象来模拟类的行为。需要注意的是,如果使用内置绑定函数生成硬绑定函数,该函数没有原型属性,目标函数的原型将替换硬绑定函数的原型。在这样的函数上使用instanceof或new等同于直接在目标函数上使用。functionAnimal(){};console.log(Animal.prototype);//{}在JS中,newAnimal()看起来像是实例化了Animal类,但实际上并没有。consta=newAnimal();console.log(Object.getPrototypeOf(a)===Animal.prototype);//true在面向类的语言中,类可以被复制(或实例化)多次。实例化类意味着“将类的行为复制到物理对象中”,并且为每个新实例重复此过程。但是在JS中,并没有类似的复制机制。您不能创建一个类的多个实例,只能创建其“原型”与同一对象相关联的多个对象。但是默认情况下没有复制,所以这些对象并没有完全丢失,它们是相互关联的。newAnimal()将生成一个新对象(我们称它为a),并且这个新对象的内部链接“原型”与Animal.prototype对象相关联。我们没有初始化一个类,我们实际上没有将任何行为从“类”复制到一个对象中,我们只是让两个对象相互关联。2.JS中的构造函数是什么?功能动物(){};consta=newAnimal();JS中没有真正的类,但是看到这两行代码,还是觉得Animal是一个类。为什么是这样?在我看来,原因之一是new运算符的存在,这在面向类的语言中是必需的。另一个原因是在newAnimal()中,Animal的调用方式与实例化类时调用类构造函数的方式非常相似。但实际上,Animal与您程序中的任何其他函数没有什么不同。函数本身不是构造函数,但是当我们在一个普通函数调用前加上new关键字时,它会将这个函数调用变成“构造函数调用”(new会劫持所有普通函数,以call的形式构造对象它)。简单地说,在JS中,“构造函数”可以解释为使用new运算符调用的函数。但是,我们需要知道的是,JS中的函数并不是构造函数,只有在使用new的时候,函数调用才会变成构造函数调用。3、“面向类”的functionAnimal(name){this.name=name;}Animal.prototype.sayName=function(){console.log(this.name);};constdog=newAnimal('dog');constcat=newAnimal('cat');dog.sayName();//狗猫.sayName();//catconsole.log(dog.constructor);//[Function:Animal]this.name=name通过this的隐式绑定为每个对象添加name属性,有点像类实例封装的数据值。Animal.prototype.sayName=...将向Animal.prototype对象添加一个属性(函数)。在创建过程中,在dog和cat中),这个新对象的内部链接“原型”将与Animal.prototype相关联。当在dog和cat中找不到sayName时,会在Animal.prototype中查找。需要注意的是,dog.constructor指向的是Animal函数,所以dog的constructor属性好像代表了狗是谁构造的。但实际上,这只是它看起来的样子,因为dog本身并没有constructor属性。constructor属性和sayName一样,也是Animal.prototype的一个属性。此属性与狗(或猫)之间没有任何联系。对于Animal.prototype,构造函数只是Animal函数在声明时生成的一个默认属性(不可枚举,但可以更改),Animal.prototype.constructor='animal';console.log(dog.构造函数);//'animal'当改变指向Animal.prototype的指针时,指向constructor属性的指针也变得混乱。这是因为fish上没有constructor属性,所以搜索的是Animal.prototype(即{})上的constructor属性,但是{}也没有constructor属性,所以会继续搜索Object.prototype。该对象具有指向内置对象函数的构造函数属性。Animal.prototype={};constfish=newAnimal();console.log(fish.constructor);//[Function:Object]当然我们可以手动指定constructor属性。Animal.prototype={};Object.defineProperty(Animal.prototype,'constructor',{enumerable:false,//不可枚举writable:true,configurable:true,value:Animal,//让构造函数指向Animal});constfish=newAnimal();console.log(fish.constructor);//[Function:Animal]简而言之,constructor属性只是一个可能被改变的普通属性,dog.constructor引用是不可靠的。4.JS中原型样式继承的继承:functionAnimal(name){this.name=name;}Animal.prototype.sayName=function(){console.log(this.name);};functionDog(name,color){Animal.call(this,名字);this.color=color;}//创建一个新的Dog.prototype对象并将其与Animal.prototypeDog.prototype=Object.create(Animal.prototype);//Object.setPrototypeOf(Dog.prototype,Animal.prototype)//笔记!现在Dog.prototype.constructor的指向已更改为AnimalDog.prototype.sayName=function(){console.log('rewritesayName');//显式多态,调用Animal.prototype.sayNameAnimal.prototype.sayName.call(this);};constteddy=newDog('Teddy','brown');teddy.sayName();//重写sayName当Teddy声明Dog时,和所有函数一样,Dog会有一个原型属性指向默认对象(假设对象名为originObj),但originObj并不是我们想要的Foo.prototype。所以我们创建一个新对象并将这个新对象与Foo.prototype相关联,丢弃默认对象originObj。上面的代码是通过Object.create实现的。当然也可以通过ES6的Object.setPrototypeOf实现,Object.setPrototypeOf(Dog.prototype,Animal.prototype),这个功能是修改originObj而不是放弃originObj,因此通过Object.setPrototypeOf,Dog.prototype.constructor指向Nothinghaschanged。我们可以使用instanceof来检查泰迪和狗或动物之间的关系。console.log(泰迪动物实例);instanceof左边是一个普通对象a??,右边是一个函数B。这个操作符检查B.prototype是否存在于a的“原型”链上。如果你想检查两个普通对象之间的关系,你可以使用isPrototypeOfconsole.log(Animal.prototype.isPrototypeOf(teddy));5.ES6中的class语法你??可能认为JS引入了ES6的class语法,创造了一种新的“类”机制,其实不然。类基本上只是“原型”机制的语法糖。类动物{构造函数(名称){this.name=name;}sayName(){console.log(this.name);}}classDogextendsAnimal{constructor(name,color){super(name);=颜色;}sayName(){console.log('重写sayName');//相对多态的super.sayName();}}constteddy=newDog('Teddy','brown');泰迪熊。说名字();除了更好的语法,ES6还解决了哪些问题?不再有混乱的原型参考。Dog在声明时直接“继承”了Animal,不需要通过Object.create替换原型对象,也不需要设置__proto__或Object.setPrototypeOf。通过super可以实现相对多态,使得任何方法都可以引用原型链上层的同名方法。不需要使用显式多态写法,Animal.prototype.sayName.call(this);类字面量语法不能声明属性(只能声明方法)。似乎是一个限制,但它会排除很多糟糕的情况,在这些情况下,原型链末尾的“实例”可能会意外地在其他地方获得属性(所有“实例”隐含地“共享”这些属性)“)。所以,类语法实际上可以帮助您避免犯错。对象(子)类型,甚至内置对象(子)类型,如Array或RegExp,都可以通过extends很自然地扩展。如果没有类,这是很难实现的。extendssyntax.需要注意的是,与此不同的是,super不是动态绑定的,下面testObj.sayName中的super并不是指向它当前的“原型”对象testParen,而是指向Animal.consttestObj={name:'测试',sayName:Dog.prototype.sayName,};consttestParen={sayName(){console.log('testParen');},};Object.setPrototypeOf(testObj,testParen);testObj.sayName();//重写sayName测试
