2.03.05 原型 与 原型链

1.构造函数的弊端

  • 介绍:构造函数方法很好用,但是存在一个浪费内存的问题。我们以Dog对象为例:
    function Dog(name, breed, weight) {this.name = name;this.breed = breed;this.weight = weight;this.bark = function() {if (this.weight > 25) {alert(this.name + " says WOOF!");} else {alert(this.name + " says woof!");}};}var fido = new Dog("Fido", "柴犬", 38);var fluffy = new Dog("Fluffy", "贵宾", 30);var spot = new Dog("Spot", "吉娃娃", 10);console.log(fido.bark === fluffy.bark) // falseconsole.log(fido.bark === spot.bark) // falseconsole.log(fluffy.bark === spot.bark) // false
  • 上面会返回false的原因是因为 fido实例对象、fliffy实例对象与spot实例对象里面的方法bark(这是个应用数据类型)有不同的地址
  • 所以,构造函数的弊端就是:那就是对于每一个实例对象,bark()方法功能完全相同,但每个小狗对象都有自己的副本。每一次生成一个实例,都必须为重复的内容,多占用一些内存。这样会影响应用程序的性能,占用计算机资源。这可能是个大问题,在移动设备上尤其如此。
    • 而要解决以上问题这是我们需要充分利用JavaScript的对象模型。JavaScript对象模型基于原型的概念,在这种模型中,可通过扩展其他对象(即原型对象)来创建对象。

2.解决构造函数的弊端

  1. 解决办法:
    function Dog(name, breed, weight) {this.name = name;this.breed = breed;this.weight = weight;}// 这里给小狗原型添加了属性和方法。Dog.prototype.species = "犬科"Dog.prototype.bark = function() {if (this.weight > 25) {alert(this.name + " says WOOF!");}else {alert(this.name + " says woof!");}}; Dog.prototype.run = function() {alert(this.name + " Run!");}; Dog.prototype.wag = function() {alert(this.name + "Wag!"); }; // 像通常那样创建小狗对象。var fido = new Dog("Fido", "柴犬", 38);var fluffy = new Dog("Fluffy", "贵宾", 30);var spot = new Dog("Spot", "吉娃娃", 10);// 然后,像通常那样对每个小狗对象调用方法。每个小狗对象都从原型那里继承了这些方法。fido.bark();fido.run();fido.wag();fluffy.bark();fluffy.run();fluffy.wag();spot.bark();spot.run();spot.wag();console.log(fido.bark===fluffy.bark); //trueconsole.log(fido.bark===spot.bark); //trueconsole.log(fluffy.bark===spot.bark); //true
  • 注意:添加到原型的方法中的this指向调用它的实例对象

  • 图解:首先,需要创建小狗对象Fido、Fluffy和Spot的对象图,让它们继承新创建的小狗原型。为表示继承关系,我们将绘制从小狗实例到原型的虚线。注意,我们只将所有小狗都需要的方法和属性放在小狗原型中,因为所有小狗都将继承它们。对于所有随小狗对象而异的属性,如name,我们都将其都放在小狗实例中,因为每条小狗的这些属性都各不相同:

  1. 继承的工作原理
  • Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
  • 继承的方法和属性并不包含在各个小狗对象中,而是包含在原型中,上面的例子如何让小狗发出叫声呢?这正是继承的用武之地。对象调用方法时,如果在对象中找不到,将在原型中查找它
    • 首先,需要编写一些代码。例如,一个小狗对象调用方法bark的代码:
    • 为执行这些代码,我们在实例fido中查找方法bark,但没有找到。
    • 既然在实例fido中找不到方法bark,我们就沿继承链上移,在其原型中接着查找。
    • 在小狗原型中查找,发现其中确实有方法bark。
    • 最后,找到方法bark后,我们调用它,导致小狗对象fido发出叫声。
    • 属性的情况也一样。如果我们编写了需要获取fido.name的代码,将从fido对象中获取这个值。如果要获取fido.species的值,将首先在对象fido中查找;在这里找不到后,将接着在小狗原型中查找
  • 明白如何使用继承后,意味着,我们可以把那些不变的属性和方法,直接定义在prototype对象上。便可以创建大量的实例对象了。例如上面的小狗对象都能发出叫声,但依赖于小狗原型提供的方法bark。原型模式实现的代码重用,不仅只需在一个地方编写代码,而且让所有小狗实例都在运行阶段使用同一个bark方法,从而避免了庞大的运行阶段开销。
