浅谈JavaScript对象的创建方式


Posted in Javascript onJune 13, 2016

通过Object构造函数或对象字面量创建对象时,使用同一个接口创建很多对象时,会产生大量的重复代码。为了简化,引入了工厂模式。

工厂模式

function createPerson(name, age, job) {
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.job = job;
  obj.sayHello(){
    alert(this.name);
  };
  return obj;
}
var p1 = createPerson("xxyh", 19, "programmer");
var p2 = createPerson("zhangsan", 18, "student");

这种创建对象的方式大大简化了代码,然而也存在不足,那就是无法确定对象的类型。为了解决这个问题,出现下面这种模式。

构造函数模式

创建自定义的构造函数,从而定义自定义对象类型的属性和方法。

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = function () {
    alert(this.name);
  };
}
var p1 = new Person("xxyh", 19, "programmer");
var p2 = new Person("Jack", 18, "student");

上例中,Person()取代了createPerson(),除此之外,还有几点不同:

•没有显示地创建对象;

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

•没有return语句

创建Person对象,必须使用new操作符。分为4个步骤:

•创建一个新对象

•将构造函数的作用域赋给新对象

•执行构造函数中的代码

•返回新对象

p1和p2分别保存着Person的一个实例。

alert(p1.constructor == Person);  // true
alert(p2.constructor == Person);  // true

检测类型时最好使用instanceof:

alert(p1 instanceof Object);  // true
alert(p1 instanceof Person);  // true
alert(p2 instanceof Object);  // true
alert(p2 instanceof Person);  // true

p1和p2都是Object的实例,因为所有对象均继承自Object。

2.1将构造函数当作函数

// 当作构造函数使用
var person = new Person("xxyh", 19, "programmer");
person.sayName();  // "xxyh"

// 当作普通函数
Person("zhangsan", 18, "student");  // 添加到window
window.sayName();  // "zhangsan"

// 在另一个对象的作用域中调用
var obj = new Object();
Person.call(obj, "Jack", 29, "manager");
obj.sayName();  // "Jack",obj拥有了所有属性和方法

2.2构造函数的问题

使用构造函数的问题,就是每个方法都要在每个实例上重新创建一遍。p1和p2都有一个sayName()方法,但是他们不是一个Function的实例。在JavaScript中,函数时对象,因此每定义一个函数,就实例化了一个对象。

构造函数也可以这样定义:

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = new Function("alert(this.name)");
}

因此,不同实例上的同名函数时不相等的:

alert(p1.sayName == p2.sayName);  // false

然而,创建两个同样功能的Function是多余的,根本不需要在执行代码前就把函数绑定到特定对象上面。

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = sayName;
}
function sayName() {
  alert(this.name);
}
var p1 = new Person("xxyh", 19, "programmer");
var p2 = new Person("Jack", 18, "student");

上面将sayName()的定义移到构造函数外部,然后在构造函数内部将属性sayName设置为全局的sayName函数。这样,sayName包含了指向函数的指针,p1和p2共享了全局作用域中定义的同一个sayName()函数。

但是,这样做又出现了新问题:在全局作用域中定义的函数只能被某个对象调用。而且如果对象定义了很多方法,那么引用类型就失去了封装性。

原型链模式

每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象。这个对象的用途是:包含可以由特定类型的所有实例共享的属性和方法。prototype是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。这就是说不必在构造函数中定义对象实例的信息,而是将这些信息添加到原型对象中。

function Person() {
}

Person.prototype.name = "xxyh";
Person.prototype.age = 19;
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
  alert(this.name);
};

var person1 = new Person();
person1.sayName(); // "xxyh"
var person2 = new Person();
person2.sayName(); // "xxyh"

alert(person1.sayName == person2.sayName); // true

3.1理解原型对象

只要创建一个新函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor属性。这个属性包含一个指向prototype属性所在函数的指针。Person.prototype.constructor指向Person。

当调用构造函数创建一个实例,实例的内部将包含指向构造函数的原型对象的指针(内部属性),称为[[Prototype]]。在Firefox、Safari和Chrome通过_proto_访问。这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

下图展示了各个对象之间的关系:

浅谈JavaScript对象的创建方式

