资讯专栏INFORMATION COLUMN

JavaScript之对象创建

Michael_Lin / 2243人阅读

摘要:在构造函数的内部,的指向是新创建的对象。如果构造函数没有显式的表达式,则会隐式的返回新创建的对象对象。原型模式在构造函数模式中提到每次之后创建的新的对象是互相独立的,是独享的。

1.构造函数模式

JavaScript中的构造函数是通过new调用的,也就是说,通过new关键字调用的函数都被认为是构造函数。

在构造函数的内部,this的指向是新创建的对象Object

如果构造函数没有显式的return表达式,则会隐式的返回新创建的对象——this对象。

function Foo () {
  this.name = "rccoder";
}

Foo.prototype.test = function () {
  console.log (this.name);
}

var bar = new Foo();

bar.name;    // rccoder
bar.test();  // rccoder

在构造函数中,显式的return会影响返回的值,但是仅限于返回的是 一个对象当返回值不是一个对象的时候,实际上会返回一个新创建的对象;当返回值就是一个对象的时候,返回的不是新创建的对象,而是本身就要返回的那个对象

function Foo () {
  return 2;
}

new Foo();    // Foo {} 返回的不是2,而是新创建了一个对象

function Bar () {
  this.name = "rccoder";
  return {
    foo: 1
  }
}

new Bar();   // Object {foo: 1}  返回要返回的那个对象
(new Bar()).name  // undefined
(new Bar()).foo   // 1

构造函数模式主要有以下几个特点:

没有显式的返回对象

直接将属性和方法赋值给this对象

没有return语句

通过new关键字调用的函数都被认为是构造函数。

new之后产生的是一个新的对象,可以在new的时候传递参数,并且把这个参数的值赋值给this 指针,这样,传递进去的内容,就变成了新产生的对象的属性或者方法。

为了让代码看起来更加的“优雅”,构造函数的首字母都是大写。

除此之外,用构造函数产生的实例中,他的原型都会默认的包含一个constructor属性,会指向构造函数。这样就能够知道这个对象是从哪儿创建的,也就是说能够区分这个对象的类型了(下面的工厂模式就无法区分对象的类型)。

function Foo () {
  this.value = 1;
}

test = new Foo ();

test.constructor  == Foo ();

当然,构造函数也是可以直接执行的,而不是一定要new,直接执行的化“构造函数”中的this指向的就不再是新产生的对象了(实际上这种情况下就和普通的函数一样,并不会产生新的对象),往往在浏览器中是window.

构造函数在每次new的时候产生的实例都是重新创建的,因此不同实例上的同名函数是不相等的。

function Foo () {
  this.test = function () {

  };
}

var a = new Foo();
var b = new Foo();

a.test === b.test    // false

所以说,构造函数每次new都是产生一个新的实例,并且这个实例的属性和方法是独享的。这样往往造成了一些浪费(属性是独有的可以理解,但是就方法而言,大多数往往是一样的,这和我们想要的可能有点差别)。

2.工厂模式

为了不去使用new关键字,上面提到的构造函数必须显式的返回。当前这个时候就可以理解为不是构造函数了。

function Foo () {
  var value = 1;

  return {
    method: function () {
      return value;
    }
  };

};

Foo.prototype = {
  foo: function () {

  }
};

new Foo();
Foo();

上面加不加new的返回结果是完全一样的,都是一个新创建的,拥有method属性的对象。嗯。如果对闭包有所理解的话,他返回的就是一个闭包!

需要注意的是,上面返回的是一个包含method属性的自定义对象,所以他并不返回Foo.prototype.

(new Foo()).foo    // undefined
(Foo()).foo        // undefined

按照正常人的思路,一般选择用new来调用函数总是显得很奇怪,这也估计就是大多人说不要使用new关键字来调用函数的原因了,因为如果忘记new就会产生难以察觉的错误。

嗯,是时候引出工厂模式了:

function Foo () {
  var obj = {};
  obj.value = "rccoder";

  var privateValue = 2;

  obj.method = function (value) {
    this.value = value;
  };

  obj.getPrivate = function () {
    return privateValue;
  };
  
  return obj;
}

就像上面的代码一样,有个工厂,就这样生产出了一个个的工人。工厂模式解决了多个比较相似的对象重复创建的问题。但是这个创建只单纯的一个创建,但并不知道这个对象是从哪里创建的,也就是说无法去区分这个对象的类型。

当然还有一些其他的缺点,比如由于新创建的对象只是简单的创建,所以不能共享原型上的方法,如果要实现所谓的继承,就要从另外的一个对象去拷贝所有的属性...嗯,他放弃了原型,为了去防止new带来的问题。

3.原型模式

在构造函数模式中提到每次new之后创建的新的对象是互相独立的,是独享的。

构造函数每次new都是产生一个新的实例,并且这个实例的属性和方法是独享的。这样往往造成了一些浪费(属性是独有的可以理解,但是就方法而言,大多数往往是一样的,这和我们想要的可能有点差别)

就最后一句而言,我们或许可以这样写:

function Foo (value) {
  this.value = value;
  this.method = method;
}

function method () {
  console.log (this.value);
}

这样把方法去放在外面,在构造函数里面去调用这个函数,好像就hack的解决了上面的问题。但是这个函数好像就是全局函数了,并且和Foo()看上去并不怎么愉快的是一家人,谈封装也就有些牵强。

