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

深入理解JavaScript——原型

时间:2023-03-27 14:21:16 JavaScript

本文将尝试回答这些问题:什么是原型?为什么会有原型?原型和__proto__有什么区别?概述首先,JavaScript是一种基于原型继承的语言。原型是为其他对象提供共享属性的对象。每个函数都有一个原型属性,它指向一个原型对象。每个对象都有一个隐式引用([[Prototype]]),[[Prototype]]指向它的原型对象,它从中继承数据、结构和行为。同时,原型对象还有一个原型(函数也是一个对象,它也有[[Prototype]]),这么一层,最后指向null,这种关系叫做原型链,本质上,原型是实现继承的一种手段。既然JavaScript选择了这种方式来实现,那我们就有必要讨论一下什么是原型继承?它的优点和缺点是什么以及它和类继承的区别,以及JavaScript函数中的其他继承方式,都会按照特定的规则为这个函数创建一个prototype属性(指向原型对象)。默认情况下,所有的原型对象都会自动获得一个名为constructor的属性,指向关联的构造函数《JavaScript 高级程序设计第 4 版》英文版介绍prototype:每当一个函数被创建时,它的prototype属性也会根据一组特定的规则被创建。默认情况下,所有原型都会自动获得一个名为constructor的属性,该属性指向它作为属性的函数。原型在ECMA规范中定义如下:4.4.8prototypeobjectthatprovidessharedpropertiesforotherobjects定义为:ObjectsthatprovidesharedpropertiesfromotherobjectsMDNIntroductionPrototype:遵循ECMAScript标准,表示法someObject.[[Prototype]]用于指向someObject的原型。从ECMAScript6开始,可以通过Object.getPrototypeOf()和Object.setPrototypeOf()访问器访问[[Prototype]]。这相当于JavaScript的非标准但许多浏览器实现的属性__proto__但不应与构造函数的原型属性混淆。构造函数创建的实例对象的[[Prototype]]指向func的prototype属性。Object.prototype属性表示Object的原型对象,所以我们这样理解原型:也称为prototype,它的职责是为其他对象提供共享的属性,从数据结构的角度看,它是一种单向链接listprototype对象:每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象称为原型对象;每个对象都有一个[[Prototype]]属性,也是一个指针,指向原型对象原型属性:每个函数都有一个原型属性,称为原型属性,指向一个原型对象,所以prototype,prototype,prototypeobject,和prototype属性其实是对一个事物的不同称呼,就像一个人在父母眼中是孩子,在孩子面前是父母。路上有个路人。当我们称这个东西为原型时,我们想表达的是它做了什么;我们称它为原型对象,是因为每个对象在创建时都会有自己的[[Prototype]]属性,并指向它;我们称其为原型属性,是因为每个函数在创建时都会有自己的原型属性,而这个属性是一个指针,指向原型对象。另外,还有一些概念:函数对象:所有的Function(内置Constructor)实例都是函数对象普通对象:除了函数对象,都是普通对象这样我们统一概念,然后解释一下什么是原型,为什么会有原型……等等。原型prototype的解说让我想起了《百年孤独》,一个六代人的家族故事。作者还记得书中的一句话:家里的第一口人被绑在树上,最后一个人正在被蚂蚁吃掉。为什么一看到原型就会想到?因为作者经常被原型、原型对象、原型、__proto__、[[Prototype]]等名词和概念搞糊涂,就像《百年孤独》中的人物名字一样。过了一会儿,我分不清谁是谁了。规范中,有对原型的解释:JavaScript中的每个函数都有一个prototype属性,它指向原型对象;每个对象都有一个[[Prototype]]属性,它指向原型对象给你一个例子functionFoo(){}Foo.prototype.name='johan';console.dir(Foo);console.dir(Foo.prototype);打印后:打印Foo的原型,看到三个属性。而且我们只赋了name,为什么多了两个参数呢?其实语言的底层帮助我们认识到,不管是什么类型的对象,一旦被创建,就会有自己的构造函数和[[Prototype]]。原型对象也是对象,即Foo.prototype是对象,所以它也有构造函数和[[Prototype]]当然,因为函数也是对象,所以它也有构造函数和[[Prototype]]这里需要说明一下:虽然在浏览器中PrintFoo看不到constructor属性,但是它确实存在,它指向Function的内置构造函数这里我们可以确认只要创建了一个函数,该函数将有自己的原型属性,它是一个带有[[Prototype]]和构造函数的对象。那么什么是[[Prototype]][[Prototype]]和__proto__在前面的例子中,Foo.__proto__用于打印日志而不是Foo.[[Prototype]],但是在打印Foo时,我看到了隐藏的名称原型的类型是[[Prototype]],但您可以在其他文章中看到__proto__。其实无论是[[Prototype]]还是__proto__,指的都是同一个东西。在之前的文章中,为了区分原型,我们称之为隐式原型。而它的出现是一个历史问题。ECMAScript官方规定prototype是隐式引用,但是私有浏览器开放并实现了一个属性__proto__,使得实例对象可以通过__proto__来访问prototype对象。后来,官方只好向事实低头,将__proto__属性纳入规范。后来在ECMAScript2015中,提出了getPrototypeOf()和setPrototypeOf()方法来获取/设置原型对象。至于[[Prototype]],只有在浏览器打印时才会显示。它与__proto__含义相同,但浏览器制造商对其进行了更改。背心。而我们在开发者工具中看到的[[Prototype]](或__proto__)是浏览器厂商有意渲染的虚拟节点。实际上对象是不存在的,所以forin无法遍历[[Prototype]]属性,Object.key(obj)也找不到。在上一篇文章中,我们解释了每个对象都有一个[[Prototype]]属性。它指向原型对象,原型对象也有自己的隐式引用([[Prototype]]),还有自己的原型对象,我们可以理解为父类对象。它的作用是当你访问一个属性时,如果对象内部不存在该属性,它会跟随[[Prototype]]属性在它的原型对象(父对象)上查找,如果父类对象还没有这个值,它会沿着父类对象的[[Prototype]]查找它的父类对象。依此类推,直到找到null。这个层层追溯的过程就构成了原型链。原型链是prototype和[[Prototype]]相结合的产物举个例子functionPerson(name){this.name=name;}Person.prototype.sayName=function(){returnthis.name;};varjohan=newPerson('johan');console.log(johan.sayName());//'johan'console.log(johan.toString());//这里的'[objectObject]'涉及到继承和new关键字,后面会讲解,假设你已经了解了基本概念。我们创建了一个构造器Person,并在它的prototype上创建了一个方法sayName,新的Person实例对象johan,此时johan属性上唯一的值就是name。当我们使用方法johan.sayName()时,它会在自己的属性上查找它。如果找不到sayName方法,则按照[[Prototype]]寻找它的原型对象,即Person.prototype,找到sayName,并调用它返回值。当我们调用方法johan.toString()时,同样的,找它自己的属性,找不到就顺着[[Prototype]]找它的原型对象,同上,如果还是找不到,再往上找沿着Person.prototype的原型对象,即Person.prototype.__proto__,这里,找到属性toString,调用返回,如果熟悉上面的属性,就可以理解为Object.prototype,即说Person.prototype.__proto__===Object.prototype;//true表示构造函数Person的原型继承自Object.prototype,这是原型链的作用,所以JavaScript是一门基于原型继承的语言,所以继承可以依靠原型来实现。至于构造函数(constructor),也可以实现继承,不过是另外一个话题了。该构造函数在上一篇文章中已经使用过。Number,它的作用是初始化对象,并将初值赋给对象的成员变量。关于构造函数,我们可以推导出构造函数的祖先Function(内置构造函数)和原型的祖先Object.prototype之间的鸡生蛋还是蛋生鸡的问题,具体可以看这篇文章——JavaScript中的先帝也可以通过以下方式实现继承窃取构造函数,结合原型可以实现更多可能的继承。我将在这篇文章中写出所有的具体方法——继承不是在理解继承之前,我们先来看看原型继承创建对象和原型继承。讲Object的时候提到了创建对象的三种方式,对象字面量、关键字new、Object.create,这三种都是原型。继承所谓原型继承,无非就是将一个对象设置为另一个对象的原型。在JavaScript中,有两种原型继承方法:显式继承和隐式继承。两者的区别在于是否主动操作。比如对象字面量和关键字new是隐式继承,语言底层帮我们继承(就像上面第一个例子),像Object.create,是显式继承,需要开发者手动操作。除此之外,还有一个显式继承-Object.setPrototypeOfObject.setPrototypeOf这个方法将特定对象的原型设置为另一个对象或为空。ES6新方法。语法是:Object.setPrototypeOf(obj,prototype)。具体例子是:constobja={a:1};constobjb={b:1};Object.setPrototypeOf(obja,objb);控制台日志(对象);可以看出,我们使用Object.setPrototypeOf方法设置Objb为obja的原型。打印obja时,我们可以看到obja的隐式原型([[Prototype]])指向objb。此外,还有一个方法可以显式继承原型——Object.createObject.create,它用于创建新对象,使用现有对象作为新创建对象的原型。ES5新方法。它的语法是Object.create(proto,propertiesObject)。可以看到案例:constobja={a:1};consta=Object.create(obja);控制台日志(一);作者之前写过一篇文章——Object.create,介绍了它是如何实现的,底层是原型继承的使用我们比较Object.setPrototypeOf和Object.createObject.setPrototypeOf,给我两个对象,设置其中一个作为另一个Object.create的原型,给我一个对象,设置它作为我创建的新对象的原型。从开发的角度来看,ES5中出现了Object.create,但是在设置原型的时候不满足两个对象。ES6提供了一个新方法——Object.setPrototypeOf。但不管怎么说,都是后来因个人需要加入的API,无法与JavaScript一开始采用的隐式继承相提并论。在这里,我们不妨回顾一下new的实现原理:创建一个新的对象,设置对象的[[Prototype]]是构造函数的prototype属性。在构造函数内部将this赋值给对象,执行构造函数的内部代码。如果构造函数返回一个非空对象,则返回该对象;否则,返回新创建的新对象。这就是隐式继承,只要我们使用new创建一个对象,就会执行上面的步骤。当然,同样的隐式操作是用对象字面量varobj={};//===newObject()对象字面量vararr=[];//===newArray()数组文字函数func(){}//newFunction();函数文字varstr='123';//===newSrting('123')字符串文字varbool=true;//===newBoolean(true)Booleanliteral//...JavaScript提供了几个内置的构造函数,如Object,Array,Boolean,String,Number等,当我们使用符号或关键字如{}时,[],function等,我们正在执行与new操作相同的隐式原型继承。我们可以这样说:隐式原型继承的目的是为了方便开发者更简洁地实现继承隐式原型继承和显式原型继承之间的相互转换无论是隐式原型继承还是显式原型继承,都是隐式引用对象的应用.两者之间存在一定程度的互操作性,也就是说,使用其中一个可以实现另一个的部分行为。比如我们使用隐式原型继承来实现Object.create,这是一个手写的Object.create方法,就像我们在Object.create中实现的那样:functioncreate(proto){functionF(){}F.prototype=原型;returnnewF();}原理也很简单,创建一个函数并将其原型赋值为目标Object,在实例化这个函数时,返回实例化后的值。新建实例化,[[Prototype]]相当于实例化后的值指向目标对象functioncreate(proto){functionF(){}//创建一个函数,每个函数都有一个prototype属性,指向原型对象F。原型=原型;//原原型是一个对象,它有两个属性,一个是constructor,即构造函数,指向F;另一个是[[Prototype]],指向Object.prototype//NowitAssignedtoprotoreturnnewF();//new的作用是创建一个空对象,将对象的原型赋值给另一个构造函数的prototype属性,执行构造函数//所以newF()后的实例[[Prototype]]指向F.prototype,也就是传入的proto},我们已经用new实现了显式原型继承,那么如何用显式原型继承来实现new(或对象字面量)呢?我们在手写new中已经实现了,把代码贴在这里:functionnew2(Constructor,...args){varobj=Object.create(null);//创建一个对象obj.__proto__=Constructor.prototype;//设置新对象的[[Prototype]]属性赋值为构造函数的原型对象varresult=Constructor.apply(obj.args);//这会分配一个新对象并初始化returntypeofresult==='object'?结果:对象;//nonEmptyreturnresult}这也解释了面试时经常考的两道面试题:手写new和手写Object.create,其中一个是隐式原型继承,一个是显式原型继承,可以通过他们各自的特点对方的方法是这样总结的,我们明白了原型是什么,原型和原型链的关系等等。我们知道“JavaScript是一种基于原型继承的语言”这句话的意思。虽然现在面试原型已经不重要了,知乎曾经有过这样的问题——面试一个5岁的前端,但是连原型链都不清晰,全是Vue、React等实现,所以人应该用它?.不懂原型就开发业务很正常。笔者认为这并不奇怪。毕竟前端前端开发已经从面向对象转向了函数式编程。在文章的最后,我会回答文章开头的问题Q:原型是什么?A:为其他对象提供共享属性的对象Q:为什么我们有原型?A:JavaScript是一门基于原型继承的语言,这里是为了实现继承Q:prototype和__proto__有什么区别A:Prototype是函数独有的属性,每个函数在创建时都会有自己的prototype属性,它指向一个对象。该对象称为原型对象。并且每个对象都有一个__proto__属性,它也指向原型对象。如果两者之间有什么关系呢?那么子对象的__proto__===父对象的原型Q:什么是原型链?对象,一层一层,最终指向null。这种关系称为原型链。Q:原型如何实现继承A:原型继承有四种方式,根据是否需要手动操作分为隐式原型继承和显式原型继承。隐式原型继承占了我们开发的大部分,也就是对象字面量和new,也就是这两种方法的底层语言会帮助我们创建对象,关联原型,初始化属性。显式原型继承分为Object.create和Object.setPrototypeOf,可以主动将一个对象设置为另一个对象的原型Q:原型和原型链有什么关系A:Prototype是实现继承的方法,原型chainisinheritedProductReference深入理解JavaScript原型面试中如何回答JavaScript原型链问题系列文章深入理解JavaScript-入门深入理解JavaScript-什么是JavaScript深入理解JavaScript-什么是JavaScript深入理解JavaScript——万物皆对象理解JavaScript——Object(对象)深入理解JavaScript——new是做什么的?深入理解JavaScript——对象。JavaScript中的始皇深入理解JavaScript——instanceof——寻祖深入理解JavaScript——Function深入理解JavaScript——深入理解scope——深入理解JavaScript——this关键字深入理解JavaScript——调用、应用和绑定JavaScript——立即执行函数(IIFE)深入理解JavaScript——词法环境深入理解JavaScript——执行上下文和调用栈——深入理解JavaScript——ScopeVSExecutionContext深入理解JavaScript——Closure深入理解JavaScript——防抖深入理解JavaScript——函数式编程深入理解JavaScript——垃圾回收机制深入理解JavaScriptJavaScript之——数组深入理解JavaScript——循环来这里深入理解JavaScript——字符串