JS面向对象的程序设计之创建对象_工厂模式,构造函数模式,原型模式-1

2018-10-12 admin

2. 原型对象与in操作符

有两种方式使用in操作符: 单独使用和在for-in循环中使用。

单独使用时,in操作符会通过对象能够访问给定属性时返回true。

==无论该属性存在实例中还是原型中。==

请看下下面的例子

function Person(){
}
Person.prototype.name = "Shaw";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
}
var person1 = new Person();
var person2 = new Person();

console.log(person1.hasOwnProperty("name")); // false , 实例对象person1没有name属性
console.log("name" in person1); // true, "name"属性存在于构造函数的原型对象中,所以返回true

person1.name = "Roc";
console.log(person1.name); // "Roc"
console.log(person1.hasOwnProperty("name")); // true
console.log("name" in person1); // true

console.log(person2.name); // "Shaw"
console.log(person2.hasOwnProperty("name")); // false;
console.log("name" in person2); // true

delete person1.name
console.log(person1.hasOwnProperty("name")); //false
console.log(person1.name); // "Shaw"
console.log("name" in person1); //true

在以上代码执行的整个过程中, name属性要么是直接在实例对象上访问到的,要么是通过构造函数的原型对象访问到的。

因此, 调用“name” in person1 始终返回true,无论该属性存在于实例对象中还是存在于构造函数的原型对象中。

同时使用hasOwnProperty()方法和in操作符,就可以确定该属性到底是存在于对象中,还是存在于原型对象中。

//确定属性是否只存在于原型对象中。

function isPrototypeProperty(object, property) {
    return !object.hasOwnProperty(property) && (property in object);
}

由于in操作符只要通过对象能够访问到属性就返回true,因此只要in操作符返回true而实例对象的hasOwnProperty()方法返回false,就可以确定属性是原型中的属性。

function Person(){   
}
Person.prototype.name = "Shaw";
Person.prototype.age = 19;
Person.prototype.job = "Designer";
Person.prototype.sayName = function(){
    console.log(this.name);
}

var person1 = new Person();
console.log(isPrototypeProperty(person1, "name")); // true;

person1.name = "Roc";
console.log(isPrototypeProperty("person1", "name")); false;

在这里,name属性存在于Person.prototype中,因此isPrototypeProperty()方法返回true。

当在实例中重写name属性后,该属性就存在于实例中,因此isPrototypeProperty()返回false。

在使用for-in循环时,返回的是所有能够通过对象访问 且 可枚举的(enumerated) 属性,其中既包括存在于实例中对象的属性,也包括存在于原型中的属性。

==回想下,in操作符的定义, 只要属性存在于对象中,就会返回true。==

属性的枚举特性(即将属性的特性[[Enumerable]]的值标记为false)标记为false的话,就不可被枚举了!!

要取得对象上所有可枚举的属性,可以使用ECMAScript 5的Object.keys()方法。 这个方法接收一个对象作为参数, 返回一个包含所有可枚举属性的字符窜数组。

function Person(){
}
Person.prototype.name = "Shaw";
Person.prototype.age = 19;
Person.prototype.job = "Soft Engineer";
Person.prototype.sayName = function() {
    console.log(this.name);
}

console.log(Object.keys(Person.prototype)); // ["name", "age", "job", "sayName"]

Object.defineProperty(Person.prototype, "name", {
    enumerable: false
})

Object.getOwnPropertyDescriptor(Person.prototype, "name").enumerable; //false

console.log(Object.keys(Person.prototype));  //name属性的枚举特性变为false了,所以 [ "age", "job", "sayName"];

var keys = Object.keys(Person.prototype); //[ "age", "job", "sayName"]

for(var prop in Person.prototype) {
    console.log(prop); // "age", "job", "sayName"
}

这里,变量keys将保存一个数组,数组中是字符窜“age”,“job” 和“sayName”。 这个顺序也是他们在for-in循环出现的顺序。

如果是通过Person的实例对象调用,则返回的实例对象属性。

