《你不知道的JavaScript》:js构造函数的假面

本篇继续看下对象的内置属性[[Prototype]]

在js中[[Prototype]]属性最常出现的地方构造函数添加“原型方法”上面了。

1function Foo(name){
2    this.name = name;
3}
4Foo.prototype.showName = function(){
5    return this.name;
6}
7var obj = new Foo("nitx");
8obj.showName();     // nitx

瞧,上面是js中最常见的类构造函数创建新实例对象,然后实例对象调用类的成员方法的过程。

唔,习惯了java之类的面向对象思维的同学一看,没毛病,就是这么个道理,对Foo类的有参构造方法进行new一下,创建出一个复制了Foo成员属性和成员方法的新对象obj。

但是!

js中是不存在类的!

js是基于原型的,面向原型的。

上面这段代码的正确描述应是:普通函数Foo拥有一个所有函数都有的公有且不可枚举的属性[[Prototype]],这个属性的作用就是指向另一个对象,这个对象通常被称为Foo的原型,因为通常是通过名为Foo.prototype的属性引用来访问它。对象a是在调用 new Foo()时创建的,最后会被关联到"Foo点prototype"对象上,也就是对象a会被关联Foo.prototype。注意,是关联哦,不是像类创建对象那样复制出一个新对象那样。用js的方式理解就是,对象a的原型就指向了Foo.prototype对象了,来验证下:

1function Foo(){}
2var obj = new Foo();
3Object.getPrototypeOf(obj) === Foo.prototype;    // true

证明new Foo()创建出来的新对象obj的原型的确指向了Foo.prototype这个对象了。

看到这里,需要仔细思考下上面说的对象obj关联到Foo.prototype对象的意义所在了。正是这个“关联”用词把js的原型和类彻底区分开来。java等面向对象语言中,类实例化是一个复制过程,可以复制多次创建多个新对象,这个复制的过程就是“把类的行为复制到物理对象中”,对于每个实例对象来说都会重复这一过程。

但在js中,却没有这样的重复机制,不能创建一个类的多个实例,只能创建多个对象,它们的内置属性[[Prototype]]指向(关联)的的是同一个对象。但是在默认情况下不会复制,因此这些对象不会完全失去联系,它们是关联的。

new Foo()会创建一个新对象obj,这个新对象obj的内置属性[[Prototype]]关联的是Foo.prototype对象。最后我们就得到两个对象,它们之间相互关联,整个过程就是这样。我们没有初始化一个类,实际上我们都没有从“类”中复制任何行为到一个对象中,只是让两个对象相互关联。

理解了上面的代码的原理,再来回头看下所谓的“构造函数Foo”。它其实不是一个真正意义上的构造函数,因为js中都没有类,就更别提哪来的构造函数了。Foo其实就是一个js最普通的函数罢了,只有当使用 new 关键字来调用函数Foo时,Foo才被称为构造函数,同时为了与普通函数区分,学习了真正的构造函数那样写作首字母大写,以示把它当作构造函数。注意,只是当作,实际这个函数Foo依然是js世界中最普通的一个函数,本质没有改变,只是称呼改变罢了。

除了令人迷惑的“构造函数”之称,还有个容易被搞混的东西constructor,看下面代码:

1function Foo(){};
2Foo.prototype.constructor === Foo;      //true
3var obj = new Foo();
4obj.constructor === Foo;    //true

看上面代码,可以暂时这样理解:Foo.prototype默认有一个公有并且不可枚举的属性constructor,这个属性引用的是对象关联的函数,上例关联的是Foo函数。另外通过”构造函数“调用new Foo()创建的对象obj也有一个属性constructor,指向是是创建这个对象的函数。

但这里注意了,实际上new调用创建的新对象obj默认是没有constructor这个属性的,虽然obj.constructor确实指向了Foo函数,但是它其实是通过原型链委托给了Foo.prototype对象,obj.constructor行为访问的其实是obj对象的原型对象Foo.prototype对象上的属性constructor的值。

1function Foo(){};
2var obj = new Foo();
3console.log(Object.getPrototypeOf(obj) === Foo.prototype);  // true
4console.log(obj.constructor);                        // Foo
5console.log(obj.hasOwnProperty("constructor"));     // fakse
6console.log(Foo.prototype.constructor);             // Foo
7console.log(Foo.prototype.hasOwnProperty("constructor"));       // true

所以上例代码可以看到,obj对象本身确实是没有内置属性constructor,而对obj.constructor的访问其实全是委托给obj对象的原型链上层对象Foo.prototype对象的。

此时可以思考一个问题,看如下代码:

