第一行, 创建一个空对象 obj
第二行, 将这个空对象的__proto__成员指向了构造函数对象的 prototype 成员对象, 这是最关键的一步, 具体细节将在下文描述
第三行, 将构造函数的作用域赋给新对象, 因此 CA 函数中的 this 指向新对象 obj, 然后再调用 CO 函数于是我们就给 obj 对象赋值了一个成员变量 p, 这个成员变量的值是 Imin constructed object
第四行, 返回新对象 obj 当构造函数里包含返回语句时情况比较特殊, 这种情况会在下文中说到
正确定义 JavaScript 构造函数
不同于其它的主流编程语言, JavaScript 的构造函数并不是作为类的一个特定方法存在的; 当任意一个普通函数用于创建一类对象时, 它就被称作构造函数, 或构造器一个函数要作为一个真正意义上的构造函数, 需要满足下列条件:
1 在函数内部对新对象 (this) 的属性进行设置, 通常是添加属性和方法
2 构造函数可以包含返回语句(不推荐), 但返回值必须是 this, 或者其它非对象类型的值
上文定义的构造函数 CO 就是一个标准的简单的构造函数下面例子定义的函数 C1 返回了一个对象, 我们可以使用 new 表达式来调用它, 该表达式可以正确返回一个对象:
- function C1(){
- var o = {
- p: Im p in C1
- }
- return o;
- }
- var o1 =new C1();
- alert(o1.p);//Im p in C1
但这种方式并不是值得推荐的方式, 因为对象 o1 的原型是函数 C1 内部定义的对象 o 的原型, 也就是 Object.prototype 这种方式相当于执行了正常 new 表达式的前三步, 而在第四步的时候返回了 C1 函数的返回值该方式同样不便于创建大量相同类型的对象, 不利于使用继承等高级特性, 并且容易造成混乱, 应该摒弃
一个构造函数在某些情况下完全可以作为普通的功能函数来使用, 这是 JavaScript 灵活性的一个体现下例定义的 C2 就是一个多用途函数:
- 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(); 创建对象的时候, 发生了四件事情:
- 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 如果没有考虑过这个问题, 这一点可能不太好理解, 那就让我们举个例子来说明一下吧:
- 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 属性来测试对象的类型:
- var myArray = [1,2,
- 3
- ];
- (myArray.constructor == Array);
- // true
这招对于简单的对象是管用的, 涉及到继承或者跨窗口等复杂情况时, 可能就没那么灵光了:
- 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 属性的时候一定要小心, 或者干脆不要用它
来源: https://www.cnblogs.com/maqingyuan/p/8444565.html