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

把javascript对象解释清楚(一)

时间:2023-04-05 15:52:10 HTML5

有了前面几节的知识,我们理解这一节就会容易很多。在javascript中,函数也是对象,浏览器的全局上下文也是对象。键值在代码中更常见。合理利用对象的多维性和可扩展性的特点,可以给开发带来很多乐趣。如果存在知识盲点,在实际开发中就会因评价不充分、模型设计不合理而出现各种问题。小的会打补丁,重新设计模块API,做兼容处理。大的是关键数据维度不能满足应用场景,需要花很大的功夫去调整或者重构架构。接下来我们来梳理一下javascript对象的表达方式和特点,太细的知识就不梳理了。JavaScript是用一个简单的基于对象的范例设计的。对象是属性的集合,属性包含属性名和属性值。属性的值可以是函数,在这种情况下,属性也称为方法。除了浏览器中预定义的那些对象,我们还可以定义自己的对象。熟悉javascript的语法特点,合理设计数据模型,创建灵活无歧义的自定义对象,可以提高javascript的运行效率。字面量对象使用字面量方法创建对象占据了大部分开发场景,字面量对象示例:letfoo={a:1,b:'1234',c:function(){console.log(this.a+this.b)}}letfoo1={a:666,b:'hi',c:function(){console.log(`${this.b},${this.a}`)}}foo.c()//'11234'foo1.c()//'hi,666'对象字面量的特点主要是直观、简单、灵活,每个key和value都是在编码阶段确定的。使用对象字面量创建对象的缺点是,当我们需要创建多个相同的对象时,我们必须在源代码中为每个对象编写变量和方法。当有许多具有相同内容的此类对象时,这是一场灾难。因此我们发明了许多其他创建对象的方法,下面将进一步讨论。工厂模式工厂模式创建对象示例:letcreateFoo=function(a,b,c){leto=newObject()o.a=ao.b=bo.c=creturno}letfoo=createFoo(1,'1234',function(){console.log(this.a+this.b)})letfoo1=createFoo(666,'hi',function(){console.log(`${this.b},${this.a}`)})foo.c()//'11234'foo1.c()//'hi,666'所谓工厂模式就是对象的创建就像'商品'一样通过工厂按照标准化进行加工过程。以上是一个工厂函数的栗子。createFoo函数执行时,首先创建一个对象o,然后将传入的实参加入到o中,最后返回对象o。这样,每次执行createFoo函数,都会返回一个新的对象。当我们需要1000个相似的对象时,createFoo会在内部为我们生成1000个独立的对象o。通过这个栗子的分析,我们会发现:工厂函数在创建大量对象的时候会消耗大量的资源,而且由于每次都返回一个新的对象,所以我们没有办法判断对象的类型。与字面量方式创建对象相比,工厂函数的优势在于不需要在编码阶段创建大量结构相似的对象,而这一系列的创建工作都在运行阶段创建。每创建一个实例,都要创建该实例对应的所有属性和方法,所以工厂函数也存在创建N个实例时创建N个属性和方法的问题。工厂函数创建实例也面临实例类型的问题:fooinstanceofcreateFoo//falsefoo1instanceofcreateFoo//false//返回的对象是构造函数的实例ObjectfooinstanceofObject//truefoo1instanceofObject//true为什么实例函数不等于毛呢?对象是JavaScript中的引用类型。两个独立声明的对象永远不会相等(因为变量foo和foo1指向不同的堆地址),即使它们具有相同的属性,只有在将一个对象与对该对象的引用进行比较时才会返回true.lettoo={a:1}lettoo1={a:1}lettoo2=too1too==too1//faltoo===too1//faltoo1==too2//truetoo1===too2//trueconstructor构造方法创建一个自定义对象是在函数中利用构造函数、原型和实例对象之间的关系来封装私有和公共属性:functionFoo(a,b,c){this.a=athis.b=bthis.c=c}letfoo1=newFoo(1,'1234',function(){console.log(this.a+this.b)})letfoo2=newFoo(666,'hi',function(){console.log(`${this.b},${this.a}`)})//foo1和foo2是Foo的实例foo1instanceofFoo//truefoo2instanceofFoo//真正的构造函数的实现看起来简单多了,而且类型也可以通过例子来判断。构造函数的执行逻辑:在构造函数的初始化阶段,会先将一个上下文压入上下文栈,然后在创建变量对象时收集实际参数,初始化函数内部的变量声明,方向这一点,以及行动链将被确定。将实参的值分别复制到变量a、b、c中。然后像普通函数一样进入执行阶段,执行函数内部语句。构造函数是一个函数。既然构造函数是一个普通的函数,为什么在函数前面加一个new就可以实例化并返回一个对象呢?下面我们来创建一个模拟的构造器来加深理解,没错,创建一个构造器(思路来自网络,无耻盗用了????)。//假设我们创建了一个汽车对象类型,汽车功能functionCar(make,model,year){this.make=makethis.model=modelthis.year=yearthis.drive=function(name){console.log(`${name}驱动${this.model}${this.make}`)}}//将函数作为参数传递functionNew(func){//声明一个中间对象,它是最终返回的实例letres={}if(func.prototype!==null){//将实例的原型指向构造函数的原型res.__proto__=func.prototype}//ret是构造函数执行的结果,这里通过apply,修改构造函数内部的this指向res,也就是实例对象varret=func.apply(res,Array.prototype.slice.call(arguments,1))//当我们显式指定在构造函数中返回对象时,new的执行结果就是返回的对象if((typeofret==="object"||typeofret==="function")&&ret!==null){returnret}//如果不是显式指定返回对象,则默认返回res。这个res就是实例对象returnres}//通过new语句创建一个实例。这里,p1实际上是接收到new中返回的reslet。mycar=New(Car,"Tesla","ModelX",2018)mycar.drive('小丸子')console.log(mycar.make);//mycar是Car的实例mycarinstanceofCar//truewillletmycar=newCar(...)instance对象被视为它是一个简单的语法糖letmycar=New(Car,"Tesla","ModelX",2018)当执行代码newCar(...)时,会发生以下事情:一个Car.prototype继承了New对象被创建。使用指定参数调用构造函数Car并将其绑定到新创建的对象。newCar等同于newCar(),即不指定参数列表,不带任何参数调用Car。构造函数返回的对象是new表达式的结果。如果构造函数没有显式返回对象,则使用在步骤1中创建的对象。(一般情况下构造函数不会返回值,但是用户可以选择主动返回对象来覆盖正常的对象创建步骤)通过构造函数创建对象完美解决无法判断实例类型的问题.但是构造函数有和工厂函数一样的问题:每创建一个实例对象,内部都会创建一个中间对象,同时也会创建N次实例方法,所以有不必要的内层消耗。原型和构造函数结合在上面Car构造函数的栗子中。当创建100个Car实例时,drive函数在内部被复制100次。虽然各个驱动函数的功能相同,但由于属于不同的实例,所以每次都会分配独立的内存空间。同一个功能怎么能忍受重复创建。回想一下我们之前在原型部分提到的,每个函数都有一个原型属性,通过它指向自己的原型对象。那么我们就可以在函数的原型上做文章,将实例的公共属性和方法挂载到原型上。实例通过__ptoto__属性指向构造函数的原型,使得构造函数的原型对象在每个实例的原型链上,所以我们利用构造函数的原型来实现对公共属性和方法的封装,以及它只会被创建一次。还是上面Car的栗子:functionCar(make,model,year){this.make=makethis.model=modelthis.year=year}Car.prototype.drive=function(name){console.log(`${name}开着${this.model}${this.make}`)}letmycar=newCar("Tesla","ModelX",2018)mycar.drive('小丸子')上面的栗子也可以这样写:functionCar(make,model,year){this.make=makethis.model=modelthis.year=year}Car.prototype={constructor:Car,drive:function(){console.log(`${name}驱动${this.model}${this.make}`)}}letmycar=newCar("Tesla","ModelX",2018)mycar.drive('小丸子')两种写法是等价的。需要注意的是,后者相当于新建了一个对象,赋值给构造函数Car的原型。如果新原型的构造函数没有重新指向构造函数,会导致CarInstance类型判断错误的构造函数(instanceofCar为false)。不同的实现方式有各自的使用场景。同时,对象的实现涉及到数据维度和另一个主题设计模式。我们可以通过使用原型和构造函数的组合模式来解决很多问题。关于javascript的各种模式可以参考:JavascriptDesignPatterns