下面小编就为大家带来一篇浅谈 JavaScript 对象的创建方式。小编觉得挺不错的,现在就分享给大家,也给大家做个参考,一起跟随小编过来看看吧
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
通过 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 对象的创建方式就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持 phperz。
来源: http://www.phperz.com/article/17/0329/264915.html