浅谈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 相关文章推荐
了解了这些才能开始发挥jQuery的威力
Oct 10 Javascript
js中用window.open()打开多个窗口的name问题
Mar 13 Javascript
用JavaScript获取页面文档内容的实现代码
Jun 10 Javascript
原生javascript移动端滑动banner效果
Mar 10 Javascript
js图片放大镜效果实现方法详解
Oct 28 Javascript
详解vue中computed 和 watch的异同
Jun 30 Javascript
ztree简介_动力节点Java学院整理
Jul 19 Javascript
使用vue-router为每个路由配置各自的title
Jul 30 Javascript
解决Vue+Electron下Vuex的Dispatch没有效果问题
May 20 Javascript
解决vue-photo-preview 异步图片放大失效的问题
Jul 29 Javascript
Ajax获取node服务器数据的完整步骤
Sep 20 Javascript
解决vue数据不实时更新的问题(数据更改了,但数据不实时更新)
Oct 27 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
阿拉伯的咖啡与水烟
2021/03/03 咖啡文化
PHP入门学习的几个不错的实例代码
2008/07/13 PHP
PHP生成树的方法
2015/07/28 PHP
Smarty分页实现方法完整实例
2016/05/11 PHP
文字幻灯片
2006/06/26 Javascript
Javascript处理DOM元素事件实现代码
2012/05/23 Javascript
nodejs实现的一个简单聊天室功能分享
2014/12/06 NodeJs
js简单设置与使用cookie的方法
2016/01/22 Javascript
JavaScript File API实现文件上传预览
2016/02/02 Javascript
jQuery获取访问者IP地址的方法(基于新浪API与QQ查询接口)
2016/05/25 Javascript
jquery树形菜单效果的简单实例
2016/06/06 Javascript
JavaScript实现翻页功能(附效果图)
2017/02/16 Javascript
微信小程序通过保存图片分享到朋友圈功能
2018/05/24 Javascript
新手必须知的Node.js 4个JavaScript基本概念
2018/09/16 Javascript
JS中appendChild追加子节点无效的解决方法
2018/10/14 Javascript
微信小程序Echarts图表组件使用方法详解
2019/06/25 Javascript
多个Vue项目部署到服务器的步骤记录
2020/10/22 Javascript
JavaScript手写数组的常用函数总结
2020/11/22 Javascript
[03:39]DOTA2英雄梦之声_第05期_幽鬼
2014/06/23 DOTA
Python的Flask框架与数据库连接的教程
2015/04/20 Python
Python制作爬虫抓取美女图
2016/01/20 Python
利用Python将时间或时间间隔转为ISO 8601格式方法示例
2017/09/05 Python
opencv与numpy的图像基本操作
2019/03/08 Python
python爬虫之验证码篇3-滑动验证码识别技术
2019/04/11 Python
Python代码使用 Pyftpdlib实现FTP服务器功能
2019/07/22 Python
最小二乘法及其python实现详解
2020/02/24 Python
Python opencv相机标定实现原理及步骤详解
2020/04/09 Python
python 实现rolling和apply函数的向下取值操作
2020/06/08 Python
使用keras实现Precise, Recall, F1-socre方式
2020/06/15 Python
大二学期个人自我评价
2014/01/13 职场文书
艺术学院毕业生自荐信
2014/07/05 职场文书
2014年小学数学教师工作总结
2014/12/03 职场文书
平遥古城导游词
2015/02/03 职场文书
2019单位介绍信怎么写
2019/06/24 职场文书
HTML5基础学习之文本标签控制
2022/03/25 HTML / CSS
win10音频服务未响应怎么解决?win10音频服务未响应未修复的解决方法
2022/08/14 数码科技