浅谈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 轻松搞定快捷留言功能 只需一行代码
Apr 01 Javascript
jquery 插件学习(四)
Aug 06 Javascript
js鼠标滑过弹出层的定位IE6bug解决办法
Dec 26 Javascript
.net,js捕捉文本框回车键事件的小例子(兼容多浏览器)
Mar 11 Javascript
Ext中下拉列表ComboBox组件store数据格式用法介绍
Jul 15 Javascript
JavaScript判断文件上传类型的方法
Sep 02 Javascript
js实现点击图片将图片地址复制到粘贴板的方法
Feb 16 Javascript
JavaScript中this详解
Sep 01 Javascript
jQuery获取同级元素的简单代码
Jul 09 Javascript
JS HTML5拖拽上传图片预览
Jul 18 Javascript
使用jquery给新生的th绑定hover事件的实例
Feb 10 Javascript
小程序实现五星点评效果
Nov 03 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
Apache+php+mysql在windows下的安装与配置图解(最新版)
2008/11/30 PHP
php zip文件解压类代码
2009/12/02 PHP
关于PHPDocument 代码注释规范的总结
2013/06/25 PHP
PHP+Mysql+jQuery文件下载次数统计实例讲解
2015/10/10 PHP
eclipse php wamp配置教程
2016/06/30 PHP
PHP实现链式操作的三种方法详解
2017/11/16 PHP
PHP swoole和redis异步任务实现方法分析
2019/08/12 PHP
JS 建立对象的方法
2007/04/21 Javascript
网页中表单按回车就自动提交的问题的解决方案
2014/11/03 Javascript
javascript实时获取鼠标坐标值并显示的方法
2015/04/30 Javascript
详解Javascript中的Object对象
2016/02/28 Javascript
js 求时间差的实现代码
2016/04/26 Javascript
动态设置form表单的action属性的值的简单方法
2016/05/25 Javascript
livereload工具实现前端可视化开发【推荐】
2016/12/23 Javascript
javascript实现用户点击数量统计
2016/12/25 Javascript
js实现截图保存图片功能的代码示例
2017/02/16 Javascript
Mac中安装nvm的教程分享
2017/12/11 Javascript
zTree 树插件实现全国五级地区点击后加载的示例
2018/02/05 Javascript
从0到1搭建Element的后台框架的方法步骤
2019/04/10 Javascript
layer弹出层倒计时关闭的实现方法
2019/09/27 Javascript
详解javascript void(0)
2020/07/13 Javascript
vue3.0 项目搭建和使用流程
2021/03/04 Vue.js
python字典多键值及重复键值的使用方法(详解)
2016/10/31 Python
浅谈python str.format与制表符\t关于中文对齐的细节问题
2019/01/14 Python
Python和Bash结合在一起的方法
2020/11/13 Python
与世界上最好的跑步专业品牌合作:Fleet Feet
2019/03/22 全球购物
英国著名的美容护肤和护发产品购物网站:Lookfantastic
2020/11/23 全球购物
什么是典型的软件三层结构?软件设计为什么要分层?软件分层有什么好处?
2012/03/14 面试题
网上商城创业计划书范文
2014/01/31 职场文书
《蚂蚁和蝈蝈》教学反思
2014/02/24 职场文书
党的群众路线教育学习材料
2014/05/12 职场文书
圣诞节活动策划方案
2014/06/09 职场文书
乒乓球比赛通知
2015/04/27 职场文书
文艺有韵味的诗句(生命类、亲情类...)
2019/07/11 职场文书
MySQL 使用索引扫描进行排序
2021/06/20 MySQL
Linux、ubuntu系统下查看显卡型号、显卡信息详解
2022/04/07 Servers