console.log(fido.bark === fluffy.bark) // true
console.log(fido.bark === spot.bark) // true
console.log(fluffy.bark === spot.bark) // true

3.原型

1.基于原型的语言

  • JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。(准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。)
  • 在传统的 OOP 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制,而是在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。
  • 注意: 理解对象的原型(可以通过Object.getPrototypeOf(obj)或者已被弃用的__proto__属性获得)与构造函数的prototype属性之间的区别是很重要的。前者是每个实例上都有的属性,后者是构造函数的属性。也就是说,Object.getPrototypeOf(new Foobar())和Foobar.prototype指向着同一个对象。

2.原型的概念

  1. 在javascript中, 每个函数都自带一个特殊的属性叫作原型(prototype)。
  2. 这个prototype属性的数据类型是object,也就是对象,因此称为原型对象。
  3. 在javascript中,函数也是一个对象,所以函数可以有属性。

3.认识特殊属性————原型(prototype)

1. 特殊点1

  • 构造函数实例出来的对象继承构造函数的prototype属性的所有属性和方法
    function Dog(name,weight) {this.name = name;this.weight = weight;}var spot = new Dog("Spot",10);console.log(spot);  //这里打印了一个Dog的一个实例对象spot// spot.bark();  //控制台反馈:spot.bark is not a function// console.log(spot.bark); //undefined/*Dog.prototype.bark=function(){console.log("say Woo woo!");}*///spot.bark();   //say Woo woo!

2.特殊点2

  • 之所以有特殊点1,是因为特殊点2的存在
  • 实例对象a在构造函数A构造出来的前提下,实例对象a在使用属性和方法的时候,会现在本身寻找,找不到就会从构造函数A的prototype中寻找
  1. 看看实例对象的内部
    function Dog(name,weight) {this.name = name;this.weight = weight;}Dog.prototype.bark=function(){console.log("say Woo woo!");}var spot = new Dog("Spot",10);console.log(spot);  //这里打印了一个Dog的一个实例对象spot
  • 实例对象的内部打印:
    Dog{name: "Spot"weight: 10[[Prototype]]: Object{bark: ƒ ()constructor: ƒ Dog(name,weight)[[Prototype]]: Object}}
  • 以看到实例对象spot本身并没有bark()这个方法,只是它里面有个[[Prototype]],[[Prototype]]里面有个bark()这个方法
  • 只是在Dog.prototype中添加了bark()方法,却被实例对象spot识别到了,所以说构造函数的prototype的属相和方法会被实例对象继承,但这些被继承的属性和方法虽被继承了,但他还是在构造函数的protitype里面,至于说spot为什么能识别到构造函数的prototype,就可以看看下面原型链的说法
  1. 重写实力对象的方法,不会影响原型
    function Dog(name,weight) {this.name = name;this.weight = weight;}Dog.prototype.bark=function(){console.log("say Woo woo!");}var spot = new Dog("Spot",10);var spot2 = new Dog("SPOT2",10);spot.bark=function(){console.log("hahahaha");}spot.bark();  //hahahahaspot2.bark();   //say Woo woo!
  • 在任何情况下,都可重写原型的属性和方法,为此只需在对象实例中提供它们即可。这之所以可行,是因为JavaScript总是先在对象实例(即具体的spot对象)中查找属性;如果找不到,再在原型中查找。因此,要为对象spot定制方法bark,只需在其中包含自定义的方法bark。这样,JavaScript查找方法bark以便调用它时,将在对象spot中找到它,而不用劳神去原型中查找。

4.原型链

1.属性__proto__

  • 每个对象都自带一个__proto__属性
  • 这个属性用于指向他的构造函数的原型prototype,就是 实例对象.__proto__与构造函数.prototype相等
  • 就是因为属性__proto__指向他的构造函数的原型prototype,所以实例对象能使用构造函数的原型prototype中的属性与方法,但这些属性与方法却不存在于实例对象身上
  • 属性__proto__也称为隐式原型
  • 这个属性现在被弃用了,但很多浏览器还支持这个效果,我们可以像不弃用之前那样正常使用
    function Dog(name,weight) {this.name = name;this.weight = weight;}Dog.prototype.bark=function(){console.log("say Woo woo!");}var spot = new Dog("Spot",10);console.log(spot.__proto__ === Dog.prototype);  //true