Person.prototype指向了原型对象,而Person.prototype.constructor又指回了Person。原型中除了constructor属性,还有其他添加的属性。Person实例中都包含一个内部属性,该属性仅仅指向了Person.prototype,它们和构造函数没有直接关系。

虽然无法访问[[Prototype]],但是可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。

alert(Person.prototype.isPrototypeOf(person1));  // true
alert(Person.prototype.isPrototypeOf(person2));  // true

在读取某个对象的属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。搜索首先从对象实例本身出发开始,如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找给定名字的属性。如果在原型对象中找到了这个属性,则返回属性的值。

可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果在实例中添加一个与实例原型中的一个属性同名的属性,该属性将会屏蔽原型中的属性。

function Person() {
}

Person.prototype.name = "xxyh";
Person.prototype.age = "20";
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
  alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = "oooo";
alert(person1.name);  // "oooo"
alert(person2.name);  // "xxyh"

上例中,person1中的name属性屏蔽了原型中的name属性。

当对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。这也就是说,这个属性的存在会阻止对原型中那个属性的访问。使用delete可以完成删除实例属性。

function Person() {
}

Person.prototype.name = "xxyh";
Person.prototype.age = "20";
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
  alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = "oooo";
alert(person1.name);  // "oooo"
alert(person2.name);  // "xxyh"

delete person1.name;
alert(person1.name);  // "xxyh"

hasOwnProperty()可以检测一个属性是存在于实例中,还是存在于原型中。

function Person() {
}

Person.prototype.name = "xxyh";
Person.prototype.age = "20";
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
  alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

alert(person1.hasOwnProperty("name"));  // false

person1.name = "oooo";
alert(person1.hasOwnProperty("name"));  // true

下图展示了不同情况的实现与原型的关系:
浅谈JavaScript对象的创建方式

3.2原型与in操作符

使用in操作符的方式:单独使用、在for-in循环中使用。在单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。

function Person() {

}
Person.prototype.name = "xxyh";
Person.prototype.age = "20";
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
  alert(this.name);
};

var person1 = new Person();

alert("name" in person1);  // true

person1.name = "oooo";
alert("name" in person1);  // true

结合前面的hasOwnProperty()特点,可以确定某个属性是原型中的属性还是实例中的属性。如果in操作符返回true而hasOwnProperty返回false,则属性是原型中的属性。

function hasPrototypeProperty(object, name) {
  return !object.hasOwnProperty(name)&& (name in object);
}

接下来,看看hasPrototypeProperty()的用法:

function Person() {
}

Person.prototype.name = "xxyh";
Person.prototype.age = "20";
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
  alert(this.name);
};

var person = new Person();
alert(hasPrototypeProperty(person, "name"));  // true
person.name = "oooo";

alert(hasPrototypeProperty(person, "name"));  // false

在使用for-in循环时返回的是所有能够通过对象访问的、可枚举的属性,包括实例中的属性和原型中的属性。屏蔽了原型中不可枚举数据(即[[Enumerable]]标记为false的属性)的实例属性也会在for-in中返回,因为根据规定,开发人员定义的属性都是可枚举的。

要取得对象上所有可枚举的实例属性,可以使用Object.keys()方法。

function Person() {
}

Person.prototype.name = "xxyh";
Person.prototype.age = "20";
Person.prototype.job = "programmer";
Person.prototype.sayName = function () {
  alert(this.name);
};

var keys = Object.keys(Person.prototype);
alert(keys);      // name, age, job, sayName

var p1 = new Person();
p1.name = "oooo";
p1.age = 15;
var p1_keys = Object.keys(p1);
alert(p1_keys);     // name, age

如果需要得到所有实例属性,可以使用Object.getOwnPropertyNames()方法

var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys);  // "constructor,name,age,job,sayName"

3.3更简单的原型语法

为了精简输入,用一个包含所有属性和方法的对象字面量来重写整合原型对象。

function Person() {   
}

Person.prototype = {
  name : "xxyh",
  age : 18,
  job : "programmer",
  sayName : function () {
    alert(this.name);
  }
};

上面将Person.prototype设置为等于一个以对象字面量形式创建的新对象。结果相同,但是constructor属性不在指向Person了。

通过instanceof能返回正确结果,但是constructor无法确定对象的类型:

var boy = new Person();
alert(boy instanceof Object);    // true
alert(boy instanceof Person);    // true
alert(boy.constructor == Person);  // false
alert(boy.constructor == Object);  // true

