原型陷阱
来源:互联网 发布:蒙泰软件下载官方网站 编辑:程序博客网 时间:2024/05/20 03:41
为什么要写这篇呢?前段时间在看别人用js写一个flappy bird游戏的源码的时候,发现里面用到了借用构造器这个方法来实现对象之间的继承,自己看得有一脸蒙B,毕竟当初继承这方面没怎么看,很多东西也没用过,都忘记了,所以自己把书找来又重新看了一遍,从最开始的对象,到原型,再到继承,发现又收获了很多知识,赶紧来记录一下;这篇就先写一下"原型陷阱”吧。
先用书上的例子说明一下什么是原型陷阱吧。
var obj={ };
obj.__proto__=Dog.prototype;
var result=Dog.call(obj);
return typeof result==='object'? result :obj;
其实这里我们可以发现,如果构造器函数返回的是一个非对象类型(或者不返回)时,会正常返回this;如果返回的是一个对象,那么我们将得到的是这个对象,说起来很模糊,我们用例子来说明一下;
根据上面的代码,我们benji是Dog实例出来的对象,那么我们benji.tail会等于true,这一点毋庸置疑;但是我们改一下代码呢?
如果是这样的代码,我们benji.tail则是false; 这里是我们需要注意的一个点;
prototype是每一个函数都有的属性,叫做原型,只有这个函数被当做构造器函数的时候才起作用;而.__proto__是每个对象的属性,指向的是这个对象的构造器函数的原型;
在构造器函数的原型中再添加属性
这里我们发现无论是在添加原型方法之前实例出来的对象,还是之后实例出来的对象,都能使用这个新方法;
这个很简单嘛,rusty.__proto__=Dog.prototype
用一个自定义的新对象完全覆盖掉原有的原型对象
然后就出现了一个很奇怪的现象:
我们新实例出来的对象可以访问新添加的原型属性,但是不能使用以前的原型属性,但是以前实例出来的对象和它正好相反;
问题出在哪里呢?
我们可以利用constructor这个属性来帮我们查找一下;
这里我们需要注意一下:constructor是原型对象上的一个属性,默认指向这个原型的构造函数。
举个栗子:
然后我们再这样操作一下:
我来解释一下,刚前面说了constructor是原型上的方法,我们实例出来的对象其实是没有的,他们是通过__proto__去寻找其构造器函数的原型上使用的这个方法;
lucy.constructor其实是lucy.__proto__.constructor
我们发现他们的构造器函数错了,所以其函数上的原型也是错了,自如也找不到原型上定义的方法了;
这里我们终于知道排查到错误的原因了,是因为实例的构造器函数错了,为什么会错呢?是因为问我们完全覆盖prototype其实是对象赋值,对象赋值是传的引用,导致我们原型对象的地址发送了改变;
上面这一句其实可以改成
而我们这个obj对象的构造器函数是Object这个函数;
解决办法就是重新把其原型上的constructor重置一下;
到这里我觉得自己明白了原型陷阱的原因了,不过这里我当初有一个小纠结的地方,后来写完这篇后解惑了;
先用书上的例子说明一下什么是原型陷阱吧。
新建一个构造器函数,然后实例出1个对象
function Dog(){ this.tail=true;}var benji=new Dog();
因为是带着学习的目的,所以我想把尽可能多的知识点就提一下;
var obj={ };
obj.__proto__=Dog.prototype;
var result=Dog.call(obj);
return typeof result==='object'? result :obj;
其实这里我们可以发现,如果构造器函数返回的是一个非对象类型(或者不返回)时,会正常返回this;如果返回的是一个对象,那么我们将得到的是这个对象,说起来很模糊,我们用例子来说明一下;
根据上面的代码,我们benji是Dog实例出来的对象,那么我们benji.tail会等于true,这一点毋庸置疑;但是我们改一下代码呢?
function Dog(){ this.tail=true; return { tail:false; }}var benji=new Dog();
如果是这样的代码,我们benji.tail则是false; 这里是我们需要注意的一个点;
prototype是每一个函数都有的属性,叫做原型,只有这个函数被当做构造器函数的时候才起作用;而.__proto__是每个对象的属性,指向的是这个对象的构造器函数的原型;
在构造器函数的原型中再添加属性
Dog.prototype.say=function( ){ return 'woof';}
这里我们发现无论是在添加原型方法之前实例出来的对象,还是之后实例出来的对象,都能使用这个新方法;
var rusty=new Dog();rusty.say();//woofbenji.say();//woof
这个很简单嘛,rusty.__proto__=Dog.prototype
用一个自定义的新对象完全覆盖掉原有的原型对象
Dog.prototype={ hair:true}
然后就出现了一个很奇怪的现象:
var lucy=new Dog();lucy.say();//报错lucy.hair//truebenji.say();//woofbenji.hair//报错
我们新实例出来的对象可以访问新添加的原型属性,但是不能使用以前的原型属性,但是以前实例出来的对象和它正好相反;
问题出在哪里呢?
我们可以利用constructor这个属性来帮我们查找一下;
这里我们需要注意一下:constructor是原型对象上的一个属性,默认指向这个原型的构造函数。
举个栗子:
function fn(){}fn.prototype.constructor===fn //true
然后我们再这样操作一下:
lucy.constructor //function Object(){[native code]}benji.constructor // function Dog(){this.tail=true}
我来解释一下,刚前面说了constructor是原型上的方法,我们实例出来的对象其实是没有的,他们是通过__proto__去寻找其构造器函数的原型上使用的这个方法;
lucy.constructor其实是lucy.__proto__.constructor
我们发现他们的构造器函数错了,所以其函数上的原型也是错了,自如也找不到原型上定义的方法了;
这里我们终于知道排查到错误的原因了,是因为实例的构造器函数错了,为什么会错呢?是因为问我们完全覆盖prototype其实是对象赋值,对象赋值是传的引用,导致我们原型对象的地址发送了改变;
Dog.prototype={ hair:true}
上面这一句其实可以改成
var obj={ hair:true}Dog.prototype=obj
而我们这个obj对象的构造器函数是Object这个函数;
解决办法就是重新把其原型上的constructor重置一下;
Dog.prototype.constructor=Dog;
到这里我觉得自己明白了原型陷阱的原因了,不过这里我当初有一个小纠结的地方,后来写完这篇后解惑了;
function fn(){}fn.constructor//function Function() { [native code] }
这里我刚才是的疑惑是函数不是有prototype属性么?而这个属性的prototype的cnstructor属性指向的就是这个函数本身啊,为毛这里会是Function,后来一看是我傻逼了,fn去调用constructor属性的时候,发现自己没有的时候,会去.__proto__去寻找,而我以为是去其prototype中寻找~~╮(╯▽╰)╭ 好蠢,糊涂了,还好不是面试;(其实所有函数都是被Function 这个构造器函数实例出来的;)
阅读全文
0 0
- 原型陷阱
- 一些原型陷阱
- JavaScript原型继承的陷阱
- 1.constructor prototype详解 原型陷阱
- “陷阱”
- 陷阱!!!!!!!!!
- 原型
- 原型
- 原型
- 原型
- 原型
- 原型
- 原型
- 原型
- 原型
- 原型
- 原型
- 原型
- Win7系统下VS2005_2008不识别WinCE5/CE6 SDK解决办法
- u盘安装ubuntu linux系统
- C语言字符串操作相关函数
- 【PHP基础知识】——文件及目录操作
- MOOC清华《面向对象程序设计》第1章:源文件的拆分与多文件的编译运行
- 原型陷阱
- 机器学习面试
- Linux下单分支if语句下 unary operator expected错误解决方法
- 2016区域赛青岛赛区总结(2016/11/15)
- 微信小程序开发-自定义模态对话框实例
- Spring Cloud入门二:eureka集群
- JDBC中Statement、PreparedStatement 、CallableStatement 区别和联系
- HDU 6080 度度熊保护村庄(计算几何+floyd)
- java 静态方法和实例方法的区别