2.原型链

  1. 例子:
    function Dog(name,weight) {this.name = name;this.weight = weight;}var spot = new Dog("Spot",10);Dog.prototype.bark=function(){console.log("say Woo woo!");}console.log(spot.__proto__ === Dog.prototype);  //truespot.bark();   //say Woo woo!
  • 分析一下上面的继承:

    • 就像上面看到的, spot的__proto__ 属性就是Dog.prototype。
    • 但是这又有什么用呢? 好吧,当你访问 spot的一个属性, 浏览器首先查找 spot是否有这个属性.
    • 如果 spot没有这个属性, 然后浏览器就会在spot的__proto__中查找这个属性(也就是 Dog.prototype).
    • 如果 spot的__proto__有这个属性, 那么 spot的__proto__ 上的这个属性就会被使用.
    • 否则, 如果 spot的__proto__ 没有这个属性, 浏览器就会去查找 spot的__proto__ 的__proto__ ,看它是否有这个属性.
    • 默认情况下, 所有函数的原型属性的__proto__ 就是 window.Object.prototype. 所以 spot的__proto__ 的__proto__ (也就是 Dog.prototype 的__proto__ (也就是 Object.prototype)) 会被查找是否有这个属性.
    • 如果没有在它里面找到这个属性, 然后就会在 spot的__proto__ 的__proto__ 的__proto__ 里面查找.
    • 然而这有一个问题: spot的__proto__ 的__proto__ 的__proto__ 不存在.
    • 最后, 原型链上面的所有的__proto__ 都被找完了, 浏览器所有已经声明了的__proto__ 上都不存在这个属性,然后就得出结论,这个属性是 undefined.
  1. 概念:由每个对象的__proto__ 这样一层一层向上查找,所形成的链式结构,叫做原型链,每个对象的__proto__ 都指向他们的构造函数的原型prototype。

  2. 原型链的尽头

  • 因为在JavaScript中,function、object、数组等都是object对象类型,所以说js中万物皆对象,而这些都是Object这个构造函数(这个Object函数本身也是对象)实例出来的,所以原型链的尽头是Object.prototype
    // 原型链的尽头:console.log( Object.prototype.__proto__);// nullconsole.log( Object.prototype.__proto__ === null);// true
  1. JavaScript的内置的构造函数 的原型 的隐式原型
    //Date      日期对象//Array     数组对象//String    字符串对象//Number    数字对象//Function  函数对象//Object    对象(原始数据)console.log( Object.prototype.__proto__);// nullconsole.log( Object.prototype.__proto__ === null);// trueconsole.log(Date.prototype.__proto__===Object.prototype);//trueconsole.log(Array.prototype.__proto__===Object.prototype);//trueconsole.log(String.prototype.__proto__===Object.prototype);//trueconsole.log(Number.prototype.__proto__===Object.prototype);//trueconsole.log(Function.prototype.__proto__===Object.prototype);//true
  1. 函数对象
  2. 概念:JavaScript中函数就是对象,普通“键值对”对象其原型对象连接到Object.prototype。函数对象会隐藏连接到Function.prototype(Function.prototype对象本身连接到Object.prototype)。
  3. 我们知道每个函数在创建时会配有一个prototype属性,而且prototype属性的值是一个拥有constructor属性且值为该函数的对象。
    Function.prototype.OK="欧宽";function Dog(name,weight) {this.name = name;this.weight = weight;}console.log(Dog.OK); //欧宽console.log(Dog.__proto__===Function.prototype);//true

3.prototype与__proto__的区别

  • prototype是函数的属性
  • __proto__是对象属性,但函数也是对象,所以函数也有__proto__属性
  • 函数可也拥有prototype,proto ;对象只拥有__proto__
    function Dog(name,weight) {this.name = name;this.weight = weight;}var spot = new Dog("Spot",10);console.log(Dog.__proto__===Function.prototype); //trueconsole.log(Dog.prototype!=undefined);//trueconsole.log(Dog.__proto__!=undefined);//trueconsole.log(spot.prototype); //undefinedconsole.log(spot.__proto__!=undefined);//true