可以通过下面的方式设置constructor的值:

function Person() {
}

Person.prototype = {
  constructor : Person,
  name : "xxyh",
  age : 18,
  job : "programmer",
  sayName : function () {
    alert(this.name);
  }
};

3.4原型链的动态性

由于在原型中查找值的过程是一次搜索,因此对原型对象所做的任何修改都会反映到实例上。但是如果重写整个原型对象,结果就不同了。调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,而把原型修改为另一个对象就等于切断了构造函数与最初原型的联系。实例中的指针仅指向原型,而不指向构造函数。

function Person() {
}

var boy = new Person();
Person.prototype = {
  constructor : Person,
  name : "xxyh",
  age : 29,
  job : "programmer",
  sayName : function () {
    alert(this.name);
  }
};

boy.sayName();  // 错误

具体过程如下:
浅谈JavaScript对象的创建方式
从上面可以看出,重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;它们引用的是最初的原型。

3.5原生对象的原型

所有原生引用类型都是在构造函数的原型上定义了方法。通过原生对象的原型,不仅可以取得默认方法,而且可以定义新方法。

String.prototype.startsWith = function (text) {
  return this.indexOf(text) == 0;
};

var msg = "good morning";
alert(msg.startsWith("good")); // true

3.6原型对象的问题

原型模式存在两个问题:

•在默认情况下都取得相同的属性值。

•原型中的所有属性是实例共享的

下面看一个例子:

function Person() {

}

Person.prototype = {
  constructor: Person,
  name: "xxyh",
  age : 18,
  job : "programmer",
  friends:["张三", "李四"],
  sayName: function () {
    alert(this.name);
  }
};

var p1 = new Person();
var p2 = new Person();

p1.friends.push("王五");

alert(p1.friends);         // 张三,李四,王五
alert(p2.friends);         // 张三,李四,王五
alert(p1.friends == p2.friends);  // true

上面通过p1.friends添加了一项,由于friends数组存在于Person.prototype中,所以在p2.friends也反映出来了。可是,实例一般都是要有属于自己的全部属性的。

组合使用构造函数模式和原型模式

构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。这样,每个实例都会有自己的一份实例属性的副本,但是同时又共享着对方法的引用。

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.friends = ["张三", "李四"];
}
Person.prototype = {
  constructor: Person,
  sayName: function () {
    alert(this.name);
  }
}

var p1 = new Person("萧萧弈寒", 18, "programmer");
var p2 = new Person("魁拔", 10, "捉妖");

p1.friends.push("王五");
alert(p1.friends);         // 张三,李四,王五
alert(p2.friends);         // 张三,李四
alert(p1.friends == p2.friends);  // false
alert(p1.sayName == p2.sayName);  // true

上例中,实例属性都是在构造函数中定义的,共享属性constructor和方法sayName()则是在原型中定义的。p1.friends的修改并不会影响到p2.friends的结果。

动态原型模式

动态原型模式把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。这就是说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

function Person(name, age, job) {
    
  // 属性
  this.name = name;
  this.age = age;
  this.job = job;
  
  // 方法
  if (typeof this.sayName != "function") {
    Person.prototype.sayName = function () {
      alert(this.name);
    }
  }
}

这里只在sayName()方法不存在时,才会将它添加到原型中,只会在初次调用构造函数时执行。

寄生构造函数模式

这种模式的思想是创建一个函数,该函数的作用是封装创建对象的代码,然后再返回新创建的对象。

function Person(name, age) {
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.sayName = function () {
    alert(this.name);
  }
  return obj;
}

var boy = new Person("xxyh", 19, "programmer");
boy.sayName();

需要说明:首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;构造函数返回的对象与在构造函数外部创建的对象没有不同。不能依赖instanceof操作符来确定对象类型。

稳妥构造函数模式

稳妥对象指的是没有公共属性,而且其方法也不引用this的对象。稳妥构造函数遵循与寄生构造函数类似的模式,但是有两点不同:

•新创建对象的实例方法不引用this;

•不使用new操作符调用构造函数

重写Person构造函数如下:

function Person(name, age, job) {
  var obj = new Object();
  obj.sayName = function () {
    alert(name);
  };

  return obj;
}
function Person(name, age, job) {
  var obj = new Object();
  obj.sayName = function () {
    alert(name);
  };

  return obj;
}

