当前位置: 首页 > news >正文

商城页面seo优化搜索结果

商城页面,seo优化搜索结果,自己动手做网站,有哪些网站免费做推广#x1f680; 个人主页 极客小俊 ✍#x1f3fb; 作者简介#xff1a;web开发者、设计师、技术分享博主 #x1f40b; 希望大家多多支持一下, 我们一起学习和进步#xff01;#x1f604; #x1f3c5; 如果文章对你有帮助的话#xff0c;欢迎评论 #x1f4ac;点赞 个人主页 极客小俊 ✍ 作者简介web开发者、设计师、技术分享博主 希望大家多多支持一下, 我们一起学习和进步 如果文章对你有帮助的话欢迎评论 点赞 收藏 加关注 前言 有些新手朋友可能听说过这么一句话就是js中存在两个链条,它们分别为:作用域链和原型链 它们彼此的区别在于作用域链是为了访问变量和数据而存在的一种链条访问机制 而原型链是访问对象的属性或者方法而存在的一种机制 其中这里的原型链就是今天我要说的主题! 我们学习js必须要知道什么是原型、原型链、构成函数、实例对象这些彼此之间的关系和应用范围! 如果你没有搞明白js中的原型链也就说明你没有把js学明白! 那么接下来就跟着我一起开始学习吧! 为什么要使用原型? 原型在js中也称之为原型模式, 那么为什么要有这种模式存在呢? 因为我们js面向对象不是说过吗面向对象编程其实就是一种模块、一种封装对吧! 如果你还没有完全明白javascript面向对象建议你一定要听我讲完下面的知识点! 构造函数模式 有的时候我们会使用构造函数模式来创建对象 虽然这种构造函数创建对象看起来没什么大问题但是并非没有缺点! 这里我们来看一段简单的构造函数模式创建对象的案例吧! 代码 function Person(username,age,job){this.usernameusername;this.ageage;this.jobjob;this.sayfunction (){console.log(我的名字叫:this.username,年龄:this.age,职业:this.job);} }var testnew Person(张三,18,ui设计师); test.say(); 这就是一段非常简单的构造函数模式封装对象的方法! 构造函数模式的缺点 任何一种技术的出现都是为了弥补旧技术的不足! 那么构造函数模式有什么缺点呢为什么又要搞出一个原型模式呢? 搞出这么个东西意义何在 这里我先卖个关子想知道的话就继续往下看吧! 从上面的代码角度上看,确实感觉不出什么奇怪之处! 那我们再来看一个构造函数模式的例子: 代码 //构造函数 function Person(){this.sayfunction (num){console.log(测试方法num);} } //实例化 var obj_1new Person(); var obj_2new Person();//调用 obj_1.say(1); obj_2.say(2);console.log(obj_1.sayobj_2.say); //返回false然后我们看看下面这张图你就知道其中缺点在什么地方了! 如图 此时你会发现这个函数居然会不相等!! 为什么呢 从另一个角度来讲构造函数每执行一次就会把构造函数中的方法也重新在内存中生成一份相同的方法! 比如: 执行一万次构造函数,那么就会在内存中创建一万次构造函数里面的方法, 这对内存是一种非常大的消耗 要知道在ECMAScript中只要是函数都是对象 这句话! 之所以会返回false就是因为函数的底层地址是不一样的! 不要以为方法名称一样就是一样! 那你就错了 如图 并且函数本身也是对象, 你定义一个函数也就相当于实例化了一个函数对象, 其实就会在内存中开辟一个空间,地址也会不一样! 上面的这行代码 this.sayfunction (num){console.log(测试方法num); }其实从一定逻辑上讲也可以看成以下形式 this.saynew Function (num){console.log(测试方法num); }这样子其实你更好理解每个构造函数实例中的方法,其实是不同Function的实例 所以说像这种在内存中无限创建很多完成同样方法的Function实例是完全没有必要的! 那么有没有什么好的方案可以解决这个问题呢? 我们完全可以使所有的对象共享同一个方法,那么构造函数执行一万次,在内存中也只会存在一份相应的方法! 通常是可以把这个函数方法转移到构造函数外部 代码说明 function Person(username,age,city){this.nameusername;this.ageage;this.citycity;this.saysay;/*this.sayfunction() {console.log(this.name的年龄是:this.age);}*/ }//把say方法写在全局作用域中 function say() {//这里的this要清楚是谁在调用say这个方法 this自然就指向谁console.log(this.name的年龄是:this.age); } var test1new Person(张三,33,北京市); var test2new Person(李四,66,深圳市);//判断test1与test2之间的方法是否是共用的 console.log(test1.saytest2.say);如图 案例2 以下案例也是同样的道理 //构造函数 function Person(){this.saysay; }//定义到全局下 function say(num){console.log(测试方法num); }//实例化 var obj_1new Person(); var obj_2new Person();//调用 obj_1.say(1); obj_2.say(2);console.log(obj_1.sayobj_2.say); //返回true目的我们是达到了但是这样写就真的行了吗 不会存在其他问题吗 分析 很明显在开发当中 如果把函数这样子写在全局作用域中在多人开发的时候那么就会出现命名冲突或者说这样做会 污染全局作用域的命名空间, 所以说我们在项目开发的时候都是尽量地不在全局作用域中写变量和函数 那么如何来解决这样的问题呢? 就是接下来我要说的原型和原型链,然后根据情况来解决相应的问题! 原型的定义 我们说在js当中一切皆对象对吧, 那么在js的函数对象中都有一个内置的Prototype属性 这个属性指向一个对象你可以把这个Prototype属性想象成一个指针 它指向一个对象 而这个对象就成为原函数对象的原型,俗称原型对象 我们来看一段简单的代码: function Test(){}console.log(Test.prototype);var T1new Test(); var T2new Test(); var T3new Test();console.log(T1.__proto__); console.log(T2.__proto__); console.log(T3.__proto__);结果 这里还要给大家科普一个小知识就是普通对象没有原型对象的,也就是说不是函数对象 也可以说成不是通过new Function创建的对象那么就不会存在原型对象 我们来验证一下 代码如下 //普通对象 var json{} console.log(json.prototype);//元素对象 var oDiv document.getElementById(connent); console.log(oDiv.prototype);//函数对象 function test(){} console.log(test.prototype);结果如下 所以说首先只有函数对象才会有原型对象 理解构造函数、实例化对象、原型对象彼此之间的关系 这样就形成了每个函数对象其实都有一个指向另一个对象的指针 这里我们要说明一点的就是只有函数才有一个prototype属性这个prototype属性就是我们的原型对象 同时从图中也可以看到原型当中含有一个constructor 属性,而这个属性指向的就是当前原型对象的构造函数 我们一般会拿这个构造函数通过new创建出来实例对象,而实例对象是没有prototype属性的! 如果你在一个实例对象上调用prototype必然返回undefined, 而一个实例对象靠的是使用__proto__的隐式属性进行访问原型对象! 如图 如果按照这个逻辑推理的话你可以使用以下代码进行验证一下是否正确: console.log(T1.__proto__Test.prototype); //返回true console.log(Test.prototype.constructorTest); //返回true结果证明的确是这样子实例对象自己会有一个指针属性为__proto__, 用它来指向构造函数的原型对象 其实你也可以使用isPrototypeOf方法来判断当前这个实例对象中的__proto__指针到底是不是能够指向到本身构造函数的原型对象中! 例如 //构造函数1 function Person(name,age) {}var p1new Person();//构造函数2 function Test(){} var testnew Test();console.log(Person.prototype.isPrototypeOf(p1)); //返回true console.log(Person.prototype.isPrototypeOf(test)); //返回false以上我们用了isPrototypeOf方法来监测一个实例对象中的__proto__指针是否指向对应的原型对象 在js中我们还可以使用一个叫Object.getPrototypeOf()的方法来监测实例对象和原型对象之间的关系 例如 console.log(Object.getPrototypeOf(p1)Person.prototype); //返回true console.log(Object.getPrototypeOf(p1)Test.prototype); //返回false所以从结果上看 使用这个Object.getPrototypeOf方法返回的就是当前实例对象中__proto__属性所指向的原型对象 同时constructor属性也的确指向了本身的构造函数 这样原型对象和实例对象之间就通过__proto__连接在一起,形成了一个链条, 而所谓的原型链也就是实例对象和原型对象之间的链条关系, __proto__这条线也就是原型链的关键 并且这条链条从图中我们也可以看到还可以往上走到一个叫Object.prototype的地方! 实例对象属性和方法搜索的优先级 上面说了有了原型对象,那么实例对象可以共享原型对象中的属性和方法 那么问题来了这些实例对象又是如何进行查找属性和方法的呢? 举个栗子 function createPerson(name,age) {this.namename;this.ageage;this.sayfunction (){console.log(2.构造函数中定义的say方法!);} }createPerson.prototype.sayfunction () {console.log(我的名字叫:this.name); }var anew createPerson(张三,33); var bnew createPerson(李四,55); var cnew createPerson(王武,66);c.sayfunction(){console.log(1.实例对象c 定义的say方法!); }a.say(); b.say(); c.say();结果如下: 代码分析 按照这个查找逻辑上来看的话调用的查找方式如下: 先在实例对象上查找定义的属性和方法,优先级最高,如果找不到的情况下然后再是构造函数中进行查找我们定义的属性和方法, 最后如果也找不到的情况下就到原型对象中去寻找! 注意:这并不是把原型对象中的属性和方法覆盖了,只是优先调用的顺序而已! 例如 function Person(){}Person.prototype.username张三; Person.prototype.age30; Person.prototype.job设计师; Person.prototype.sayfunction (){console.log(我是this.username); }var p1new Person(); var p2new Person();p1.username李四;console.log(p1.username); console.log(p2.username);代码分析 首先这里构造函数中我们什么都没有定义的情况下这里就是先搜索实例对象本身,如果在实例对象中找到了具有给定的属性或者方法则进行返回! 如果没有找到,则会根据一个叫__proto__的指针到原型对象中去寻找,如果找到就返回! 如果最终都没有找到则返回undefined 那么这个案例中则执行了两次搜索先询问了实例对象本身是否具有, 然后顺着指针到原型对象中去询问 所以说我们在实例对象上调用属性和方法的时候都会出现以上相同的搜索过程! 而有了这个搜索模式的帮助下多个实例对象则可以共享原型对象所定义的属性和方法就是这个原理! 如图 这里我再次提一嘴前面不是使用到了constructor属性吗, 这个属性也默认是共享的也就是所有实例对象默认情况下都可以通过访问这个属性来确定构造函数是谁! 所以大家也应该注意一下就是如果你在实例对象上定义了一个属性或者方法而且原型对象中也定义了同名的属性或者方法,依照查找的顺序会依次搜索实例对象---构造函数---原型对象, 即便是同名也是优先调用最先找到的位置! 也就是说当你在实例对象上定义一个与原型对象中同名属性和方法的时候会自动屏蔽原型对象中的同名属性和方法注意这里也仅仅是屏蔽而不是覆盖! 当然换句话说也可以理解为你在实例对象上添加同名属性和方法的时候会阻止访问原型对象中的同名属性和方法,明白这个意思吧! 即便是你在实例对象上把某个属性和方法的值定义为null , 那么访问的时候也只会停留在实例对象这个层面而不会恢复其指向构造函数和原型对象的链接! 但是如果你使用delete操作符是可以完全删除实例属性同时也删除构造函数中的同名属性从而让我们能够访问原型对象中的同名属性! 例如 function Person(){this.username李四; }Person.prototype.username王五; Person.prototype.age30; Person.prototype.job设计师; Person.prototype.sayfunction (){console.log(我是this.username); }var p1new Person();p1.usernamenull;delete p1.username;console.log(p1.username); //这里输出的结果来自于 原型对象其实我们就可以按照这个查找逻辑来修改原型对象从而实现父子继承的关系 这个我们后面再说! __proto__的真正含义! 那么实例对象到底底层是如何查到原型对象中去的呢 这其实就要说到刚刚我们提及到的__proto__这个东西了! 嘿嘿 我们来看一张图: 如图 每个实例对象都会有一个 __ proto__ 属性,这个属性是自动生成的, __ proto__ 属性指向自己的原型对象 而且实例对象也就是通过这一条__ proto__线路找到原型对象中的属性和方法的 这就是我马上要提到的原型链 特别注意 这里我提醒一下可能你以前看到的也的确是叫__proto__这个 但是目前Chrome打印出来之后效果提示的是[[Prototype]] 如图 这里只是显示变了而已代码层面上,实例对象还是可以继续使用__proto__这个属性的 然而__proto__的真正意义也就在于两个字:查找 也就是接下来要说的原型链 因为原型链就是通过__proto__属性形成的任何对象普通对象和函数对象都有__proto__属性 prototype与__proto__的区别 其实我们在上面的图中也能看出来彼此的一个很明显的区别: __proto__ 是实例对象指向原型对象的指针我们俗称隐式原型并且是每个实例对象都会有的一个属性! prototype是构造函数/函数才有的原型对象我们俗称为显式原型 这里我特别提一下,其实prototype就是一个用来设置原型而另外一个__proto__则用来查找数据,如果你还不明白那么就看下面的原型链解释就清楚了! 所以说大家不要再把__proto__与函数的 func.prototype 属性混淆了! 原型链 理解了以上这些是什么之后那么接下来我们就可以来研究一下什么是原型链了! 并且js中实现继承主要是依靠原型链来实现! 所以我们才需要学习原型链的原理! 在学习原型链之前你先记住一个概念就是原型链: 其实是实例对象和原型对象之间的链条! 我们之前不是说了 实例对象在搜索属性或者方法的优先级吗 就是说如果当你调用一个属性或者方法时, 其实首先是会在当前实例对象上进行搜索, 如果没有那么就到构造函数中去进行搜索如果还是没有那么就到原型对象当中去是进行搜索 那么有人就会问了,如果 当前构造函数的原型对象中也没有呢? 接下来js会干什么呢 揭晓谜底吧请看下图: 如图 分析 以上这张图就是默认情况下如果说person这个实例对象如果顺着__proto__这个链条没有在Person.prototype中找到想找的方法或数据那么又会继续顺着__proto__这个链条往上走继续寻找 那么找谁呢? 这是js设计者规定的默认就会找到一个Object.prototype原型对象当中去, 那么这样子的查找链条也就形成了原型链 并且既然这里有一个所谓的Object.prototype原型对象 那么这个对象也会有一个属性叫constructor的属性,来返指向其它的构造函数Object, 这里的Object.prototype原型对象自身其实也有一个__proto__, 因为我们说了实例对象才有这个属性所以还可以向上访问不过这里再往上就只能返回null了,因为没有了! 小结 js其实在面向对象的设计之初就是依照近原则当我们要使用一个方法和数据时js会优先查找自身如果没有就查找离自己最近的这里也就指的是构造函数如果自己没有他就会沿着原型链__proto__这个链条向上查找如果还没有找到它还会沿着原型链继续向上查找直到找到Object.prototype原型对象 Object.prototype原型对象默认也是会有一些方法在里面的 如图 所以这里 其实就是解释了js中每个函数都存在原型对象属性prototype 并且在js中所有函数的默认的原型对象都是Object的实例对象,而且默认还形成一个层层嵌套的形式,这也就是默认原型链 那我们要Object的原型对象干嘛它里面又没几个方法和属性来满足我们日常开发的需求对吧 所以我们需要扩大我们的原型链条, 这就要谈论到继承了 原型对象中的this指向 当我们使用new操作符来执行一个函数的时候这个时候构造函数中的this会指向到该实例对象上 而原型对象中如果函数方法出现的this 那么也是指向的该实例对象 举个栗子 function Test(name,age,company,salary){this.usernamename;this.ageage;this.companycompany;this.salarysalary;console.log(this);//打印this }Test.prototype.sayfunction (){console.log(this); //打印this }var testnew Test(张三,18,重庆科技,9K);//打印实例对象 console.log(test);结果如下 由此可见构造函数中的this与原型对象中方法中的this 其实就是当前new出来的实例对象 原型链继承的实现 在js中继承就是通过原型链来实现的,那么到底如何实现呢? 我们废话不多说直接看个案例! 代码 //猫类 function Cat(){this.username小猫; }//狗类 function Dog(){this.username小狗; }//老虎类 function tiGer(){this.username老虎; }//猫类的原型对象中有一个方法 Cat.prototype.behaviorfunction (){console.log(【this.username】 这种动物真的会要咬人!!....);console.log(this);//谁调用this归谁! }//实例化猫类 var catnew Cat();//把狗类的原型对象指向猫 Dog.prototypecat;//实例化狗类 var dognew Dog();//把老虎的原型对象指向狗 tiGer.prototypedog;//实例化老虎类 var tigernew tiGer();//调用方法 tiger.behavior(); dog.behavior();这里我们修改了原型对象的指向, 也就是修改了构造函数的prototype属性值对吧! 那么这样一来会造就什么样的情况呢 简单一点说, 我们就会顺着一个: C实例−C原型(B实例)−B原型(A实例)−A原型 这样一个过程来进行查找! 也就是实例tiger--Tiger原型对象(dog实例)---Dog原型对象(cat实例)---Cat原型对象 进行查找 这里也很明显,tiGer的实例和原型对象都没有一个叫behavior的方法, 那么就会顺着一条线路一直往上寻找 如图 当然你也可以通过修改__proto__来实现! 代码如下 //猫类 function Cat() {this.username 小猫; }//狗类 function Dog() {this.username 小狗; }//老虎类 function tiGer() {this.username 老虎; }//猫类的原型对象中有一个方法 Cat.prototype.behavior function () {console.log(【 this.username 】 这种动物真的会要咬人!!....);console.log(this);//谁调用this归谁! }//实例化猫类 var cat new Cat();//实例化狗类 var dog new Dog();//实例化老虎类 var tiger new tiGer();//修改原型链指针 tiger.__proto__ dog; dog.__proto__ cat;//console.log(tiger); tiger.behavior();原理分析 首先定义了三个构造函数Cat、Dog和Tiger,每个构造函数都有一个属性username, 分别赋值为小猫、小狗和老虎 接下来在Cat类的原型对象中定义了一个方法behavior,该方法用于打印出动物的名字以及调用该方法的对象信息。 然后通过实例化Cat类创建了一个名为cat的实例对象并将Dog类的原型对象指向了cat。 这样就建立了一个继承关系即Dog类会继承Cat类的属性和方法。 接着通过实例化Dog类创建了一个名为dog的实例对象并将Tiger类的原型对象指向了dog。同样地这也建立了一个继承关系即Tiger类会继承Dog类的属性和方法。 最后通过实例化Tiger类创建了一个名为tiger的对象并调用了它的behavior方法。 由于原型链上的继承关系调用这个dog.behavior()和tiger.behavior()都会查找到最终的原型对象也就是Cat.prototype中的behavior方法进行调用! 当然如果这里再调用Object.prototype.__proto__往上就没有了就会返回null 这样就形成了一个父子级别的关系,因为我们通过修改prototype或者__proto__形成了一个链条 毕竟原型对象其实也是一个Object的实例,所以它也有一个__proto__属性,本身它在一个普通原型对象下的指向为Object.Prototype原型对象也就是说所有函数的默认原型对象都是Object的实例 但是这里我们把它修改了! 通过__proto__相连接, 每个继承父函数的实例对象都包含一个__proto__指针 最后会指向我们指定父函数的prototype原型对象 这样一直可以以此类推进行迭代父函数的原型对象, 利用__proto__属性一直可以再往上一层继承。 在这个程中就形成了原型链 我们也可以使用Chrome并且打印一下实例对象来进行查看这个链条的走向! console.log(tiger);如图 这里如果眼尖的朋友可能已经注意到了一个问题,那就是constructor这个属性显示不见了, 构造函数的指向也不对了、原型的显示也不对了, 全部都指向了Cat构造函数, 当然从继承的效果上是不影响的! 我们可以用以下代码测试一下: console.log(tiGer.prototype.constructor); console.log(Dog.prototype.constructor);如图 原因:简单点说因为修改原型对象的时候,指向了另一个新的实例对象所以把 constructor给丢失了! 如果你想看上去比较合理一点加入以下代码 解决方案 Dog.prototype.constructorDog; tiGer.prototype.constructortiGer;修改之后如图 这就是原型链查找的关系一层一层的链接关系就是:原型链 有些实例对象能够直接调用Object.prototype中的方法也是因为存在原型链的机制! 所以说JavaScript 中原型链用于实现继承就是这样实现的! 给大家专门准备了一张通用默认原型链原理图,拿去背吧!! 如图 基于原型链的继承 看了以上的案例和图例之后我们应该就对javascript中的继承有个深入的理解了! JavaScript的对象其实都会有一个指向一个原型对象的链条, 当我们试图访问一个对象的属性时它不仅仅在该对象本身上去进行搜寻还会搜寻该对象的原型以及原型的原型依次层层向上搜索直到找到一个名字匹配的属性为止 或到原型链的末尾! 对象属性的继承 但是有一点我觉得值得注意就是修改原型链 也就是使用{ __proto__: ... } 和 obj.__proto__ 有点不同,前者是标准且未被弃用的一种方式! 举个栗子 var obj{ a: 值1, b: 值2,__proto__: c }比如在像这样的对象字面量中c的值必须为 null 或者指向另一个对象用来当做字面量所表示的对象的 原型链而其他的如a 和 b将变成对象的自有属性, 这种语法读起来非常自然,并且兼容性也比较不错! 我们来看个实际的小案例 代码如下 const obj {a: 张三,b: 李四,__proto__: {b: 王五,c: 绿巨人,}, };console.log(obj); console.log(obj.a); delete obj.b; console.log(obj.b); console.log(obj.c);分析 当前obj的原型链中具有属性 b和c两个属性 如果obj.__proto__.__proto__ 依照之前的图例肯定是访问到Object.prototype 最后obj.__proto__.__proto__.__proto__则是 null, 这里就是原型链的末尾值为null 完整的原型链看起来像这样: { a: 张三, b: 李四 } --- { b:王五, c: 绿巨人 } --- Object.prototype --- null 那么要说继承关系的话那就是__proto__ 设置了原型链,也就是说它在这里的原型链被指定为另一个对象字面量! 即便是这里我使用了delete obj.b删除了属性b也会从__proto__这个链条找到原型链中所继承来的属性b 如图 但是注意了如果这里我没有使用delete obj.b来删除了属性b 那么,当我们调用obj.b返回的则是李四而不是王五这里其实叫做属性遮蔽(Property Shadowing),意思是虽然没有访问到王五,但这只是被遮住了而已并不是被覆盖和删除的意思! 当然我们也可以根据这个原理来创建更长的原型链并在原型链上查找一个属性 代码如下 const obj {a: 1,b: 2,// __proto__ 设置了原型链。它在这里被指定为另一个对象字面量。__proto__: {b: 3,c: 4,// __proto__ 设置了原型链。它在这里被指定为另一个对象字面量。__proto__: {d: 5,},}, }; console.log(obj.d); // 输出5那么它的原型链就是如下这样: { a: 1, b: 2 } --- { b: 3, c: 4 } --- { d: 5 } --- Object.prototype --- null 其实就是这样就可以嵌套很多层出来,让对象看起来更加有层次结构,也方便管理一些特殊的数据! 对象方法的继承 方法或者函数的继承在js中其实和属性的继承也没有差别! 特别要说明的其实也就是this的指向,当继承的方法被调用时this值指向的是当前继承的对象而不是拥有该函数属性的对象 代码说明 const parent {username: 张三,age:35,method() {return 我的年龄是(this.age 1)岁;} }console.log(parent.method()); //输出36//然后我们通过child继承了parent的对象 const child {__proto__: parent, } console.log(child.method());//输出36 child.age 5; console.log(child.method());代码分析 当调用 parent.method 时this指向了parent所以按照正常逻辑执行 所以输出36 然后我们通过child继承了parent的对象 现在调用 child.method 时this虽然指向了child但是又因为 child 继承的是 parent 的方法 首先在 child 上寻找有没有method方法, 但由于child本身没有名为method方法则会根据原型链__proto__找上去,最后找到即 parent.method方法来执行! 然后我们在 child添加一个age属性赋值为5, 这会就会遮蔽parent上的age属性 child对象现在的看起来是如下这样的 { age: 5, __proto__: { username: 张三, age: 35, method: [Function] } } 最后输出:6, 是因为child 现在拥有age属性,就不会去找parent对象中的age属性了, 但是方法还是会去找parent中的method方法, 而方法中的this.age现在表示 child.age然后再这个基础上1结果就是这样了! 更多参考案例 function Test() {this.username 张三;this.age 33;this.job软件开发; }Test.prototype.sayfunction (){return 我的名字叫:this.username,我的年龄是:this.age我的职业是:this.job; }var testnew Test();//新建一个对象并且修改原型链 var obj {username:李四,age:35,__proto__:test }console.log(obj); console.log(obj.username); console.log(obj.age); console.log(obj.say());另类继承实现方法 修改构造函数this指向从而实现继承 我们有时候可以借助call方法来实现简单的继承效果! 举个栗子 function Animal(name,age,food){this.usernamename;this.ageage;this.eatfunction (){console.log(这只[this.username]动物要吃[food]);} }Animal.prototype.color黑色; Animal.prototype.sayfunction (){console.log(我的名字叫this.name); }function Panda(name,age,eat){this.like玩耍;Animal.call(this,name,age,eat); //借用一下 }var p1new Panda(熊猫盼盼,18,竹叶);//打印输出 console.log(p1); p1.eat();结果 这个案例中应用了call修改this指向来达到一个共享的目的! 但是这种使用call等方法来修改this严格意义上来讲只能算借用! 因为这种方式有一个很大的缺点就是不能继承所谓父类中原型里面的属性和方法不然你看上图打印的结果当中并没有出现Animal类原型对象中的color属性和say方法 所以这种继承方式如何和prototype修改原型方式结合一起使用就会有意想不到的效果,并且参数的传递也会更加灵活多变! 举个栗子 //定义构造函数 function Person(userename,age,sex){this.nameuserename;this.ageage;this.sexsex;this.type人类; } Person.prototype.sayfunction(){console.log(hello world); }function Student(username,age,sex,score){//借用Person构造函数Person.call(this,username,age,sex);//定义属性this.scorescore }//改变原型指向 Student.prototypenew Person();//不传值 Student.prototype.behaviorfunction(){console.log(英语学习!!); }var s1new Student(张三,15,男,100分)//打印结果看看 console.log(s1); console.log(s1.type); console.log(s1.name); console.log(s1.age); console.log(s1.sex); console.log(考试得分:s1.score);s1.behavior(); s1.say();代码分析 从上面的代码中我们可以看到构造函数Student中我们借用了Person构造函数, 然后在通过prototype修改原型指向这样一来,不仅可以获取到父类构造函数中的属性和方法 也可以获取到父类原型对象中的属性和方法 这时都可以通过__proto__这个链条拿到! 如图 通过循环复制实现继承 我们的原型对象prototype既然是一个对象那么我们也可以通过循环复制的手法把父级原型对象里面的属性和方法拷贝到目标原型对象下,同时也可以结合call方法借用构造函数中的属性和方法 代码如下 function Person(username,age) {this.nameusername;this.ageage; }Person.prototype.type 人类; Person.prototype.nationality 中国; Person.prototype.job 软件开发; Person.prototype.like 足球,篮球,游戏; Person.prototype.test 123;function Student(username,age) {Person.call(this,username,age); }var per Person.prototype; var stu Student.prototype;//过滤不需要的属性 var arr[type,nationality,test]; for (k in per) {if(arr.indexOf(k)-1){stu[k] per[k];} }var s1new Student(李四,18); var s2new Student(王五,25);console.log(s1); console.log(s2);如图 __proto__的兼容性 根据MDN官方的建议其实__proto__是被弃用了的! 那么到底我们平常使用什么来修改原型的指针呢? 在 JavaScript 中你的确是可以通过直接修改实例对象的 __proto__ 属性来达到目的! __proto__ 是一个非标准的属性它在大多数的js环境中都可以使用包括浏览器和Node.js 但是由于这个属性是非标准的它在一些环境中可能不可用或者在未来的标准中可能会被弃用,也就是说虽然一些浏览器仍然支持__proto__但也许已从相关的web标准中移除也许正准备移除或者出于兼容性而保留! __proto__浏览器兼容性如下表 所以如果可以的话我们尽量不使用__proto__而改成其他例如:Object.setPrototypeOf方法 举个栗子 让我们使用Object.setPrototypeOf来实现一个简单的继承 代码如下 function Test(){}Test.prototype.company重庆科技;function Test2(){}Test2.prototype.num100;function Test3(){}Test3.prototype.username张三;//实现继承 Object.setPrototypeOf(Test2.prototype, Test.prototype); Object.setPrototypeOf(Test3.prototype, Test2.prototype);var t3new Test3(); console.log(t3);效果 再看一个案例! const a { company : 重庆科技 }; const b { age: 33 }; const c { username: 张三 };//实现继承 Object.setPrototypeOf(a, b); Object.setPrototypeOf(b, c);console.log(a); console.log(a.username); console.log(a.age); console.log(a.company);效果如下 所以我觉得可以的情况下尽量使用标准的Object.setPrototypeOf方法来实现继承 因为Object.setPrototypeOf方法基本上被所有现代浏览器引擎所支持, 并且也允许动态地修改对象的原型 原型链与继承查找机制 当你访问一个对象的属性或方法时如果这个对象本身没有这个属性或方法那么js会在这个对象的原型中寻找这个属性或方法,如果找到了就会使用它, 如果还是找不到就会在原型的原型中寻找以此类推直到找到为止, 而继承的关键也就在于自定义修改原型的指向! 所以当你把之前的原型链图分析透彻你就会知道原型链就是通过__proto__属性形成的任何对象普通对象和函数对象都有__proto__属性,并且其核心思想也就是通过__proto__这个链条来进行查找数据! DOM原型链的形成 这其实也很好的解释了我们javascript中DOM属性和方法也是这样子进行查找的 html div idconnent/divjs var oDiv document.getElementById(connent);console.log(oDiv.__proto__); console.log(oDiv.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__);结果如下 其实这样你就会知道当一个DOM元素在使用某个属性和方法的时候是怎么进行查找的它的链条也是通过__proto__来进行查找的,对吧! 其中顺序如下 ↓HTMLDivElement ↓HTMLElement、 ↓Element、 ↓Node、 ↓EventTarget ↓Object通过__proto__就形成了JavaScript中与DOM文档对象模型相关的概念了 这里也给大家简单介绍一下方便理解! HTMLDivElement: 这是一个代表 HTML div 元素的类, 它继承了 HTMLElement 的属性和方法包括可以用来改变元素样式的属性和方法,当然这里我只是举个栗子不一定就是div元素根据你打印的情况决定!HTMLElement: 这是一个基础类代表任何 HTML 元素, 所有的 HTML元素都继承了 HTMLElement 的属性和方法Element: 这是一个基础类代表任何 HTML 或 XML 元素, 它定义了所有元素共享的属性和方法例如 getAttribute()和setAttribute()Node: 这是所有DOM节点的基类包括元素、文本节点、注释等, 它定义了一些通用的属性和方法如 parentNode 和 childNodes。EventTarget: 这个接口表示可以添加或删除事件监听器的事件目标Object: 这个也就是顶层的Object构造函数 在w3c也有这些属性和方法的详细解释 如图 而且这些继承关系这样子一直走下来查找的属性和方法的关键就是原型链, 可以说没有原型链就没有现在的javascript 当查找对象的某个属性或方法的时候首先在当前对象中查找如果没有去对象的__proto__中去查找, 这样子一直到最顶层null,而这样的__proto__形成的一条查找链条就是原型链 现在你可以感受一下是不是如此呢! 并且继承也就是修改原型的指向即__proto__或prototype 以上这些类和接口一起构成了JavaScript 的 DOM API应用程序编程接口, 这样来允许我们以代码编程方式操作网页中的元素内容、结构和样式。 DOM中所有的属性和方法你都可以看做为一个原型链的继承关系! 其实你可以去通过js创建一些xml、svg、普通元素以及文档模型! 代码如下 // 创建一个新的XML文档 var xmlDoc document.implementation.createDocument(null, null); // 创建根元素 var root xmlDoc.createElement(root); xmlDoc.appendChild(root); // 创建一个子元素 var child xmlDoc.createElement(child); // 设置子元素的内容 var childText xmlDoc.createTextNode(This is a child element); child.appendChild(childText); // 将子元素添加到根元素 root.appendChild(child);// 打印XML文档 console.log(xmlDoc.__proto__); console.log(xmlDoc.__proto__.__proto__); console.log(xmlDoc.__proto__.__proto__.__proto__); console.log(xmlDoc.__proto__.__proto__.__proto__.__proto__); console.log(xmlDoc.__proto__.__proto__.__proto__.__proto__.__proto__);console.log(------------------------------------------------------------);var oDiv document.getElementById(oDiv); console.log(oDiv.__proto__); console.log(oDiv.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__.__proto__.__proto__);console.log(------------------------------------------------------------);console.log(document.__proto__); console.log(document.__proto__.__proto__); console.log(document.__proto__.__proto__.__proto__); console.log(document.__proto__.__proto__.__proto__.__proto__); console.log(document.__proto__.__proto__.__proto__.__proto__.__proto__);console.log(------------------------------------------------------------);// 创建一个新的SVG文档 var svgNS http://www.w3.org/2000/svg; var svgDoc document.implementation.createDocument(svgNS, svg, null);// 添加根元素 var root svgDoc.documentElement;// 添加一个矩形元素 var rect svgDoc.createElementNS(svgNS, rect); rect.setAttribute(x, 10); rect.setAttribute(y, 10); rect.setAttribute(width, 100); rect.setAttribute(height, 100); rect.setAttribute(fill, blue); root.appendChild(rect);// 添加一个圆形元素 var circle svgDoc.createElementNS(svgNS, circle); circle.setAttribute(cx, 120); circle.setAttribute(cy, 120); circle.setAttribute(r, 50); circle.setAttribute(fill, red); root.appendChild(circle);// 将SVG文档添加到HTML文档中 document.body.appendChild(root);console.log(rect.__proto__); console.log(rect.__proto__.__proto__); console.log(rect.__proto__.__proto__.__proto__); console.log(rect.__proto__.__proto__.__proto__.__proto__); console.log(rect.__proto__.__proto__.__proto__.__proto__.__proto__); console.log(rect.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__); console.log(rect.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__); console.log(rect.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__);然后看看他们的__proto__原型链的指针你就明白了! 如图 使用原型和原型链的好处 到这里学了那么多我们使用原型链到底有什么好处呢 其实原型链也可以适当的帮助我们优化代码减少代码冗余,提高程序代码的复用性! 举个栗子 现在有一个属性应该出现在每一个实例上,那我们就可以重用它,尤其是对于方法或者函数这种类型的属性! 比如说现在有多个实例字面量对象,每一个对象都是一个容器,而里面都包含一个 getValue方法 可以用来访问的值对象本身内部的某值! 代码如下 const arr [{ value: 张三, getValue() { return this.value; } },{ value: 李四, getValue() { return this.value; } },{ value: 王五, getValue() { return this.value; } }, ];但是你可以想一下这样子做好吗 每一个对象都基本上有同样的代码, 这就是冗余且不必要的代码,并且我以前也说过一个函数在一个对象中就会开辟一块内存空间如果代码巨大的情况下这样子做非常耗费内存资源! 所以你可以尝试优化一下当然优化的办法有很多这里我们重点讨论的就是原型链 你可以试想一下将 getValue方法移动到所有盒子的原型链[[prototype]]上! 那么我们加以修改一下变成如下形式 代码 //公共使用 const _ObjPublic{getValue() {return this.value;} }const arr [{ value: 张三, __proto__:_ObjPublic },{ value: 李四, __proto__:_ObjPublic },{ value: 王五, __proto__:_ObjPublic }, ];console.log(arr[0].getValue()); console.log(arr[1].getValue()); console.log(arr[2].getValue()); 效果如下 这样一来所有对象中的 getValue方法 都会根据原型链的原理找到并引用相同的函数降低了内存使用率! 但是上面这样一个一个手动去捆绑原型链太麻烦了, 如果代码多了这样一个一个的去修改也是一件很大工程量的事情! 那么怎么办呢 这时我们就可以使用构造函数方式创建实例对象 因为当我们使用构造函数来构造的实例对象它会自动为实例对象设置 原型链(__proto__)属性 构造函数是使用 new 调用的函数 还记得吗! 其实在js的设计之初就给我们考虑过这些问题, 大致我分为以下几个用途: 优化和简化代码实现代码重用根据__proto__的链条实现属性和方法的继承只要是这个__proto__链条上的东西都可以被调用到 所以在js中就有了这样一个说法:js是基于对象的脚本语言但是也有人说js是基于原型的脚本语言! 我们单纯的来说一下prototype原型对象 用它来实现代码重用与属性和方法的共享! 所以为了避免了代码冗余公共使用的属性和方法我们是可以设置到原型对象中的! 方法 构造函数名.prototype.属性值;构造函数名.prototype.方法function(){..代码段.. } 然后通过构造函数来实例化的所有实例对象都可以使用该构造函数对应原型对象中的属性和方法 也就是说这个类型的实例对象就都会共享这些属性和方法 也就是通过原型链(__proto__)在进行查找! 这样做的一个好处是:减少了内存占用, 并且也实现了代码重用! 也是使用原型对象的一大优点 代码如下 function createPerson(name,age) {this.namename;this.ageage; }createPerson.prototype.sayfunction () {console.log(我的名字叫【this.name】, 我的年龄是:this.age); }var anew createPerson(张三,33); var bnew createPerson(李四,55);a.say(); b.say();console.log(a.sayb.say);如图 大家可以看到,say方法,我没有定义到实例对象上也没有定义到构造函数当中,而是定义到了原型对象里面! 并且这样子做就相当于所有的实例对象都共享一个方法那么它们的地址都是相等的了! 这样做的好处在于节约内存开销 为什么这样说呢? 我们来看下面这张图: 如图 这就是让公用的方法或者属性在内存中只存在一份,所以prototype就是这样来实现数据的共享, 不然的话你每一次new都会在内存中创建一份属性或者方法出来 而不管我们实例化多少次对象出来原型对象里面的属性和方法只生成一次所以会节省内存, 同时提高代码的可重用性和可维护性 。 也就是说只要是通过 new 创建的实例对象无论多少次它们的__proto__都是指向构造函数的prototype 如图 所以我们给构造函数的原型对象添加一些方法,就能让创建的多个实例对象共享同一个方法,减少内存的使用。 当然你也可以把所有的属性和方法都添加到原型对象当中构造函数中就不用再去定义了,看情况来决定! 相当于构造函数创建的每一个实例都会自动将构造函数的prototype属性作为其 原型链__proto__ 你完全可以使用Object.getPrototypeOf方法来进行验证以下 console.log(Object.getPrototypeOf(a) createPerson.prototype); //返回true console.log(Object.getPrototypeOf(b) createPerson.prototype); //返回true console.log(createPerson.prototype.constructor createPerson); //返回true字面量与原型链之间的关系 在JavaScript 中的一些字面量语法会隐式的创建原型链__proto__ 这里我给大家举几个案例就会明白了~~ 举栗 对于使用对象字面量创建的对象__proto__返回的是:Object的原型对象 你也可以理解为对象字面量没有 __proto__ 的情况下,自动将Object.prototype 作为它们的__proto__值 代码 var obj {} console.log(obj.__proto__); console.log(Object.getPrototypeOf(obj) Object.prototype); //返回true对于使用数组字面量创建的对象__proto__返回的是:数组的原型对象 也就是说数组字面量会自动将 Array.prototype 作为它们的 __proto__值 代码 var arr []; console.log(arr.__proto__); console.log(Object.getPrototypeOf(arr) Array.prototype); //返回true如果是正则表达式字面量则会自动将 RegExp.prototype 作为这些字面量的 __proto__值 const regexp /abc/; console.log(Object.getPrototypeOf(regexp) RegExp.prototype) // true对于使用字符串字面量方式创建的字符串对象__proto__返回的是:字符串的原型对象,也就是说 如果是字符串字面量则会自动将 String.prototype 作为这些字面量的 __proto__值 var str ; console.log(str.__proto__); console.log(Object.getPrototypeOf(str) String.prototype) // true如果是使用数字字面量方式创建的数值对象__proto__返回的是:数值的原型对象,也就数值字面量则会自动将 Number.prototype 作为这些字面量的 __proto__值 var num 100; console.log(num.__proto__); console.log(Object.getPrototypeOf(num) Number.prototype) // true那么如果是函数呢一个函数名称其实也算是一种函数字面量的形式! 那__proto__返回的是:函数的原型对象也就是Function.prototype 也就是会自动将 Function.prototype 作为这些函数字面量的 __proto__值! var fn function () { } console.log(fn.__proto__); console.log(Object.getPrototypeOf(fn) Function.prototype) // true所以说这又解释了为什么有些属性和方法只是在特定的构造函数上定义的, 而它们又自动在所有特定的实例对象上才可以使用,对吧! 比如像 map()这样的数组方法只是在 Array.prototype 上定义的方法而它又只会自动在所有数组实例上可用就是因为这个原因! 性能与原型链 了解原型继承的模型是使用javascript编写复杂代码的重要基础,另外我们还要注意代码中原型链的长度在必要时可以将其分解以避免潜在的性能问题! 因为原型链上较深层的属性和方法的查找, 在时间上可能会对性能产生负面影响这在性能至关重要的代码中可能会格外明显, 因为如果尝试访问不存在的属性始终会遍历整个原型链也就是原型链中的每个可枚举属性都将被枚举, 那么层次多了反而不好! 所以说我们在遍历对象的属性时最好先判断一下 要检查对象是否具有在其自身上是否有定义的属性,而不是让__proto__自动的去搜索其原型链上的某个地方! 必要的情况下可以使用hasOwnProperty()判断 举个栗子 function Graph() {this.vertices [];this.edges []; }Graph.prototype.addVertex function (v) {this.vertices.push(v); };const g new Graph(); // 当前原型链为: g --- Graph.prototype --- Object.prototype --- null//检查对象自身是否有vertices属性 返回true console.log(g.hasOwnProperty(vertices)); //检查对象自身是否有nope属性 返回false console.log(g.hasOwnProperty(nope)); //检查对象自身是否有addVertex属性 返回false console.log(g.hasOwnProperty(addVertex)); //检查原型对象自身是否有addVertex属性 返回true console.log(Object.getPrototypeOf(g).hasOwnProperty(addVertex)); 最后总结 原型链其实是一种关系的链条, 它是让实例对象和原型对象之间产生关系一种链条! 而这个关系是通过原型([[Prototype]])也就是__proto__来进行关联的! 而也只有实例对象才有这个__proto__不标准的属性,当然这里的意思是有的游览器并不支持这个属性! 那么有了原型链我们实例对象在进行查找属性的时候则按照以下规则: 首先在实例对象上查找,如果有则使用自身带有的属性或方法如果没有则通过__proto__指向的原型对象进行 查找,找到则使用, 如果找不到则继续向原型对象的__proto__进行查找, 找到则使用以此类推, 如果最终未找到则会报错! 同时我们也对构造函数有了一个深入的了解,也就是构造函数在实例化的时候会生成一个叫prototype的属性,它就是构造函数的原型对象, 这个对象中还有一个默认存在的属性constructor用来指向原型对象所在的构造函数的指针! 实例对象的__proto__属性指向的是构造函数的原型对象,这个对象的指向是可以被修改的,从而实现层层继承 也就是说原型的指向是可以被改变的, 也不管你是修改prototype还是__proto__最终原来本身指向的原型会指向到一个新的原型对象上从而通过__proto__查找链条来实现继承关系! 点赞 ✍️评论 收藏❤️ 大家的支持就是我坚持下去的动力! 如果以上内容有任何错误或者不准确的地方欢迎在下面 留个言指出、或者你有更好的想法 欢迎一起交流学习❤️❤️ 更多 好玩 好用 好看的干货教程可以 点击下方关注❤️ 微信公众号❤️ 说不定有意料之外的收获哦..嘿嘿嘿、嘻嘻嘻!
http://www.zqtcl.cn/news/123391/