5.其他杂项

  1. 往构造函数的原型添加方法也可以这么写:
    Dog.prototype.wag = function() {alert(this.name + "Wag!"); }; 可以写成:Dog.prototype={constructor:Dog,     //此处作用是为了指向构造函数Snake,不是指向Objectwag:function() {alert(this.name + "Wag!");}}
  1. 给内置对象的原型添加方法
    var arr=[1,2,3,1,1,2,3,5,7];function unique(arr){for(var i=0;i<arr.length;i++){ //遍历数组每一个元素while((arr.indexOf(arr[i],i+1)) != -1){    //遍历arr[i]的后面的元素,  使用indexOf寻找arr[i]后面的元素是否与arr[i]相同//当返回值!=-1时,证明后面的元素与arr[i]有相同的,就是重复的元素//我们需要把它切掉 var index = arr.indexOf(arr[i], i+1); //重复元素的下标arr.splice(index, 1);   //把重复元素的元素切掉}}}unique(arr);console.log("使用unique去重后得到 => "+arr); //使用unique去重后得到 => 1,2,3,5,7var arr2=[1,2,3,1,1,2,3,5,7];function unique2(){for(var i=0;i<this.length;i++){while((this.indexOf(this[i],i+1)) != -1){    var index = this.indexOf(this[i], i+1); this.splice(index, 1); }}}Array.prototype.unique=unique2;arr2.unique();console.log("使用Array原型unique去重后得到 => "+arr2); //使用Array原型unique去重后得到 => 1,2,3,5,7
  • 因为函数unique2在被arr2调用的时候this的指向arr2,所以arr2调用unique2可以不带参数
  1. constructor 属性
  • 每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于构造此实例对象的构造函数。
    function Dog(name,weight) {this.name = name;this.weight = weight;}var spot = new Dog("Spot",10);console.log(spot.constructor===Dog);//trueconsole.log(Dog.prototype.constructor===Dog);//true
  • 因此有一个小技巧:你可以在 constructor 属性的末尾添加一对圆括号(括号中包含所需的参数),从而用这个构造器创建另一个对象实例。毕竟构造器是一个函数,故可以通过圆括号调用;只需在前面添加 new 关键字,便能将此函数作为构造器使用。
    function Dog(name,weight) {this.name = name;this.weight = weight;}var spot = new Dog("Spot",10);console.log(spot.constructor===Dog);//trueconsole.log(Dog.prototype.constructor===Dog);//truevar spot2 = new spot.constructor("SSSSSSSSpot",100000);console.log(spot2); //Dog{name: "SSSSSSSSpot" , weight: 100000}console.log(spot2.name);//SSSSSSSSpot
  1. prototype模式的验证方法
  • 为了配合prototype属性,Javascript定义了一些辅助方法,帮助我们使用它
  1. isPrototypeOf(): 这个方法用来判断,某个proptotype对象和某个实例之间的关系。表示对象是否存在于另一个对象的原型链中。
    function Dog(name,weight) {this.name = name;this.weight = weight;}var spot = new Dog("Spot",10);console.log(Dog.prototype.isPrototypeOf(spot));  //true
  1. hasOwnProperty():每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。如果属性是在对象实例中定义的,这个方法将返回true。如果属性不是在对象实例中定义的,但能够访问它,就可认为它肯定是在原型中定义的。
    function Dog(name,weight) {this.name = name;this.weight = weight;}var spot = new Dog("Spot",10);Dog.prototype.owner="欧宽";console.log(spot.hasOwnProperty("name"));// trueconsole.log(spot.hasOwnProperty("owner")); //false
  1. in运算符
  • in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。
  • in运算符还可以用来遍历某个对象的所有属性。
    function Dog(name,weight) {this.name = name;this.weight = weight;}var spot = new Dog("Spot",10);Dog.prototype.owner="欧宽";console.log("name" in spot); // true;console.log("owner" in spot); // true;for(var val in spot){console.log(val);}/*控制台输出:nameweightowner*/
  1. 原型链的图解:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uy9IqrVS-1649206160016)(./原型链图示.png)]

    function Dog(name,weight) {
    this.name = name;
    this.weight = weight;
    }

    var spot = new Dog(“Spot”,10);
    Dog.prototype.owner=“欧宽”;

    console.log(“name” in spot); // true;
    console.log(“owner” in spot); // true;

    for(var val in spot){
    console.log(val);
    }
    /*
    控制台输出:
    name
    weight
    owner
    */


5. 原型链的图解:
![请添加图片描述](https://img-blog.csdnimg.cn/7063b75cb69b472d9e7d34fe2d28b090.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Lul5ZOyX29r,size_20,color_FFFFFF,t_70,g_se,x_16)

2.03.05 原型与原型链相关推荐

  1. 【03】图解原型和原型链by魔芋

    [03]图解原型和原型链 一图胜前言 请先结合图解原型和原型链这张图. 可以分为4种情况. 情况1: Object有: constructor:是Function. __proto__:是Functi ...

  2. 深入理解JavaScript系列(5):强大的原型和原型链

    前言 JavaScript 不包含传统的类继承模型,而是使用 prototypal 原型模型. 虽然这经常被当作是 JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大.实 ...

  3. 浅谈JS原型与原型链(一)

    最近学习JavaScript原型与原型链的时候,被这块知识烧得脑壳疼,prototype与__proto__混淆不清.网上各种图解,都画的好复杂,绕老绕去,不明所以,看得越来越糊涂.还是亲自动手敲敲, ...

  4. JS原型与原型链终极详解

     一. 普通对象与函数对象   JavaScript 中,万物皆对象!但对象也是有区别的.分为普通对象和函数对象,Object ,Function 是JS自带的函数对象.下面举例说明 functi ...

  5. js原型和原型链_重学js --原型与原型链

    一.什么是原型: .每个对象都有一个__proto__属性,并且指向它的prototype原型对象 每个构造函数都有一个prototype原型对象 prototype原型对象里的constructor ...

  6. (转)【javascript基础】原型与原型链

    原文地址:http://www.cnblogs.com/allenxing/p/3527654.html 前言 原型是什么 理解原型对象 原型对象 isPrototypeOf hasOwnProper ...

  7. javascript之原型与原型链

    前言   了解JavaScript的原型与原型链有助于我们更深层次的理解这门语言,看过很多相关的文章,写的都很好,下面是根据自己的理解,一步步揭开原型与原型链 正文 一.数据类型   在JavaScr ...

  8. JavaScript原型与原型链(总结篇)

    系列文章推荐 JavaScript原型与原型链(基础篇) JavaScript原型与原型链(进阶篇) JavaScript原型与原型链(总结篇) 1 构造函数和实例对象 构造函数的prototype属 ...

  9. JavaScript对象、原型、原型链知识总结思维导图

    这个思维导图是我对Object,原型,原型链等知识的总结,主要参考高程一书第六章,写完才发现这么多,以后可能会进行精简.内容可能会出现差错,欢迎批评指正.下载==>Github ECMAScri ...

最新文章

  1. leetCode 46. Permutations 回溯问题 | Medium
  2. Win7 IIS7.5运行ASP时出现500错误的解决办法
  3. Session对象的清空
  4. java并发编程核心方法与框架_Java并发编程核心方法与框架-Future和Callable的使用...
  5. Hadoop不适合哪些场景 哪些场景适合?
  6. 听一个内行人讲云原生简史
  7. 六:SpringCloud-Config
  8. 创新元旦新年PSD分层海报,新气象开启!
  9. 【数论Day1】 最大公约数(gcd)题目
  10. 随手练—— 洛谷-P2945 Sand Castle(贪心)
  11. UIControl IOS控件编程—IOS开发
  12. 【计算机图形学】Laplacian_Surface_Editiing拉普拉斯曲面编辑算法
  13. Python Intro - xrange obsoleted by Python3
  14. Telltale:简化了Netflix应用程序监视
  15. pandas按条件筛选数据
  16. Credential Harvester Attack Method获得用户信息
  17. 波士顿房价预测python决策树_波士顿房价预测 - 最简单入门机器学习 - Jupyter
  18. 登录计算机隐藏用户名,win10系统隐藏登录界面administrator用户名的办法介绍
  19. 混合索引java代码,Elasticsearch 实现拼音,中文,首字母混合搜索
  20. 通过需求条目的层次去解决问题

热门文章

  1. 计算机桌面垃圾筒怎么恢复出来,桌面垃圾桶被误删了,怎么恢复
  2. win10系统Apache无法启动怎么办?
  3. Genesis 社区答疑—第一期
  4. DSm安装mysql_群晖Synology DSM系统安装教程
  5. 为资产分类定义折旧范围_FI-AA配置逻辑
  6. 信息学奥赛一本通1358 中缀表达式值(expr) ()
  7. 告警数下降10倍,携程实时智能检测平台实践
  8. epplus保存为流_.NET Core 2从内存流下载Excel文件 - c#
  9. 特斯拉“翻脸”,拼多多“翻车”
  10. linux中comm命令用法