以上这篇浅谈JavaScript对象的创建方式就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript 继承详解(二)
Jul 13 Javascript
一起来写段JS drag拖动代码
Dec 09 Javascript
Jquery ajax执行顺序 返回自定义错误信息(实例讲解)
Nov 06 Javascript
使用phantomjs进行网页抓取的实现代码
Sep 29 Javascript
javascript制作幻灯片(360度全景图片)
Jul 28 Javascript
仅9张思维导图帮你轻松学习Javascript 就这么简单
Jun 01 Javascript
js中的关联数组与普通数组详解
Jul 27 Javascript
jQuery实现的粘性滚动导航栏效果实例【附源码下载】
Oct 19 jQuery
vue小白入门教程
Apr 02 Javascript
JavaScript创建对象方法实例小结
Sep 03 Javascript
vue keep-alive 动态删除组件缓存的例子
Nov 04 Javascript
微信小程序实现拍照和相册选取图片
May 09 Javascript
BootStrap智能表单实战系列(八)表单配置json详解
Jun 13 #Javascript
BootStrap智能表单实战系列(七)验证的支持
Jun 13 #Javascript
BootStrap智能表单实战系列(六)表单编辑页面的数据绑定
Jun 13 #Javascript
js 截取或者替换字符串中的数字实现方法
Jun 13 #Javascript
BootStrap 智能表单实战系列(五) 表单依赖插件处理
Jun 13 #Javascript
BootStrap智能表单实战系列(四)表单布局介绍
Jun 13 #Javascript
JS/jQ实现免费获取手机验证码倒计时效果
Jun 13 #Javascript
You might like
解析php下载远程图片函数 可伪造来路
2013/06/25 PHP
php获取ajax的headers方法与内容实例
2017/12/27 PHP
编写兼容IE和FireFox的脚本
2009/05/18 Javascript
jquery使用$(element).is()来判断获取的tagName
2014/08/24 Javascript
浅谈JavaScript 框架分类
2014/11/10 Javascript
超炫的jquery仿flash导航栏特效
2014/11/11 Javascript
JavaScript实现简单图片翻转的方法
2015/04/17 Javascript
javascript实现淘宝幻灯片广告展示效果
2015/04/27 Javascript
javascript常用的方法整理
2015/08/20 Javascript
Ionic默认的Tabs模板使用实例
2016/08/29 Javascript
Vue与Node.js通过socket.io通信的示例代码
2018/07/25 Javascript
微信小程序封装的HTTP请求示例【附升级版】
2019/05/11 Javascript
关于vue属性使用和不使用冒号的区别说明
2020/10/22 Javascript
用Python制作检测Linux运行信息的工具的教程
2015/04/01 Python
python实现爬虫统计学校BBS男女比例(一)
2015/12/31 Python
python运行其他程序的实现方法
2017/07/14 Python
python交互式图形编程实例(二)
2017/11/17 Python
Python 获取windows桌面路径的5种方法小结
2019/07/15 Python
pytorch中的transforms模块实例详解
2019/12/31 Python
pytorch 实现cross entropy损失函数计算方式
2020/01/02 Python
Python动态导入模块和反射机制详解
2020/02/18 Python
iPython pylab模式启动方式
2020/04/24 Python
从Pytorch模型pth文件中读取参数成numpy矩阵的操作
2021/03/04 Python
HTML5为输入框添加语音输入功能的实现方法
2017/02/06 HTML / CSS
荷兰鞋类购物网站:Donelli
2019/05/24 全球购物
两道JAVA笔试题
2016/09/14 面试题
宿舍卫生检讨书
2014/01/16 职场文书
八一慰问活动方案
2014/02/07 职场文书
工作睡觉检讨书
2014/02/25 职场文书
《与朱元思书》的教学反思
2014/04/17 职场文书
科技之星事迹材料
2014/06/02 职场文书
政风行风评议个人心得体会
2014/10/29 职场文书
运动会100米广播稿
2015/08/19 职场文书
实用干货:敬酒词大全,帮你应付各种场合
2019/11/21 职场文书
Mysql数据库事务的脏读幻读及不可重复读详解
2022/05/30 MySQL
使用CSS实现黑白格背景效果
2022/06/01 HTML / CSS