概述原型是一个包含许多属性和方法的对象。实例对象可以通过__proto__属性访问原型对象上的属性和方法,而原型对象也有__proto__属性,可以访问另一个原型对象的属性和方法,这样递进就形成了一个链式结构。称为原型链。原型链的顶部为空。__proto__、prototype、constructor__proto__:实例对象可以通过该属性访问构造函数的原型对象。prototype:构造函数可以通过该属性访问自己的原型对象。constructor:原型对象的属性,指向构造函数本身。原型链的作用在访问实例对象上的属性或方法时,首先会在实例对象本身中寻找。如果实例对象本身没有这个属性,它会通过__proto__属性在构造函数的原型对象上寻找。在原型对象上如果没有,会继续向上查找原型对象的__proto__,直到JS引擎遇到null时返回undefined。functionParent(){this.name='a'}varparent=newParent()console.log(parent.__proto__==Parent.prototype)//trueconsole.log(parent.age)//undefinedParent.prototype.age=30console.log(parent.age)//30如果实例对象需要一些通用的属性或方法,可以在构造函数中声明这些属性,这样实例对象就会包含这些属性的副本。但是对于这些公共属性,没有必要为每个实例对象都维护一份副本,这样会造成资源的浪费。原型链解决了这个问题。通过在构造函数的原型对象上设置属性或方法,各个实例对象可以通过原型链共享这些属性和方法,节省内存资源。原型链有以下作用:实现继承:将子类构造函数的原型指向父类构造函数的实例对象,使子类实例可以继承父类实例的属性和方法。共享属性和方法:通过原型链,多个对象可以共享同一个原型对象的属性和方法,节省资源空间。轻松扩展对象的能力:在实例对象的原型对象上添加的属性和方法,可以被所有的实例对象继承,扩展了实例对象的能力。js中的一切都是对象有一句很流行的说法“JS中的一切都是对象,函数是一等公民”。首先我们来分析一下什么是“JS中的一切都是对象”。从字面上看,这句话的意思是JS中所有的值都是对象,但是我们知道JS分为基本数据类型和引用数据类型。基本数据类型:number、string、boolean、undefined、null、Symbol、BigInt引用数据类型:各种对象(Object、Array、Function等)基本数据类型和引用数据类型的区别:js中变量的存储方式engine主要有两个位置,分为Stack栈内存和Heap堆内存。js引擎存储数据时,将基本数据类型的值和对象的Heap堆内存地址存储在Stack栈内存中;引用数据类型的值存储在Heap堆内存中。所以基本数据类型和引用数据类型的存储方式不同。基本数据类型通过复制存储。将一个变量的值赋给其他变量会在内存中复制一份新的数据,所以修改变量的值不会影响其他变量;然后将引用类型的值分配给其他变量。创建变量时,变量的地址在内存中被复制,所以修改变量的值会导致其他变量的值一起改变。函数是一等公民理解了“js中一切皆对象”之后,我们知道js中除了undefined和null之外的数据都可以通过原型链找到原型对象。让我们理解为什么“函数是一等公民”。我们先看一个实例对象的原型链码:log(Parent.prototype.__proto__==Object.prototype)//trueconsole.log(Parent.__proto__==Function.prototype)//trueconsole.log(Function.prototype.__proto__==Object.prototype)//trueconsole.log(Object.prototype.__proto__)//null从上面代码可以看出,实例对象parent的__proto__指向构造函数Parent的原型对象(Parent.prototype),Parent原型对象的__proto__指向对于Object(Object.prototype)的原型对象,Object原型对象的__proto__指向null。构造函数Parent本身的__proto__指向Function(Function.prototype)的原型对象,Function原型对象的__proto__指向Object原型对象,最后指向null。可以看出原型链的顶端是null,null下面是Object的原型对象(Object.prototype)。看一个构造函数的原型链码:.prototype)//trueconsole.log(String.__proto__==Function.prototype)//trueconsole.log(String.prototype.__proto__==Object.prototype)//trueconsole.log(Function.__proto__==Function.prototype)//trueconsole.log(Function.prototype.__proto__==Object.prototype)//trueconsole.log(Object.__proto__==Function.prototype)//trueconsole.log(Object.prototype.__proto__==null)//true由此可以推导出,所有构造函数的__proto__都指向Function的原型对象(Function.prototype),也就是说所有的构造函数都是通过Function()构造的(String、Object等原生构造函数是Function的底层JS不一定是Function构造的,但是我们可以通过__proto__的指针来理解这个结构。这里比较混乱的是Function和Object的关系。console.log(Function.__proto__==Function.prototype)//trueconsole.log(Function.__proto__.__proto__==Object.prototype)//trueconsole.log(Object.__proto__==Function.prototype)//trueconsole.log(Object.__proto__.__proto__==Object.prototype)//true可以看出,Function和Object在追溯原型链的时候都会追溯到自己的原型对象,就像先有鸡还是先有蛋的问题,让人很使困惑。所以,Function.prototype和Object.prototype需要看成是两个特殊的对象,不能和Function和Object相提并论,或者说也不需要担心存在的先后顺序,因为这些原生对象本质上都是由JS引擎的底层。Function.__proto__指向Function.prototype的目的是为了满足所有函数的原型链都指向Function的原型对象,函数不仅具有对象的特性(Object原型对象的方法可以是作为函数的参数值和返回值,可以赋值给其他变量),并且具有生成实例对象的能力,所以函数是一等公民。Function.prototypetypeofFunction.prototype//在“function”之前,我们知道原型通常是一个对象,因为它的__proto__指向了Object的原型对象。但是Function.prototype是不同的。它是一个函数。ECMA(ECMA)给出的解释是:Function原型对象被指定为一个函数对象,以确保与在ECMAScript2015规范之前创建的ECMAScript代码兼容。那是为了确保与旧代码的兼容性。new操作符知道原型链的概念,那么构造函数在生成实例对象时是如何实现的呢?让我们了解一下new运算符。functionParent(){this.name='a'//做点什么}Parent.prototype.getName=()=>{return'b'}varparent=newParent()console.log(parent.name)//"a"console.log(parent.getName())//当"b"使用新命令时,其背后的函数依次执行以下步骤。生成一个空对象作为要返回的对象实例。将这个空对象的__proto__属性指向构造函数的原型对象prototype。将此空对象分配给函数内的this关键字。开始执行构造函数中的方法体。也就是说,构造函数内部的this指向一个空对象,所有对this的操作都会发生在这个空对象上。如果构造函数内部有return语句,返回了一个对象,new命令将返回指定的对象,否则返回this对象。另外,如果在普通函数上使用new命令,会返回一个空对象(因为普通函数中没有this)。instanceof操作符instanceof操作符也与原型链有关。我们先来看看instanceof操作符的使用方法:varp=newParent()pinstanceofParent//trueinstanceof操作符左边是实例对象,右边是构造函数。运行时会判断构造函数的原型对象prototype是否在实例对象上级的原型链中。所以也可以使用Object.prototype.isPrototypeOf()方法来判断。varp=newParent()pinstanceofParent//trueParent.prototype.isPrototypeOf(p)//和前面的语句效果一样因为instanceof检查原型链,有可能一个实例对象会为多个构造函数返回true.varp=newParent()pinstanceofParent//truepinstanceofObject//true如果实例对象的原型对象为null,instanceof返回false。变种对象=对象。创建(空);typeofobj//"object"objinstanceofObject//false