这种去共享方法的问题用prototype看似就可以解决,毕竟他产生的属性和方法是所有产生的实例所共享的。

function Foo () {
  ...
};

Foo.prototype.value = "rccoder";
Foo.prototype.method = function () {
  console.log (this.value);
};

var test = new Foo ();
test.method();    // rccoder

这样看起来好像是可行的,当需要找某个对象的属性的时候,往往直接看有没有这个属性,没有的话再按照原型链向上寻找,而不是去寻找构造函数。

原型是动态的,所以不要随便的去修改原型。这个修改后会立即影响实例的结果。

如果我们有Foo.prototype = Bar.prototype 的写法,改变这两个对象任何一个的原型都会影响另外一个,这在大多的情况下是不可取的。

一般情况下不建议对原型做修改,因为很可能由于代码量太多导致维护太困难。

另外,还记得用原型模式的初衷吗?是要公用方法,而不是公用属性。纯粹的用原型会同样的公用属性,这在很多情况下看起来是很郁闷的。所以可能需要我们把原型和构造函数结合起来使用。

4.优雅混合使用构造函数与原型

这或许是比较理想话的使用方法了,用构造函数来区分独享的属性,用原型来共享大家都用的方法。

function Foo (value1, value2) {
  this.value1 = value1;
  this.value2 = value2;
}

Foo.prototype.method = function () {
  console.log (this.value1)
};

test1 = new Foo (2, 3);
test1.method();  // 2

test2 = new Foo (4, 5);
test2.method()   // 4

哦,对了,你可能会看见这样写上面的代码

function Foo (value1, value2) {
  this.value1 = value1;
  this.value2 = value2;
}

Foo.prototype = {
  constructor: Foo,
  method: function () {
    console.log (this.value1);
  }
}

test = new Foo (2, 3);
test.method();

别怕,这只是覆盖了Foo的原型而已,是真的覆盖到连constructor是谁都不认识了,所以需要手动的是想一下,指向谁呢?正常人的话指向的应该是构造函数吧。

参考资料:

JavaScript密码花园

JavaScript 原型理解与创建对象应用-于江水的博客

原文链接:http://life.rccoder.net/javascript/1216.html

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78363.html

相关文章

  • JavaScript深入作用域链

    摘要:下面,让我们以一个函数的创建和激活两个时期来讲解作用域链是如何创建和变化的。这时候执行上下文的作用域链,我们命名为至此,作用域链创建完毕。 JavaScript深入系列第五篇,讲述作用链的创建过程,最后结合着变量对象,执行上下文栈,让我们一起捋一捋函数创建和执行的过程中到底发生了什么? 前言 在《JavaScript深入之执行上下文栈》中讲到,当JavaScript代码执行一段可执行代...

    waltr 评论0 收藏0
  • JavaScript深入执行上下文

    摘要:深入系列第七篇,结合之前所讲的四篇文章,以权威指南的为例,具体讲解当函数执行的时候,执行上下文栈变量对象作用域链是如何变化的。前言在深入之执行上下文栈中讲到,当代码执行一段可执行代码时,会创建对应的执行上下文。 JavaScript深入系列第七篇,结合之前所讲的四篇文章,以权威指南的demo为例,具体讲解当函数执行的时候,执行上下文栈、变量对象、作用域链是如何变化的。 前言 在《Jav...

    gougoujiang 评论0 收藏0
  • JavaScriptnew运算符

    摘要:之运算符运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。使用指定的参数调用构造函数,并将绑定到新创建的对象。 JavaScript之new运算符 new运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new关键字会进行如下的操作: 1. 创建一个空的简单JavaScript对象(即{}); 2. 链接该对象(即设置该对象的构造函数)到另一个对...

    wwq0327 评论0 收藏0
  • JavaScript深入变量对象

    摘要:深入系列第四篇,具体讲解执行上下文中的变量对象与活动对象。下一篇文章深入之作用域链本文相关链接深入之执行上下文栈深入系列深入系列目录地址。 JavaScript深入系列第四篇,具体讲解执行上下文中的变量对象与活动对象。全局上下文下的变量对象是什么?函数上下文下的活动对象是如何分析和执行的?还有两个思考题帮你加深印象,快来看看吧! 前言 在上篇《JavaScript深入之执行上下文栈》中...

    Zachary 评论0 收藏0
  • JavaScript基础创建对象、原型、原型对象、原型链

    摘要:在最开始的时候,原型对象的设计主要是为了获取对象的构造函数。同理数组通过调用函数通过调用原型链中描述了原型链的概念,并将原型链作为实现继承的主要方法。 对象的创建 在JavaScript中创建一个对象有三种方式。可以通过对象直接量、关键字new和Object.create()函数来创建对象。 1. 对象直接量 创建对象最直接的方式就是在JavaScript代码中使用对象直接量。在ES5...

    wangbjun 评论0 收藏0
  • JavaScript深入创建对象的多种方式以及优缺点

    摘要:深入系列第十四篇,讲解创建对象的各种方式,以及优缺点。也就是说打着构造函数的幌子挂羊头卖狗肉,你看创建的实例使用都无法指向构造函数这样方法可以在特殊情况下使用。 JavaScript深入系列第十四篇,讲解创建对象的各种方式,以及优缺点。 写在前面 这篇文章讲解创建对象的各种方式,以及优缺点。 但是注意: 这篇文章更像是笔记,因为《JavaScript高级程序设计》写得真是太好了! 1....

    Terry_Tai 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<