对于前端朋友来说,无论是新手还是老鸟,我觉得prototype应该都折腾过,总感觉云里雾里。原型不懂,还好意思说自己是前端攻城狮?关于对象说到面向对象(Object-OrientedOO),你的第一反应肯定是想到类、对象、接口实现等概念。那我们为什么要在这里谈论对象呢?因为ECMAScript中没有类,并且因为ECMAScript中的函数没有签名,所以也没有接口。对象在ECMAScript-262中被定义为:“无序属性的集合,其属性可以是原始值、对象或函数”。因此,从数据结构的角度来看,对象可以看作是一个哈希表(HashTable)。对象分类对象从创建方式上可以分为三类:内置对象、宿主对象和自定义对象。单击此处了解有关对象分类的详细信息。特别需要强调的是,除了number、string、boolean、null、undefined、symbol这六种基本类型外,其他都是对象(引用类型),包括函数。所有函数都是对象,反之亦然。对象与函数的关系对象的创建前面说过,ECMAScript中没有类,那么如何创建对象呢?对象字面量//方法一:对象字面量varzhangsan={type:"Human",name:"张三",age:18,greeting:function(){console.log(`helloI'm${this.name}`);}};zhangsan.greeting();//"helloI'mZhangSan"这个方法有几个问题:创建多个变量时,要写很多重复的代码;每个实例都会持有一个问候功能,但实际上功能是一样的,没有复用,浪费资源;创建“human”(type="human")的所有实例,type的值是一样的,但是每个实例仍然持有一个独立的副本;创建实例无法识别类型(也就是说不知道创建实例的具体类型,只能知道是Object的实例)。工厂模式//方法二:工厂模式functioncreatePerson(name,age){varp=newObject();p.type="人类";p.name=名称;p.age=年龄;p.greeting=问候;returnp;}varlisi=createPerson("Lisi",20);lisi.greeting();//"helloI'mLisi"functiongreeting(){console.log(`helloI'm${this.name}`);}第二个方法虽然做了封装,但是在创建的时候避免了很多重复的代码,并且还通过将greeting提取到全局作用域解决了多个实例持有多份greeting的问题,但同时也引入了只有该类型的实例才会引用全局空间的功能,污染了全局空间;最后,它也解决了物体识别的问题。//方法三:构造函数Person(name,age){this.type="Human";this.name=名称;这个。年龄=年龄;this.greeting=greeting;}varwangwu=newPerson("王五",24);//wangwuinstanceofPerson===truewangwu.greeting();//"helloI'mWangWu"functiongreeting(){console.log(`helloI'm${this.name}`);}这个方法几近完美,解决了物体识别问题,但是仍然没有解决共享函数污染全局空间的问题;为了解决这个问题,请拿出我们的主角原型(prototype)。原型和原型链终于切入正题。要解决以上三种方法所面临的问题,必须有一个对象是构造函数独享的(不需要定义它会污染全局空间),并且可以被构造函数创建的所有对象实例共享。.这个对象就是原型(或原型对象)。什么是原型(prototype)默认情况下,任何函数都有一个属性prototype,它是一个指向对象(prototypeobject)的指针。原型对象的目的是包含特定类型的实例共享的属性和方法。默认的原型对象只有一个constructor属性,我们可以为它定义更多的属性和方法。//方法四:原型方法functionPerson(name,age){this.name=name;this.age=age;}Person.prototype.type="Human";Person.prototype.greeting=function(){console.log(`你好我是${this.name}`);};varwangwu=newPerson("王五",24);//wangwuinstanceofPerson===truewangwu.greeting();//"helloI'mWangWu"上面的例子wangwu是怎么找到原型对象中定义的问候语的呢?原因是所有的对象都有一个内部指针,指向实例构造函数的原型对象,在ECMAScript-262第五版中叫做[[Prototype]]。虽然标准没有定义如何访问这个内部指针,但Firefox、Safari、Chrome在每个指向相同对象的对象上都支持一个名为__proto__的指针属性。在chrome控制台查看wangwu的属性如下图:[正在站外上传图片...(image-1d07-1644313611733)]原型链搜索当对象实例访问某个属性或调用某个方法时,first在自己的属性中找到,就会返回一个值或者发起调用。如果没有找到,它会沿着__proto__的方向向上查找,直到最终找到Object.prototype。如果没有找到,它将终止并报错。对象实例、构造函数、构造函数原型对象的关系如下:上图中红色路径为查找方向,这条带__proto__指针的链为原型链。原型链的本质是一个依次指向原型对象的指针列表。原型的动态性因为对象实例的__proto__只是指向原型对象的指针,所以对原型对象的修改可以立即反映在实例中,即使实例是在修改原型之前创建的:Person.prototype。work=function(){console.log('workfunction');}//这里wangwu就是上面创建的实例,在原型中添加work方法后,可以立即调用wangwu.work();//"workfunction"但如果重写了整个原型对象后,相当于为构造函数指定了一个新的原型对象,创建实例的__proto__仍然指向旧的原型对象,所以new中定义的方法无法访问原型:Person.prototype={work:function(){console.log('workfunction');}};//错误wangwu.work();//"wangwu.workisnotafunction"//修改原型对象后创建的实例,因为获取到的__proto__属性指向新的原型,所以不会报错varsanma=newPerson('三毛',30);//可以愉快地“工作”了sanma.work();//"workAfterfunction"[imageuploadfailed...(image-b3adea-1644313611733)]覆盖了整个原型对象,也就是说上图中原来的原型指针被截断,指向了一个新的原型。默认情况下总结一下(因为原型对象实际上是可写的,因此可以更改):任何函数都有一个指向其原型对象的指针属性原型;任何对象实例都有一个指向其构造函数原型对象内部的指针[[Prototype]](__proto__);原型对象也是一个对象,所以它也有__proto__(比如上图中那个指向Object.prototype的);对象实例的__proto__指针指向构造函数的原型对象:wangwu.__proto__===Person.prototype;原型对象的constructor属性指向构造函数:Person.prototype.constructor===Person;构造函数和对象实例没有直接关系,只是有一个指向同一个原型对象的指针属性。对象实例识别(检测)我们知道,对于number、string、boolean、undefined、function这几种类型,这些类型的值可以通过typeof运算符轻松区分,但是对于function以外的引用类型实例和nulls,typeof返回“object”,但是要进一步区分,如果一个对象实例是一个神类型的实例,typeof也无济于事。instanceof操作符需要使用instanceof操作符来识别具体的对象实例类型。格式为instanceinstanceofFunc,instance为待检测的实例对象,Func为构造函数。了解了上面的原型链,对instanceof的检测机制就简单多了,只要instance的原型链上有一个__proto__指向Func的原型对象,就返回true,否则返回false。即:instance.__proto__...__proto__===Func.prototype也可以通过Func.prototype.isPrototypeof(instance),Object.getPrototypeof(instance)===Func.prototype来判断。console.log(wangwuinstanceofPerson);//trueconsole.log(wangwuinstanceofObject);//trueconsole.log(Person.prototype.isPrototypeof(wangwu));//trueconsole.log(Object.prototype.isPrototypeof(wangwu));//trueconsole.log(Object.getPrototypeof(wangwu)===Person.prototype);//trueconsole.log(Object.getPrototypeof(wangwu)===Object.prototype);//false,因为getPrototypeof函数只返回实例的原型,而不是返回原型链上的其他原型。原型继承了解了原型,那么原型继承就很简单了。需要扩展的类可以指向父类的原型。下面是一个简单的原型继承实现:functionMen(){//}Men.prototype=Object.create(Person.prototype);Men.prototype.constructor=男性;特别注意,给prototype属性赋值后,Men.prototype.constructor指向Person,所以一定要把它指向回门。
