浅谈JavaScript对象的创建方式

通过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_访问。这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

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

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

下图展示了不同情况的实现与原型的关系:

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();  // 错误

具体过程如下:

从上面可以看出,重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;它们引用的是最初的原型。

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对象的创建方式就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 常用Javascript函数与原型功能收藏(必看篇)

    如下所示: // 重复字符串 String.prototype.repeat = function(n) { return new Array(n+1).join(this); } // 替换全部 String.prototype.replaceAll = function(str1, str2) { return this.replace(new RegExp(str1, "gm"), str2); } // 清除空格 String.prototype.trim = function

  • 各种常用的JS函数整理

    Js获取页面地址参数 复制代码 代码如下: function getUrlPara(paraName) { var sUrl = location.href; var sReg = "(?://?|&){1}" + paraName + "=([^&]*)" var re = new RegExp(sReg, "gi"); re.exec(sUrl); return RegExp.$1; } 地址跳转 复制代码 代码如下: var

  • javascript学习笔记(五)原型和原型链详解

    私有变量和函数 在函数内部定义的变量和函数,如果不对外提供接口,外部是无法访问到的,也就是该函数的私有的变量和函数. 复制代码 代码如下: <script type="text/javascript">     function Test(){         var color = "blue";//私有变量         var fn = function() //私有函数         { }     } </script> 这样在

  • 浅谈JavaScript对象的创建方式

    通过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 = createPers

  • 浅谈JavaScript对象与继承

    JavaScript是我在C语言之后接触的第二门编程语言,大一暑假的时候在图书馆找了一本中国人写的JavaScript程序设计来看.那个时候在编程方面几乎还是小白,再加上那本书根本没有提JavaScript的编程机制,又有一些误导性的话,一直以来对JavaScript有很深的误解,认为JavaScript只是一门在浏览器上运行的面向对象语言,值此文来写下JavaScript当中很具有迷惑性和容易误解的地方.当然限于作者水平有限,也没有什么开发经验,所以难免有疏漏之处,还望批评指正. JavaSc

  • 浅谈js对象的创建和对6种继承模式的理解和遐想

    JS中总共有六种继承模式,包括原型链.借用构造函数.组合继承.原型式继承寄生式继承和寄生组合式继承.为了便于理解记忆,我遐想了一个过程,对6中模式进行了简单的阐述. 很长的一个故事,姑且起个名字叫女娲造人吧. 创建对象 女娲一个一个的捏人(创建对象),这样太慢,于是设计了一种机器(函数),想造什么样的,告诉他这个人有哪些特点和功能,机器来制造.这就是工厂模式的(使用同一个接口创建对象,回产生大量重复代码,由此发明了一种函数(模具)). 但是机器造人同样也比较麻烦(挖土.和泥.捏眼睛.捏鼻子...

  • 浅谈javascript对象模型和function对象

    javascript中,函数就是对象 复制代码 代码如下: <html> <head> <script type="text/javascript"> function add(number){     alert(number+20); } var add=function(number){ alert(number+20); } function add(number,number1){     alert(number+30); } var a

  • 浅谈JavaScript 浏览器对象

    window window对象不但充当全局作用域,而且表示浏览器窗口. window对象有innerWidth和innerHeight属性,可以获取浏览器窗口的内部宽度和高度.内部宽高是指除去菜单栏.工具栏.边框等占位元素后,用于显示网页的净宽高.还有一个outerWidth和outerHeight属性,可以获取浏览器窗口的整个宽高. 补充: 网页可见区域宽:document.body.clientWidth 网页可见区域高:document.body.clientHeight 网页可见区域宽:

  • 浅谈JavaScript的内置对象和浏览器对象

    在javascript中对象通常包括两种类型:内置对象和浏览器对象,此外,用户还可以自定义对象. 对象包含两个要素: 1. 用来描述对象特性的一组数据,也就是若干变量,通常称为属性. 2. 用来操作对象特性的若干动作,也就是若干函数,通常称为方法. 浏览器对象 对象 含义 anchor 当前文档中设置了name属性的超链接 applet 当前文档中的小程序 area 客户端图形映射中的区域 button 表单中的按钮 checkbook 表单中的复选框 document 当前窗口中的HTML文档

  • 浅谈JavaScript的计时器对象

    1.JavaScript计时器,我们可以在设定的时间间隔之后来执行代码,而不是在函数被调用后立即执行. 计时器类型: 1)一次性计时器:仅在指定的延迟时间之后触发一次. 2)间隔性触发计时器:每隔一定的时间间隔就触发一次. 计时器方法: 1)setInterval() :在执行时,从载入页面后每隔指定的时间执行代码. //语法 setInterval(代码,交互时间); //参数说明:1. 代码:要调用的函数或要执行的代码串.2. 交互时间:周期性执行或调用表达式之间的时间间隔,以毫秒计(1s=

  • 浅谈JavaScript中的对象及Promise对象的实现

    JavaScript 中的所有事物都是对象:字符串.数值.数组.函数.下面小编给大家收集整理些javascript中的对象及promise对象的实现.具体内容如下: 到处都是对象 window对象 常用的属性和方法介绍 location 包含页面的URL,如果改变这个属性,浏览器会访问新的URL status 包含将在浏览器状态去显示的一个串.一般在浏览器左下角 onload: 包含了需要在页面完全加载后调用的函数 document: 包含DOM alert方法: 显示一个提醒 prompt方法

  • 浅谈JavaScript 中有关时间对象的方法

    ECMAScript中的Date类型是在早期 Java 中的 Java.unile.Date 类基础上构建的.为此 Date 类型使用自 UTC (Coordinated Universal Time, 国际协调时间)1970年1月1日午夜开始经过的毫秒数来保存日期. 在这种数据存储格式下, Date 类型保存的日期能够精确到1970年1月1日之前或之后的285616年 •Date.parse(string) 接受一个表示日期的字符串参数,然后尝试根据这个字符串返回相应日期的毫秒数,不能转换则返

  • 浅谈java对象之间相互转化的多种方式

    第一种:使用org.apache.commons.beanutils.PropertyUtils.copyProperties()拷贝一个bean中的属性到另一个bean中,第一个参数是目标bean,第二个参数是源bean. 特点: 1.它的性能问题相当差 2.PropertyUtils有自动类型转换功能,而java.util.Date恰恰是其不支持的类型 3.PropertyUtils支持为null的场景: public static void copyProperties(Object de

随机推荐