深入理解js构造函数
JavaScript对象的创建方式
在JavaScript中,创建对象的方式包括两种:对象字面量和使用new表达式。对象字面量是一种灵活方便的书写方式,例如:
1
2
3
4
5
6
|
var o1 = { p:”I’m in Object literal”, alertP:function(){ alert( this .p); } } |
这样,就用对象字面量创建了一个对象o1,它具有一个成员变量p以及一个成员方法alertP。这种写法不需要定义构造函数,因此不在本文的讨论范围之内。这种写法的缺点是,每创建一个新的对象都需要写出完整的定义语句,不便于创建大量相同类型的对象,不利于使用继承等高级特性。
new表达式是配合构造函数使用的,例如new String(“a string”),调用内置的String函数构造了一个字符串对象。下面我们用构造函数的方式来重新创建一个实现同样功能的对象,首先是定义构造函数,然后是调用new表达式:
1
2
3
4
5
6
7
|
function CO(){ this .p = “I’m in constructed object”; this .alertP = function(){ alert( this .p); } } var o2 = newCO(); |
那么,在使用new操作符来调用一个构造函数的时候,发生了什么呢?其实很简单,就发生了四件事:
1
2
3
4
|
var obj ={}; obj.__proto__ = CO.prototype; CO.call(obj); return obj; |
第一行,创建一个空对象obj。
第二行,将这个空对象的__proto__成员指向了构造函数对象的prototype成员对象,这是最关键的一步,具体细节将在下文描述。
第三行,将构造函数的作用域赋给新对象,因此CA函数中的this指向新对象obj,然后再调用CO函数。于是我们就给obj对象赋值了一个成员变量p,这个成员变量的值是” I’min constructed object”。
第四行,返回新对象obj。当构造函数里包含返回语句时情况比较特殊,这种情况会在下文中说到。
正确定义JavaScript构造函数
不同于其它的主流编程语言,JavaScript的构造函数并不是作为类的一个特定方法存在的;当任意一个普通函数用于创建一类对象时,它就被称作构造函数,或构造器。一个函数要作为一个真正意义上的构造函数,需要满足下列条件:
1、 在函数内部对新对象(this)的属性进行设置,通常是添加属性和方法。
2、 构造函数可以包含返回语句(不推荐),但返回值必须是this,或者其它非对象类型的值。
上文定义的构造函数CO就是一个标准的、简单的构造函数。下面例子定义的函数C1返回了一个对象,我们可以使用new表达式来调用它,该表达式可以正确返回一个对象:
1
2
3
4
5
6
7
8
|
function C1(){ var o = { p:”I’m p in C1” } return o; } var o1 = new C1(); alert(o1.p); //I’m p in C1 |
但这种方式并不是值得推荐的方式,因为对象o1的原型是函数C1内部定义的对象o的原型,也就是Object.prototype。这种方式相当于执行了正常new表达式的前三步,而在第四步的时候返回了C1函数的返回值。该方式同样不便于创建大量相同类型的对象,不利于使用继承等高级特性,并且容易造成混乱,应该摒弃。
一个构造函数在某些情况下完全可以作为普通的功能函数来使用,这是JavaScript灵活性的一个体现。下例定义的C2就是一个“多用途”函数:
1
2
3
4
5
6
7
8
9
10
|
function C2(a, b){ this .p = a + b; this .alertP = function(){ alert( this .p); } return this .p; //此返回语句在C2作为构造函数时没有意义 } var c2 = new C2( 2 , 3 ); c2.alertP(); //结果为5 alert(C2( 2 , 3 )); //结果为5 |
该函数既可以用作构造函数来构造一个对象,也可以作为普通的函数来使用。用作普通函数时,它接收两个参数,并返回两者的相加的结果。为了代码的可读性和可维护性,建议作为构造函数的函数不要掺杂除构造作用以外的代码;同样的,一般的功能函数也不要用作构造对象。
为什么要使用构造函数
根据上文的定义,在表面上看来,构造函数似乎只是对一个新创建的对象进行初始化,增加一些成员变量和方法;然而构造函数的作用远不止这些。为了说明使用构造函数的意义,我们先来回顾一下前文提到的例子。执行var o2 = new CO();创建对象的时候,发生了四件事情:
1
2
3
4
|
var obj ={}; obj.__proto__ = CO.prototype; CO.call(obj); return obj; |
我们说最重要的是第二步,将新生成的对象的__prop__属性赋值为构造函数的prototype属性,使得通过构造函数创建的所有对象可以共享相同的原型。这意味着同一个构造函数创建的所有对象都继承自一个相同的对象,因此它们都是同一个类的对象。关于原型(prototype)和继承的细节,笔者会再另一篇文章中深入说明。
在JavaScript标准中,并没有__prop__这个属性,不过它现在已经是一些主流的JavaScript执行环境默认的一个标准属性,用于指向构造函数的原型。该属性是默认不可见的,而且在各执行环境中实现的细节不尽相同,例如IE浏览器中不存在该属性。我们只要知道JavaScript对象内部存在指向构造函数原型的指针就可以了,这个指针是在调用new表达式的时候自动赋值的,并且我们不应该去修改它。
在构造对象的四个步骤中,我们可以看到,除第二步以外,别的步骤我们无须借助new表达式去实现,因此new表达式不仅仅是对这四个步骤的简化,也是要实现继承的必经之路。
容易混淆的地方
关于JavaScript的构造函数,有一个容易混淆的地方,那就是原型的constructor属性。在JavaScript中,每一个函数都有默认的原型对象属性prototype,该对象默认包含了两个成员属性:constructor和__proto__。关于原型的细节就不在本文赘述了,我们现在关心的是这个constructor属性。
按照面向对象的习惯性思维,我们说构造函数相当于“类”的定义,从而可能会认为constructor属性就是该类实际意义上的构造函数,在new表达式创建一个对象的时候,会直接调用constructor来初始化对象,那就大错特错了。new表达式执行的实际过程已经在上文中介绍过了(四个步骤),其中用于初始化对象的是第三步,调用的初始化函数正是“类函数”本身,而不是constructor。如果没有考虑过这个问题,这一点可能不太好理解,那就让我们举个例子来说明一下吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function C3(a, b){ this .p = a + b; this .alertP = function(){ alert( this .p); } } //我们定义一个函数来覆盖C3原型中的constructor,试图改变属性p的值 function fake(){ this .p = 100 ; } C3.prototype.constructor = fake; //覆盖C3原型中的constructor var c3 = new C3( 2 , 3 ); c3.alertP(); //结果仍然为5 |
上述代码手动改变了C3原型中的constructor函数,然而却没有对c3对象的创建产生实质的影响,可见在new表达式中,起初始化对象作用的只能是构造函数本身。那么constructor属性的作用是什么呢?一般来说,我们可以使用constructor属性来测试对象的类型:
1
2
|
var myArray = [ 1 , 2 , 3 ]; (myArray.constructor == Array); // true |
这招对于简单的对象是管用的,涉及到继承或者跨窗口等复杂情况时,可能就没那么灵光了:
1
2
3
4
5
6
7
|
function f() { this .foo = 1 ;} function s() { this .bar = 2 ; } s.prototype = new f(); // s继承自f var son = new s(); // 用构造函数s创建一个子类对象 (son.constructor == s); // false (son.constructor == f); // true |
这样的结果可能跟你的预期不相一致,所以使用constructor属性的时候一定要小心,或者干脆不要用它。
PS:本文复制与其它博客上的。
深入理解js构造函数的更多相关文章
- 深入理解js——构造函数的继承
看了阮一峰的网络日志(http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html),记录一下构 ...
- 全面理解js面向对象
前言 当今 JavaScript 大行其道,各种应用对其依赖日深.web 程序员已逐渐习惯使用各种优秀的 JavaScript 框架快速开发 Web 应用,从而忽略了对原生 JavaScript 的学 ...
- 谈谈JS构造函数
//构造函数 //使自己的对象多次复制,同时实例根据设置的访问等级可以访问其内部的属性和方法 //当对象被实例化后,构造函数会立即执行它所包含的任何代码 function myObject(msg) ...
- js对象详解(JavaScript对象深度剖析,深度理解js对象)
js对象详解(JavaScript对象深度剖析,深度理解js对象) 这算是酝酿很久的一篇文章了. JavaScript作为一个基于对象(没有类的概念)的语言,从入门到精通到放弃一直会被对象这个问题围绕 ...
- Class和普通js构造函数的区别
Class 在语法上更加贴合面向对象的写法 Class 实现继承更加易读.易理解 更易于写 java 等后端语言的使用 本质还是语法糖,使用 prototype Class语法 typeof Math ...
- 深入理解JS this,作用域
深入理解JS this 阮一峰博客链接http://www.ruanyifeng.com/blog/2010/04/using_this_keyword_in_javascript.html this ...
- 简单粗暴地理解js原型链–js面向对象编程
简单粗暴地理解js原型链–js面向对象编程 作者:茄果 链接:http://www.cnblogs.com/qieguo/archive/2016/05/03/5451626.html 原型链理解起来 ...
- javascript深入理解js闭包(转)
javascript深入理解js闭包 转载 2010-07-03 作者: 我要评论 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. ...
- 【学习笔记】六:面向对象的程序设计——理解JS中的对象属性、创建对象、JS中的继承
ES中没有类的概念,这也使其对象和其他语言中的对象有所不同,ES中定义对象为:“无序属性的集合,其属性包含基本值.对象或者函数”.现在常用的创建单个对象的方法为对象字面量形式.在常见多个对象时,使用工 ...
随机推荐
- WEB启动时就加载servlet的dopost方法
web启动的时候可以加载servlet的init方法,无法加载dopost方法,如果你需要什么内容在启动的时候执行,可以将内容放到init方法里面,dopost方法,是在客户端使用post请求的时候才 ...
- 将图片插入到excel中
static void Main(string[] args) { //说明:插入图片 //1.创建EXCEL中的Workbook IWorkbook myworkbook = new HSSFWor ...
- 一张图轻松搞懂javascript event对象的clientX,offsetX,screenX,pageX区别
总是会被javascript的event对象的clientX,offsetX,screenX,pageX 弄得头晕,于是决定做个图来区分一下(画得我手那个酸呀....) 先总结下区别: event.c ...
- Uploadify/uploadifive上传(中文文档)
Uploadify是一款基于JQuery的优秀的文件/图片上传的插件,有基于Flash和HTML5两种版本. Uploadify/uploadifive主要特点有: 1. 多文件上传 2. 个性化设 ...
- 目标检测方法总结(R-CNN系列)
目标检测方法系列--R-CNN, SPP, Fast R-CNN, Faster R-CNN, YOLO, SSD 目录 相关背景 从传统方法到R-CNN 从R-CNN到SPP Fast R-CNN ...
- linux hadoop安装
linux hadoop安装 本文介绍如何在Linux下安装伪分布式的hadoop开发环境. 在一开始想利用cgywin在 windows下在哪, 但是一直卡在ssh的安装上.所以最后换位虚拟机+ub ...
- 编写ros串口节点,使用官方serial包
参考http://www.roswiki.com/read.php?tid=557&fid=39 1.通过sudo apt-get install ros-<distro>-ser ...
- Web 播放声音 — Flash 篇 (播放 AMR、WAV)
本文主要介绍 Flash 播放 AMR 格式 Base64码 音频. 在此之前么有接触过 Flash ,接触 AS3 是一头雾水,不过幸好有 TypeScript 和 JavaScript 的基础看起 ...
- CSS学习之道
@media的IE-hacks,精彩绝伦 http://blog.keithclark.co.uk/moving-ie-specific-css-into-media-blocks/
- ASP.NET MVC异常处理
ASP.NET MVC异常处理方案 如何保留异常前填写表单的数据 ASP.NET MVC中的统一化自定义异常处理 MVC过滤器详解 MVC过滤器使用案例:统一处理异常顺道精简代码 ASP.NET MV ...