%BB%BA%E5%AF%B9%E8%B1%A1%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF,作为学习笔记的总结介绍。创建对象的几种基本方法{}对象字面量Object()ornewObject()newConstructor()Object.create()Object.assign()关于newConstructor()Object.create()和Object.assign()的过程创建对象和模拟实现可以参考这篇前端面试精要|5000字长文讲解不可错过的原型操作方法及其仿真实现(Prototype:Part2)。工厂模式functioncreatePerson(name,age,job){consto=newObject();o.name=名称;o.age=年龄;o.job=工作;o.sayName=function(){console.log(this.name);};returno;}constperson1=createPerson("Nicholas",29,"SoftwareEngineer");constperson2=createPerson("Greg",27,"Doctor");每次调用上面的createPerson工厂函数可以创建一个对象,这个对象有nameagejob三个属性和一个sayName方法。根据传入的参数不同,返回对象的值也会不同。缺点:没有解决这个对象是什么类型的对象(没有更精确的对象标识,也就是没有精确的构造函数)。构造函数模式将工厂转化为构造函数后,如下函数Person(name,age,job){this.name=name;这个。年龄=年龄;这个。工作=工作;this.sayName=function(){控制台。日志(这个。名称);};}constperson1=newPerson("Nicholas",29,"SoftwareEngineer");constperson2=newPerson("Greg",27,"Doctor");person1.sayName();//Nicholasperson2.sayName();//Greg的构造函数和工厂的区别:没有显式创建对象;直接给this赋属性和方法而不返回使用构造函数创建对象会有以下步骤:在内存中创建新对象在新对象中。new对象内部的[[Prototype]]指针指向构造函数的prototype属性指向的对象;将构造函数的上下文this指向新创建的对象;在构造函数中执行代码(向新对象添加属性);如果构造函数返回一个非空对象,则返回这个对象,否则返回新创建的对象。没有return时,隐式返回新创建的对象,returnnull会返回新创建的对象;缺点:每次实例化一个新对象时,都会在内部创建一个sayName对应的匿名函数,这个函数对所有实例都有效,不需要每次都创建,它们指向同一个函数即可。所以上面的代码修改后就变成了这样:functionPerson(name,age,job){this.name=name;这个。年龄=年龄;这个。工作=工作;this.sayName=sayName;}functionsayName(){console.log(this.name);}constperson1=newPerson("Nicholas",29,"SoftwareEngineer");constperson2=newPerson("Greg",27,"Doctor");person1.说名字();//Nicholasperson2.sayName();//Greg的上述做法虽然解决了重复创建匿名函数的问题,但是引入了新的问题。外部sayName函数仅在构造函数中使用。如果对象需要很多这样的函数,那么就需要在外部定义很多这样的函数,这无疑会使代码难以组织。原型模式函数创建后,会有一个prototype属性,使用这个构造函数创建的每个对象都会有一个[[prototype]]内部属性指向它。使用原型的好处是它的所有属性和方法将在实例之间共享,共享的属性和方法直接在原型上设置。functionPerson(){}Person.prototype.name="Nicholas";Person.prototype.age=29;Person.prototype.job="软件工程师";Person.prototype.sayName=function(){console.log(this.name);};constperson1=newPerson();person1.sayName();//"Nicholas"constperson2=newPerson();person2.sayName();//"Nicholas"console.log(person1.sayName==person2.sayName);//true关于原型的工作原理,可以查看以下三篇文章。看完之后,相信你对原型的理解比大多数人都更深刻!前端面试要点|5000字长文详解不可错过的原型操作方法及其模拟实现(Prototype:Part2)前端面试必备|奇怪的原型(先有鸡还是先有蛋)(Prototype:Part2))前端面试要点|使用原型和构造函数创建对象(原型:第1部分)了解在原型分层对象中搜索属性的机制:当从对象访问属性时,JS引擎将根据属性名称进行搜索。JS引擎首先寻找对象本身。如果找到该属性,则停止搜索并返回该属性对应的值。如果在对象本身没有找到,它会通过原型链继续在原型对象中寻找这个属性。如果找到这个属性,就会停止查找并返回该属性对应的值,否则会继续在上层原型链中查找,直到遇到null。当一个属性被添加到一个实例时,这个属性将覆盖原型上同名的属性。此覆盖意味着搜索时不会在原型中找到具有相同名称的属性。即使该属性的值被赋值为null或undefined,它仍然会阻止对原型链的访问。所以如果你想访问,你需要删除这个属性,使用deleteobj.xx。您可以使用hasOwnProperty来确定实例是否拥有某个属性。返回true表示实例本身拥有该属性,否则表示不具有该属性。当一个属性存在于原型链上时,可以访问它,但是使用hasOwnProperty会返回false。in运算符in运算符用在两个地方,一个用在for...in循环中,另一个单独使用。单独使用时,返回true表示可以在对象或其原??型链上找到该属性。functionPerson(){}Person.prototype.name="Nicholas";Person.prototype.age=29;Person.prototype.job="软件工程师";Person.prototype.sayName=function(){console.log(this.name);};constperson1=newPerson();constperson2=newPerson();console.log(person1.hasOwnProperty("name"));//falseconsole.log("name"inperson1);//trueperson1.name="Greg";console.log(person1.name);//"Greg"-来自instanceconsole.log(person1.hasOwnProperty("name"));//trueconsole.log("name"inperson1);//trueconsole.log(person2.name);//"Nicholas"-来自prototypeconsole.log(person2.hasOwnProperty("name"));//falseconsole.log("name"inperson2);//truedeleteperson1.name;console.log(person1.name);//"Nicholas"-来自原型console.log(person1.hasOwnProperty("name"));//falseconsole.log(person1中的“name”);//true可以结合hasOwnProperty和in判断原型链上是否存在一个属性:functionhasPrototypeProperty(object,name){return!object.hasOwnProperty(name)&&nameinobject;}constobj=Object.create({name:"lxfriday"});console.log(obj);console.log(hasPrototypeProperty(obj,"name"));关于对象属性的枚举Object.keys()中...的枚举顺序Object.getOwnPropertyNames/Symbols()和Object.assign()对...inObject.keys()中的属性枚举顺序的处理会有很大的不同不是determined它们被枚举的顺序取决于浏览器的实现。而Object.getOwnPropertyNames()Object.getOwnPropertySymbols()和Object.assign()有明确的枚举顺序。数字键将按升序枚举;字符串和符号键将按插入顺序枚举;对象字面量中定义的键将在代码中以逗号分隔的顺序被枚举;constk2=Symbol("k2");constk1=Symbol("k1");consto={1:1,[k2]:"sym2",second:"second",0:0,first:"第一"};o[k1]="sym1";o[3]=3;o.third="third";o[2]=2;//['0','1','2','3','second','first','third']console.log(Object.getOwnPropertyNames(o));//[Symbol(k2),Symbol(k1)]console.log(Object.getOwnPropertySymbols(o));ES2017引入的对象迭代提供了两个静态方法来将对象的内容转换为可迭代的格式。Object.values()返回一个对象值数组;Object.entries()返回一个二维数组,数组中的每个小数组由对象的属性和值组成,类似于[[key,value],...]。consto={foo:"bar",baz:1,qux:{}};console.log(Object.values(o));//["bar",1,{}]console.log(Object.entries(o));//[["foo","bar"],["baz",1],["qux",{}]]在输出数组中,非字符串属性会被转换为字符串,以上两种方法获取引用类型的浅拷贝。consto={qux:{}};console.log(Object.values(o)[0]===o.qux);//trueconsole.log(Object.entries(o)[0][1]===o.qux);//truesymbol键将被忽略。constsym=符号();consto={[sym]:"foo"};console.log(Object.values(o));//[]console.log(Object.entries(o));//[]原型的另一种写法上面的例子中,对原型的赋值是一个一个的进行,比较繁琐。看看下面的赋值方法:}};在上面的例子中,Person的原型直接指向了一个对象字面量。这个方法的最终结果和前面的一样个别赋值是一样的,除了原型的constructor属性,这里的constructor不再指向Person的构造函数。默认情况下,创建一个函数时,会创建一个原型对象,这个对象上的constructor属性会自动指向这个函数。所以这种方式重写了默认的原型对象,意味着constructor属性指向了新对象的对应属性。尽管instanceof运算符仍将正常工作,但无法再使用构造函数来确定实例的类型。请参见以下示例:constfriend=newPerson();console.log(friendinstanceofObject);//trueconsole.log(friendinstanceofPerson);//trueconsole.log(friend.constructor==Person);//falseconsole.log(friend.constructor==Object);//true如果constructor属性很重要,那么你可以为它手动修复这个问题:functionPerson(){}Person.prototype={constructor:Person,name:"Nicholas",age:29,job:"SoftwareEngineer",sayName(){console.log(this.name);}};不过上面的设置方法有个问题,constructor的属性说明如下:{value:[Function:Object],writable:true,enumerable:false,configurable:true}当我们自己赋值时,枚举属性会默认设置为true,所以需要设置Object.defineProperty为non-enumerable:Object.defineProperty(Person.prototype,"constructor",{value:Person,enumerable:false,configurable:true,writable:true});原型的问题我们知道原型属性是所有实例共享的。当原型属性是原始值时没有问题,当原型属性是引用类型时就会有问题。看看下面的例子:functionPerson(){}Person.prototype={constructor:Person,name:"Nicholas",age:29,job:"SoftwareEngineer",friends:["Shelby","Court"],sayName(){console.log(this.name);}};constperson1=newPerson();constperson2=newPerson();person1.friends.push("Van");console.log(person1.friends);//"Shelby,Court,Van"console.log(person2.friends);//"Shelby,Court,Van"console.log(person1.friends===person2.friends);//上面例子中的true,原型属性friends本来是一个包含两个字符串的数组,但是因为person1修改了它的内容,所以原型上的这个属性被改变了,所以当person2访问的时候,也会打印三个字符串。由于这个问题,原型模式并没有单独使用,我们经常将构造函数和原型结合起来创建对象。总结我们知道使用构造函数或者原型创建对象存在问题,然后我们结合使用两者来解决上述问题。构造函数问题:每个对象都会声明对应的函数,浪费内存;原型问题:改变引用类型的原型属性的值会影响其他实例访问该属性;为了解决上面的问题,我们可以把所有对象相关的属性都定义在构造函数中,所有共享的属性和方法都定义在原型上。//在构造函数中定义对象相关的属性Human(name,age){this.name=name,this.age=age,this.friends=["Jadeja","Vijay"]}//分享属性和方法定义在原型上Human.prototype.sayName=function(){console.log(this.name);}//使用Human构造函数创建两个对象varperson1=newHuman("Virat",31);varperson2=newHuman("Sachin",40);//检查person1和person2的sayName是否指向同一个函数console.log(person1.sayName===person2.sayName)//true//改变好友person1的属性person1.friends.push("Amit");//Output:"Jadeja,Vijay,Amit"console.log(person1.friends)//Output:"Jadeja,Vijay"console.log(person2.friends)我们希望每个实例对象都应该有nameage和friends属性,所以我们使用它在构造函数中定义这些属性。此外,由于sayName是在原型对象上定义的,因此该函数将在所有实例之间共享。在上面的例子中,当person1对象的friends属性改变时,person2对象的friends属性没有改变。这是因为person1对象更改了它自己的friends属性,这不会影响person2中的那些。过去最后的精彩:前端面试要点|5000字长文详解不可错过的原型操作方法及其模拟实现(Prototype:Part2)前端面试必备|奇怪的原型(先有鸡还是先有蛋)(Prototype:Medium)前端面试必备|使用原型和构造器创建对象(Prototype:Part1)前端面试必备|一篇文章看懂JavaScript中的this关键字前端面试必备|一篇看懂现代JavaScript变量提升——let、const和var前端面试必备|一篇看懂JavaScript中的闭包前端面试必备|一篇看懂JavaScript中作用域和作用域链前端面试必备|一文了解在JavaScriptContext中执行IntersectionObserver和LazyLoading浏览器渲染原理初探CSS盒模型、布局和包含块详解CSS选择器优先级关注公众号查看更多。感谢阅读,欢迎关注我的公众号云影天空,带你解读前端技术,掌握最本质的技能。关注公众号拉你进讨论群,有什么问题我都会回复。