1function Foo(){};
2Foo.prototype = {a:10};
3var obj = new Foo();
4obj.constructor === Foo;    // 此时还是 true么??????

答案是false!因为Foo.prototype = {a:10};这段代码已经把Foo.prototype指向对象{a:2}了,这是一个字面量方法创建的对象,它的原型是Object.prototype。所以此时obj.constructor值为Object:

1obj.constructor === Object;     // true

此时如果想要将obj.constructor的指向重新修改到预期的Foo身上,就需要重新定义Foo.prototype的属性constructor的特性了,用什么方法呢?

 1function Foo(){};
 2Foo.prototype = {a: 10};
 3var obj = new Foo();
 4obj.constructor === Object;     // true
 5Object.defineProperty(Foo.prototype, "constructor", {
 6    value: Foo,
 7    writable: true,
 8    enumerable: false,
 9    configurable: true
10})
11obj.constructor === Foo;       // true

好,这样就完成constructor指向的修改。

下面来总结下本篇所学:

  • js中没有类
  • new Foo()中Foo本质不是传统面向对象语言中类中的构造函数,而是js普通函数
  • 构造函数创建的新对象没有constructor属性,访问它只能通过原型委托进一步访问Foo.prototype对象本身的constructor属性值
  • Foo.prototype对象本身的constructor指向可以修改,所以不推荐在实际开发中通过 obj.constructor来引用Foo函数

-------------------------------- 热门文章 --------------------------------

设计模式>>>

javascript设计模式一: 单例模式

javascript设计模式二:策略模式

javascript设计模式三:代理模式

javascript设计模式四:迭代器模式

javascript设计模式五:原型模式

javascript设计模式六:发布-订阅模式(观察者模式)

javascript设计模式七:模板方法模式

javascript设计模式八:职责链模式

javascript设计模式九:中介者模式

javascript设计模式十:装饰者模式

本文分享自微信公众号 - 前端小二(frontendxiao2)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-01-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏蚂蚁开源社区

如何使用JavaScript来判断是否为移动设备?

  由于移动设备的显示屏幕相对于桌面显示器来说小很多,在桌面显示器上能够正常显示的内容,到了移动设备中就不正常了。为了实现移动端和桌面端的相互跳转,我们可以通过...

10010
来自专栏Tech爬虫(公众号php_pachong)

jQuery中的$是什么

但在JS应用库JQUERY的作者将之做为一个自定义函数名了,这个函数是获取指定网页元素的函数,使用非常之频繁,所以好多新手不知道,还以为$是JS的什么特殊语法。

7120
来自专栏Tech爬虫(公众号php_pachong)

jquery 使用方法

jQuery是目前使用最广泛的javascript函数库。据统计,全世界排名前100万的网站,有46%使用jQuery,远远超过其他库。微软公司甚至把jQuer...

9710
来自专栏Tech爬虫(公众号php_pachong)

使用Firefox轻松调试JS

Firefox调试JS的功能真的很不错,推荐一下! 在页面上点击右键,再点击“查看元素”,如图:

8320
来自专栏蚂蚁开源社区

微信小程序仿阿姨帮【含教程】

官方解释,微信小程序,简称小程序,是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或搜一下即可打开应用。简而言之,就是用户需要的时...

21050
来自专栏Tech爬虫(公众号php_pachong)

thinkphp-ajax异步验证之ajaxReturn的使用

但是,观察输出的信息,发现serialize()方法做的是将表单中的数据以htpp请求格式拼接成字符串。

9030
来自专栏蚂蚁开源社区

编程有感,Web 新时代的机遇与挑战

技术决定了业务需求的多样性,而业务需求的多样性又推动着技术不断向前发展,两者相辅相成最终才能推动行业整体的发展和进步。

8220
来自专栏蚂蚁开源社区

【教程下载】HTML5游戏开发(全)

HTML5有望成为网络游戏开发的热门新平台。HTML5游戏能够运行于包括iPhone系列和iPad系列在内的计算机、智能手机以及平板电脑上。今天,我们加入HTM...

15310
来自专栏阿策小和尚

【Flutter 专题】28 图解 ListView/GridView 混用时滑动冲突小尝试

和尚在学习过程中会在一个 Page 页面同时用到 GridView 和 ListView 或多个 ListView,此时就会遇到常见的滑动冲突问题。...

12020
来自专栏程序员的知识天地

只要十分钟,用Python实现自动化水军评论

自己在写文章的时候,也有到处去逛一逛,渐渐发现了一些有意思的事,经常会有人用同样的评论到处刷,不知道是为了加没什么用的积分,还是纯粹为了表达楼主好人。那么问题来...

13320

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励