如果想要得到所有实例属性, 无论它是否枚举,可以使用方法Object.getOwnPropertyNames()。


Object.getOwnPropertyNames(Person.prototype);  //["constructor", "name", "age", "job", "sayName"]

注意结果中包含了不可枚举的constructor和name属性。

Object.keys()和Object.getOwnPropertyNames()都可以用来代替for-in循环。

区别在于Object.keys(), 只会返回参数(对象)的可枚举属性,而Object.getOwnPropertyNames()返回所有传入对象参数的所有属性。

3. 更简单的原型语法

可以注意到,前面例子中每添加一个属性和方法就要敲一遍 Person.prototype。

为减少不必要的输入,也为了从视觉上更好地封装原型的功能, 更常见的做法是用一个包含属性和方法的对象字面量来重写整个原型对象。


function Person(){
}
Person.prototype = {
    name: "Shaw",
    age: 25,
    job: "Designer",
    sayName : function(){
        console.log(this.name);
    }
}

在上面的代码中,我们将Person.prototype赋值为一个以对象字面量形式创建的新对象。

最终结果一样,==但有一个例外: constructor属性不再指向Person了。==

==前面提到过,每创建一个函数,就会创建它的prototye属性(该属性是一个对象),这个对象也会自动获得一个constructor属性指向函数本身。==

==而我们在这里使用的语法(对象字面量形式),本质上完全重写了默认的prototype对象,==

==变成一个全新的对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数), 不再指向Person函数了。==

==此时,尽管instanceof操作符还能返回正确的结果,但通过constructor已经无法确定对象的类型了。==


function Person(){
}

//字面量赋值给prototype属性的伪代码过程
//Person.prototype = new Object(); 改写了prototype
//Person.prototype.__proto__.constructor = function Object(){}
//Person.prototype.name
//....
//
//所以prototype此时的constructor指向了Object;
//不再指向Person了。

Person.prototype = {
    name: "Shaw",
    age: 19,
    job: "Designer",
    sayName: function(){
        console.log(this.name);
    }
}

var person1 = new Person();

console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true

console.log(person1.constructor == Person); // false
console.log(person1.constructor == Object); // true

在此,用instanceof操作符,测试Object和Person仍然返回true,但constructor属性则等于Object而不等于Person了。

如果constructor的值真的很重要,可以像下面这样特意将它设置回适当的值。

function Person(){
}

// Person.prototype还是被改写了!
Person.prototype = {
    constructor: Person,
    name: "Shaw",
    age: 25,
    job: "Designer",
    sayName: function(){
        console.log(this.name);
    }
}

以上代码特意包含了一个constructor属性,并将它的值设置为Person,从而确保了通过概述性能够访问到适当的值。

==注意, 以这种方式重设constructor属性会导致它的[[Enumerable]]特性被设置为true。默认情况下,原生的constructor属性是不可枚举的。因此,如果你使用兼容ECMAScript 5的JavaScrpt引擎, 可以试一试Object.defineProperty()。


function Person(){
}

Person.prototype = {
    name: "Shaw",
    age: 28,
    job: "Designer",
    sayName: function () {
        console.log(this.name);
    }
}; 

Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
})

4. 原型的动态性

由于在函数的原型对象中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反应出来- 即使是先创建了实例后修改原型对象也照样如此。


function Person(){
}

var friend1 = new Person();

Person.prototype.name = "Shaw";
Person.prototype.sayName = function(){
    console.log(this.name);
}

friend1.sayName(); // "Shaw", no problem.

以上代码创建了Person的一个实例对象, 并将其保存在变量friend1中,下一条语句在Person的prototype中添加一个方法sayName()。

即使friend1实例对象是在添加新方法之前创建的,但它仍然可以访问这个新方法。

其原因可以归结为实例与原型之间松散连接关系。 当我们调用friend1.sayName()时,首先会在实例对象中搜索名为sayHi的属性,没有找到的话,会计算搜索原型对象。

==因为实例对象与原型之间的连接只不过是一个指针,而非一个副本。==

