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

JS内功修炼:九阳神功--原型链

时间:2023-04-02 21:03:14 HTML

写在前面如果JavaScript是武侠经典,那么原型链就是九阳神功。在金庸的武侠小说中,对九阳神功的描述是这样的:“修炼《九阳神功》后,易筋洗髓;产生浓郁紫气;会以极快无尽的速度生成,普通拳脚也能使用。大增,又是治病圣典,百病不生,毒不侵,至阳热之全力,能将人烧成焦炭,专破一切寒邪内气。”可见其技能的强大。那么,如何在js中修炼好九阳神功呢?真正成功的技巧是从底层去理解它。工程师和码农的区别在于对底层的理解。当你写完一行代码,或者遇到一个bug,解决的速度取决于你对底层的理解。底层是什么?我目前个人的理解是“当你写出每一行代码的时候,在对应的虚拟机中会怎样,或者在V8引擎中是怎样工作的。再厉害的程序员甚至都知道每次数据操作是在堆中还是在栈中。”不,我不知道你会不会,哈哈哈)”。1.Js原型链的简单认识**在了解原型链之前,首先要了解js的基本类型和引用类型:1.基本类型有Undefined、Null、Boolean、Number、String,这些类型在内存中占据固定大小的空间,它们的值存储在栈空间中,我们按值访问。基本类型:简单的数据段,存储在栈中内存,占用固定大小的空间。2.引用类型引用类型,值大小不固定,存放在栈内存中的地址指向堆内存中的对象。它是通过引用访问的。存放在堆内存中的对象,变量实际上保存了一个指针,这个指针指向另外一个位置。每个空间的大小不同,要根据情况进行具体分配。当我们需要访问一个引用类型(如对象、数组、函数等)的值时,我们先从栈中获取对象的地址指针,然后再从堆内存中获取需要的数据。**JS原型链简单简单,但也难。首先,函数(Function)具有原型属性,对象(Object除外)具有_proto_。原型链的最顶层是Object.prototype,这个对象没有原型对象。你可以在Chrome中输入:Object.__proto__输出为:?(){[nativecode]}可以看到没有.prototype属性。2、prototype和_proto_的区别我们知道prototype是一个对象,其他对象可以通过它实现属性继承。变量a={};控制台日志(a.prototype);//undefinedconsole.log(a.__proto__);//对象{}varb=function(){}console.log(b.prototype);//b{}console.log(b.__proto__);//function(){}/*1,文字方法*/vara={};console.log(a.__proto__);//对象{}控制台。日志(a.__proto__===a.constructor.prototype);//true/*2,构造方法*/varA=function(){};vara=newA();console.log(a.__proto__);//A{}console.log(a.__proto__===a.constructor.prototype);//true/*3,Object.create()方法*/vara1={a:1}vara2=Object.create(a1);console.log(a2.__proto__);//对象{a:1}console.log(a.__proto__===a.constructor.prototype);//false(这里是图1中的Exception)varA=function(){};vara=newA();console.log(a.__proto__);//A{}(即构造函数A的原型对象)console.日志(a.__proto__.__proto__);//Object{}(即构造函数Object的原型对象)console.log(a.__proto__.__proto__.__proto__);//nullinstanceof计算什么?以前简单理解instanceof只是检测一个对象是否是另一个对象new的实例(比如vara=newObject(),一个instanceofObject返回true),但是实际的instanceofeof的运算规则比这个复杂//假设instanceof运算符左边是L,右边是RLinstanceofR//instanceof运算的时候,判断是否有R.prototypeL.__proto__.__proto__在L....的原型链上。===R.原型?//存在则返回true,否则返回false注意:instanceof操作会递归查找L的原型链,即L.__proto__.__proto__.__proto__.__proto__...直到找到或者找到顶层.所以一句话理解instanceof的运行规则就是:instanceof检测左边的__proto__原型链中是否有右边的prototype原型。图形构造函数Function和Object的关系看代码就明白了://①构造函数Function的构造函数是自己的Function.constructor===Function;//true//②构造函数Object的构造函数是Function(由此可以看出所有构造函数的构造函数都指向Function)Object.constructor===Function;//true//③构造函数Function的__proto__是一个特殊的匿名函数log(Function.__proto__);//function(){}//④这个特殊匿名函数的__proto__指向Object的原型。Function.__proto__.__proto__===Object.prototype//true//⑤Object的__proto__指向Function的原型,即特殊的匿名函数Object.__proto__===Function.prototype;//trueFunction.prototype===Function.__proto__;//当构造函数Object和Function遇到instanceofFunction.__proto__.__proto__===Object.prototype;//trueObject.__proto__===Function.prototype;//trueif看了上面,如果你还觉得上面的关系看晕了,你只需要记住下面两个最重要的关系,其他的关系可以推导出来:1.所有构造函数的构造函数都指向Function2,Function的原型指向一个特殊的匿名函数,而这个特殊的匿名函数的__proto__指向Object.prototypefunction、Function、Object和{}我们知道在Js中一切都是对象(Object),但是在Js中没有类(class);Js是基于原型(prototype-based)的面向对象(OOP)编程范式,但并不是所有的对象都具有原型的属性:vara={};控制台日志(a.prototype);//=>未定义varb=function(){};控制台日志(b.prototype);//=>{}varc='你好';控制台日志(c.prototype);//=>undefinedprototype是每个函数在定义的时候自带的属性,但是在Js中函数本身也是一个对象。先看一下下面几个概念的区别:function是Js中的关键字,用来定义function类型的变量,有两种语法形式:functionf1(){console.log('Thisisfunctionf1!');}typeof(f1);//=>'函数'varf2=function(){console.log('这是函数f2!');}typeof(f2);//=>'function'ifmore要以面向对象的方式定义函数,可以使用Function:varf3=newFunction("console.log('Thisisfunctionf3!');");f3();//=>'这是函数f3!'类型(f3);//=>'函数'typeof(Function);//=>'function'其实Function是用来构造函数类型变量的类,或者说是函数类型实例的构造函数(constructor);同理,Object、String、Number等都是Js内置的构造函数类型实例比较特殊的是Object,用于生成对象类型,其简写形式为{}:varo1=newObject();类型(o1);//=>'对象'varo2={};类型(o2);//=>'对象'typeof(Object);//=>'function'prototypeVS_proto_prototype和length是每个函数类型自带的两个属性,而其他非函数类型则没有(开头的例子已经说明),这点之所以比较容易被忽视或误解是因为所有类型的构造函数本身都是函数,所以它们都有自己的原型属性:Js中除了prototype之外的所有对象(除undefined和null等特殊情况外)都有一个内置的[[Prototype]]属性,指向其“父类”的原型。这个内置属性在ECMA标准中并没有给出明确的获取方式,但是很多Js实现(比如Node,大多数浏览器等)都提供了一个__proto__属性来引用这个[[Prototype]],我们使用下面的例子来说明实例中的__proto__是如何指向构造函数的原型的:varPerson=function(){};Person.prototype.type='人物';Person.prototype.maxAge=100;varp=newPerson();控制台日志(p.maxAge);p.name='下雨';===人;//=>truep.__proto__===Person.prototype;//=>trueconsole.log(p.prototype);//=>undefined上面代码解释图:Person是函数类型变量,所以自带prototype属性,prototype属性中的constructor指向Person本身;new关键字生成的Person类的实例p1通过__proto__属性指向它这里的__proto__只是为了说明实例p1在内部实现时与父类的关系(指向父类的原型)。在实际运行过程中,实例可以直接通过。属性,从而实现了继承的功能。核心图varObj=function(){};varo=newObj();o.__proto__===Obj.prototype;//=>trueo.__proto__.constructor===Obj;//=>trueObj.__proto__===Function.prototype;//=>trueObj.__proto__.constructor===函数;//=>trueFunction.__proto__===Function.prototype;//=>trueObject.__proto__===//=>falseObject.__proto__===Function.prototype;//=>trueFunction.__proto__.constructor===Function;//=>trueFunction.__proto__.__proto__;//=>{}Function.__proto__.__proto__===o.__proto__.__proto__;//=>trueo.__proto__.__proto__.__proto__===null;//=>true从上面的例子和图可以看出,原型对象也有__proto__属性,往回走直到nullnew关键字的作用是完成上图所示的实例和原型的连接父类,并创建一个新对象;instanceof关键字的作用从上图也可以看出,其实就是判断__proto__(和__proto__.__proto__...)是否指向父类的原型:varObj=function(){};varo=newObj();o对象实例;//=>真oinstanceof对象;//=>trueoinstanceof函数;//=>发lseo.__proto__===Obj.prototype;//=>trueo.__proto__.__proto__===Object.prototype;//=>trueo.__proto__.__proto__===函数;//=>falseprototypechain原型链的结构1.原型链的继承就是使用和修改原型链的结构(添加、删除、修改节点中的成员),使得实例对象可以使用整个原型链中的所有成员(属性和方法)。2.使用原型链继承属性搜索原则必须满足属性搜索原则1.Constructor对象原型链结构图functionPerson(){};varp=newPerson();2。{}对象原型链结构图3.数组原型链结构图4.Object.prototype对应的构造函数总结:从本质上理解:对象和函数是存储在堆中的引用类型,下面的一系列操作是为了使用或者访问它们的属性,所以不管是prototype还是_proto_都是函数或者对象自带的指针,让外界的其他函数或者对象可以使用它们的一些属性