相关文章:

  • 使用vue做的网站有哪些企业门为什么要建设门户网站
  • 上海移动云网站建设在门户网站上爆光怎么做
  • 网站建设开票内容百度浏览器广告怎么投放
  • 深圳公司网站建立小程序商店制作
  • 网站建设知识网犀牛云做网站多少钱
  • 东莞seo优化推广重庆做网络优化公司电话
  • 网站建设的设计思路高校建设网站的特色
  • 宁波网站建设八宝山做网站的公司
  • 哪里有网站建设多少钱网站建设哪家服务态度好
  • 白云区网站开发公司备案不关闭网站的方法
  • 男的做那个视频网站家用电脑可以做网站服务器
  • 网站建设的行业客户烟台市未成年思想道德建设网站
  • 设计个网站要多少钱鼓楼网站开发
  • 东莞外贸网站搭建制作北京app开发制作
  • 优化网站公司外包微信商城怎么开店
  • 网站设计的导航栏怎么做东莞seo网络优化
  • wordpress直接上传视频网站吗做网站软件
  • 电脑维修网站模板下载来个网站吧好人一生平安2021
  • 做公益选哪个网站好网站建设方案多少钱
  • 丰台做网站的公司vs2015 手机网站开发
  • 宝思哲手表网站qq官网登录入口网页版
  • 二手书网站开发设计太原建设网站的公司
  • 江门网站seo推广qq代挂网站建设
  • 合肥制作网站企业做文字logo的网站
  • php 网站提速有没有帮人做简历的网站
  • 九江建网站报价比特币网站做任务
  • 电子商务网站开发目的和意义网站建设湖南岚鸿建设
  • 网站改版提交给百度个人定做衣服店
  • 网站接广告网站用途说明
  • 中兴豫建设管理有限公司网站中小企业网站建设济南兴田德润o厉害吗