因此可以在原型对象中找到新的sayName属性并返回保存在原型中的函数。


尽管可以随时为原型添加属性和方法,并且修改能够立即在所有实例中反映出来,但如果是重写整个原型对象,那么情况就不一样了。

==调用构造函数时会为实例对象添加一个指针[prototype, 这个指针指向构造函数最初的原型对象。而把原型修改为另外一个对象,实例对象的指针理所当然的指向了新对象。==

==一定要记住: 实例对象的[prototype]指向的是构造函数的原型对象。==

function Person(){
}

var friend = new Person(); // friend.__proto__ => Person.prototype 指向没改写之前的原型对象

console.log(Person.prototype); //{constructor: ƒ}

Person.prototype = {
    constructor: "Person",
    name: "Shaw",
    age: "19",
    job: "Designer",
    sayName: function(){
        console.log(this,name);
    }
}

console.log(Person.prototype); //{constructor: "Person", name: "Shaw", age: "19", job: "Designer", sayName: ƒ}
// Person.prototype => new Object(); 改写了, 指向一个新对象。

friend.sayName(); // 还是指向没改成之前的原型对象,里面没有sayName属性,Uncaught TypeError: friend.sayName is not a function

重写原型对象切断了与任何之前已经存在的实例对象之间的联系。它们仍然引用的是最初的原型。

5. Javascript原生对象的原型

原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的原生引用类型,都是采用这种模式创建的。

所有原生引用类型(object、Array、String等等)都在其构造函数的原型上定义了方法。

例如, 在Array.prototype中可以找到sort()方法,而在String.prototype中可以找到substring()方法。


// 伪代码

function Array(arguments) {
}
Array.prototype.sort = function(){};

var arr = new Array(1,3,2);

arr.sort();

function String(arguments) {
}
String.prototype.substring = function(){};

var str = new String('abc');

str.substring();

console.log(typeof Array.prototype.sort); // function
console.log(typeof String.prototype.substring); // function

通过原生引用对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法。

可以像修改自定义对象的原型一样修改原生对象的原型,因此可以随时添加方法。 下面的代码就给基本包装类型String添加一个名为startWith()的方法。


String.prototype.startsWith = function(text) {
    return this.indexOf(text) == 0;
}

var str = "Hello World";
console.log(str.startsWith("Hello")); // true

这里新定义的startsWith()会在传入的文本位于一个字符窜开始时,返回true。既然方法被添加给了String.prototype, 那么当前环境中的所有字符窜都可以调用它。 由于str是字符窜,而且后台会调用String基本包装函数创建这个字符窜,因此通过str是可以调用startsWith()放的。

== 尽管可以这样做,但是不推荐这种方法(修改原生对象的原型)==

如果实现中缺少某个方法,就在原生对象的原型上添加这个方法,那么当在另一个支持该方法的实现中运行代码时,就可能会导致命名冲突。 而且这样做也可能会意外的重写原生方法。

6. 原型模式的问题

任何模式都有优缺点, 那么原型模式的缺点呢?

首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将获得相同的属性值。 虽然这会在某种程度上带来一些不方便,但还不是原型模式的最大问题,原型模式的最大问题是由其共享的本性导致的。

原型中所有属性是被很多实例共享的,这种共享对函数非常适合。

对于那些包含基本值的属性也说得过去,毕竟通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。

==然而对于包含引用类型值的属性来说, 问题就比较突出了。==

function Person() {
}

Person.prototype = {
    constructor: Person,
    name: "Shaw",
    job: "Designer",
    friends: ["Roc", "Van"],
    sayName: function(){
        console.log(this.name);
    }
}

var person1 = new Person();
var person2 = new Person();
person1.friends.push("Avich");

console.log(person1.friends); //["Roc", "Van", "Avich"]
console.log(person2.friends); //["Roc", "Van", "Avich"]

console.log(person1.friends == person2.friends); // true

在此,Person.prototype对象有一个friends属性,该属性的值为一个字符窜数组。 然后创建了两个Person的实例对象。 接着,修改person1.friends引用的数组,向数组添加了一个字符窜。 由于friends数组(数组是引用类型)存在于Person.prototype而非person1当中,所以刚刚提到的修改也会通过person2.friends(与person1.friends指向同一个数组)反映出来。

假如我们的初衷是在所有实例中共享一个数组,那么是适用的。

但是一般情况下,实例对象都是要有属于自己的全部属性的。 而这个问题,正是我们很少看到有人单独使用原型模式的原因所在。

原文链接:https://segmentfault.com/a/1190000016656205

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-42457.html

文章标题:JS面向对象的程序设计之创建对象_工厂模式,构造函数模式,原型模式-1

相关文章
为什么要选择Nodejs?Nodejs有什么好处?
什么?JavaScript还能用作服务器编程! Caleb Madrigal是来自美国密尔沃基市的一名软件顾问。四年前,他在听说“将JavaScript用作服务器端语言”这样的说法时,认为那是一个荒唐的想法。有那么多服务器端语言可供选择,为...
2015-11-12
js性能优化 如何更快速加载你的JavaScript页面
确保代码尽量简洁 不要什么都依赖JavaScript。不要编写重复性的脚本。要把JavaScript当作糖果工具,只是起到美化作用。别给你的网站添加大量的JavaScript代码。只有必要的时候用一下。只有确实能改善用户体验的时候用一下。 ...
2015-11-12
10个强大的纯CSS3动画案例分享
我们的网页外观主要由CSS控制,编写CSS代码可以任意改变我们的网页布局以及网页内容的样式。CSS3的出现,更是可以让网页增添了不少动画元素,让我们的网页变得更加生动有趣,并且更易于交互。本文分享了10个非常炫酷的CSS3动画案例,希望大家...
2015-11-16
2015年JavaScript或“亲库而远框架”
2014年过去了,作为一个JavaScript开发者很难满怀信心的去“挽回”一个特定的库或技术,即便是强大的Angular,似乎也因为最近的一些事情而动摇。 2014年10月的ng-europe会议上,Angular开发者团队透露了一个关于...
2015-11-12
Node.js 2014这一年发生了什么
Node.js 的 2014 年充满了不幸和争议. 这一年 Noder 们经历了太多的伤心事, 经历了漫长的等待, 经历了沉重的分裂之痛. 也许 Noder 们不想回忆14年 Node.js land 发生的事情, 但正因为痛才更有铭记的价...
2015-11-12
React.js编程思想
JavaScript框架层出不穷,在很多程序员看来,React.js是创建大型、快速的Web应用的最好方式。这一款由Facebook出品的JS框架,无论是在Facebook还是在Instagram中,它的表现都非常出色。 使用React.j...
2015-11-12
从2014年的发展来展望JS的未来将会如何
<font face="寰�杞�闆呴粦, Arial, sans-serif ">2014骞达紝杞�浠惰�屼笟鍙戝睍杩呴€燂紝鍚勭�嶈��瑷€灞傚嚭涓嶇┓锛屼互婊¤冻鐢ㄦ埛涓嶆柇鍙樺寲鐨勯渶姹傘€傝繖浜涜��...
2015-11-12
12个你未必知道的CSS小知识
虽然CSS并不是一种很复杂的技术,但就算你是一个使用CSS多年的高手,仍然会有很多CSS用法/属性/属性值你从来没使用过,甚至从来没听说过。 1.CSS的color属性并非只能用于文本显示 对于CSS的color属性,相信所有Web开发人员...
2015-11-12
Vue.js组件tab实现选项卡切换
本文实例为大家分享了vue插件tab选项卡的具体代码,供大家参考,具体内容如下 效果图: 代码如下: <!DOCTYPE html> <html lang="en"> <head> ...
2017-03-13
ajax为什么令人惊异?ajax的优缺点
使用Ajax的最大优点,就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变的信息。 Ajax不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。就像DHT...
2015-11-12
回到顶部