浅谈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 相关文章推荐
地址栏上的一段语句,改变页面的风格。(教程)
Apr 02 Javascript
jquery ui dialog里调用datepicker的问题
Aug 06 Javascript
jQuery学习笔记之DOM对象和jQuery对象
Dec 22 Javascript
Javascript改变CSS样式(局部和全局)
Dec 18 Javascript
微信小程序视图template模板引用的实例详解
Sep 20 Javascript
CSS3结合jQuery实现动画效果及回调函数的实例
Dec 27 jQuery
详解 vue better-scroll滚动插件排坑
Feb 08 Javascript
详解node.js 下载图片的 2 种方式
Mar 02 Javascript
vue移动UI框架滑动加载数据的方法
Mar 12 Javascript
解决vue-cli3 使用子目录部署问题
Jul 19 Javascript
vue 解决循环引用组件报错的问题
Sep 06 Javascript
vue如何使用外部特殊字体的操作
Jul 30 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 输出缓存详解
2009/06/20 PHP
修改ThinkPHP缓存为Memcache的方法
2014/06/25 PHP
Laravel框架数据库CURD操作、连贯操作总结
2014/09/03 PHP
php上传图片类及用法示例
2016/05/11 PHP
golang与PHP输出excel示例
2016/07/22 PHP
PHP实现小偷程序实例
2016/10/31 PHP
Javascript条件判断使用小技巧总结
2008/09/08 Javascript
ExtJS 2.0实用简明教程 之Border区域布局
2009/04/29 Javascript
Javascript Global对象
2009/08/13 Javascript
ASP Json Parser修正版
2009/12/06 Javascript
JavaScript中将一个值转换为字符串的方法分析[译]
2012/09/21 Javascript
JS 打印界面的CSS居中代码适用所有浏览器
2014/03/19 Javascript
利用javascript实现全部删或清空所选的操作
2014/05/27 Javascript
JavaScript中使用concat()方法拼接字符串的教程
2015/06/06 Javascript
常用原生JS兼容性写法汇总
2016/04/27 Javascript
浅谈js里面的InttoStr和StrtoInt
2016/06/14 Javascript
详解.vue文件中监听input输入事件(oninput)
2017/09/19 Javascript
JavaScript多线程运行库Nexus.js详解
2017/12/22 Javascript
解决Linux无法正常安装与卸载Node.js的方法
2018/01/19 Javascript
微信小程序之几种常见的弹框提示信息实现详解
2019/07/11 Javascript
layui表格 返回的数据状态异常的解决方法
2019/09/10 Javascript
vue ajax 拦截原理与实现方法示例
2019/11/29 Javascript
Node中对非阻塞I/O、事件循环的知识点总结
2020/01/05 Javascript
js实现鼠标滑动到某个div禁止滚动
2020/09/17 Javascript
[05:41]2014DOTA2西雅图国际邀请赛 小组赛7月10日TOPPLAY
2014/07/10 DOTA
Python3.5 Pandas模块缺失值处理和层次索引实例详解
2019/04/23 Python
Python Django Vue 项目创建过程详解
2019/07/29 Python
Python过滤序列元素的方法
2020/07/31 Python
HTML5如何为形状图上颜色怎么绘制具有颜色和透明度的矩形
2014/06/23 HTML / CSS
品管员岗位职责
2013/11/10 职场文书
2014年党员评议表自我评价
2014/09/27 职场文书
2015年银行工作总结范文
2015/04/01 职场文书
运动会主持词大全
2015/07/02 职场文书
pytorch实现ResNet结构的实例代码
2021/05/17 Python
Python爬虫实战之爬取京东商品数据并实实现数据可视化
2021/06/07 Python
JavaScript实现简单的音乐播放器
2022/08/14 Javascript