JS原型链小结


总结1,常用继承:

prototype的function中: return实现了public, 其余为private

var BaseCalculator = function() {
    this.decimalDigits = 2;
};
BaseCalculator.prototype = function() {
     add= function (x,y) {
         return x + y;
     },
     subtract=function (x,y) {
         return x - y;
     }
     return {
        A:add,
        S:subtract
     } 
}();
var Calculator = function() {
    this.tax = 3;
};
//继承
Calculator.prototype =new BaseCalculator();
//子类扩展方法
Calculator.prototype.Other = function(x, y) {
    return x + y + this.tax;
}

var cc = new Calculator();

var proto = Calculator.prototype;


==>June 6 补充module模式

var blogModule = (function () {
    var my = {}, privateName = "博客园";

    function privateAddTopic(data) {
        // 这里是内部处理代码
    }

    my.Name = privateName;
    my.AddTopic = function (data) {
        privateAddTopic(data);
    };

    return my;
} ());
可以在后续js中扩展此对象的方法

var blogModule = (function (my) {
    my.AddPhoto = function () {
        //添加内部代码  
    };
    return my;
} (blogModule || {}));





总结2,手动修改prototype的指向:

function Foo() {
    this.value = 42;
}
Foo.prototype = {
    method: function() {}
};

function Bar() {}

// 设置Bar的prototype属性为Foo的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
// 修正Bar.prototype.constructor为Bar本身
Bar.prototype.constructor = Bar;

var test = new Bar() // 创建Bar的一个新实例


总结3,通过__proto__修改prototype的指向(包括IE9在内的IE不支持)

obj = {};
obj.__proto__.toString = function() { return "hello"; }
test = {};
//alert(test);
var proto = obj.__proto__;


总结4, 查找方法的优先级

function foo() 
{
	this.add = function (x, y) {
		return x + y;
	}
}

foo.prototype.add = function (x, y) {
	return x + y + 10;
}

Object.prototype.subtract = function (x, y) {
	return x - y;
}

var f = new foo();
alert(f.add(1, 2)); //结果是3,而不是13
alert(f.subtract(1, 2)); //结果是-1
f.__proto__.add(1,2)//这个是13




转自:http://blog.jobbole.com/66441/

本文由 伯乐在线 - 埃姆杰 翻译自 Script Junkie。欢迎加入技术翻译小组。转载请参见文章末尾处的要求。

请在此暂时忘记之前学到的面向对象的一切知识。这里只需要考虑赛车的情况。是的,就是赛车。

最近我正在观看 24 Hours of Le Mans ,这是法国流行的一项赛事。最快的车被称为 Le Mans 原型车。这些车虽然是由“奥迪”或“标致”这些厂商制造的,可它们并不是你在街上或速公路上所见到的那类汽车。它们是专为参加高速耐力赛事而制造出来的。

厂家投入巨额资金,用于研发、设计、制造这些原型车,而工程师们总是努力尝试将这项工程做到极致。他们在合金、生物燃料、制动技术、轮胎的化合物成分和安全特性上进行了各种实验。随着时间的推移,这些实验中的某些技术经过反复改进,随之进入到车辆的主流产品线中。你所驾驶车辆的某些技术,有可能是在赛车原型上第一次亮相的。

你也可以说,这些主流车辆继承了来自赛车的技术原型

到现在,我们就有讨论 JavaScript 中的原型和继承问题的基础了。它虽然并不像你在 C++、Java 或 C# 中了解的经典继承模式一样,但这种方式同样强大,并且有可能会更加灵活。

有关对象和类

JavaScript 中全是对象,这指的是传统意义上的对象,也就是“一个包含了状态和行为的单一实体”。例如,JavaScript 中的数组是含有数个值,并且包含 push、reverse 和 pop 方法的对象。

1
2
3
4
5
var myArray = [1, 2];
myArray.push(3);
myArray.reverse();
myArray.pop();
var length = myArray.length;

现在问题是,push 这样的方法是从何而来的呢?我们前面提到的那些静态语言使用“类语法”来定义对象的结构,但是 JavaScript 是一个没有“类语法”的语言,无法用 Array“类”的语法来定义每个数组对象。而因为 JavaScript 是动态语言,我们可以在实际需要的情况下,将方法任意放置到对象上。例如下面的代码,就在二维空间中,定义了用来表示一个点的点对象,同时还定义了一个 add 方法。

1
2
3
4
5
6
7
8
var point = {
     x : 10,
     y : 5,
     add: function (otherPoint) {
         this .x += otherPoint.x;
         this .y += otherPoint.y;
     }
};

但是上面的做法可扩展性并不好。我们需要确保每一个点对象都含有一个 add 方法,同时也希望所有点对象都共享同一个 add 方法的实现,而不是这个方法手工添加每一个点对象上。这就是原型发挥它作用的地方。

有关原型

在 JavaScript 中,每个对象都保持着一块隐藏的状态 —— 一个对另一个对象的引用,也被称作原型。我们之前创建的数组引用了一个原型对象,我们自行创建的点对象也是如此。上面说原型引用是隐藏的,但也有 ECMAScript(JavaScript 的正式名称)的实现可以通过一个对象的__proto__属性(例如谷歌浏览器)访问到这个原型引用。从概念上讲,我们可以将对象当作类似于 图1 所表示的对象 —— 原型的关系。

 

图 1

展望未来,开发者将能够使用 Object.getPrototypeOf 函数,代替__proto__属性,取得对象原型的引用。在本文写出的时候,已经可以在 Google Chrome,FIrefox 和 IE9 浏览器中使用 Object.getPrototypeOf 函数。更多浏览器在未来会实现此功能,因为它已经是 ECMAScript 标准的一部分了。我们可以使用下面的代码,来证明我们建立的 myArray 和点对象引用的是两个不同的原型对象。

  1. Object.getPrototypeOf(point) != Object.getPrototypeOf(myArray);

对于本文的其余部分,我将交叉使用 __proto__和Object.getPrototypeOf 函数,主要是因为 __proto__ 在图和句子中更容易识别。需要记住的是它(__proto__)不是标准,而Object.getPrototypeOf 函数才是查看对象原型的推荐方法。

是什么让原型如此特别?

我们还没有回答这个问题:数组中 push 这样的方法是从何而来的呢?答案是:它来源于 myArray 原型对象。图 2 是 Chrome 浏览器中脚本调试器的屏幕截图。我们已经调用 Object.getPrototypeOf 方法查看 myArray 的原型对象。

 

图 2

注意 myArray 的原型对象中有许多方法,包括那些在代码示例中调用的 push、pop 和 reverse 方法。因此,原型对象中的确包括 push 方法,但是 myArray 方法如何引用到呢?

1
myArray.push(3);

了解其工作原理的第一步,是要认识到原型并不是特别的。原型只是普通的对象。可以给原型添加方法,属性,并把他们当作其他 JavaScript 对象一样看待。然而,套用乔治·奥威尔的小说《动物农场》中“猪”的说法 —— 所有的对象应当是平等的,但有些对象(遵守规则的)比其他人更加平等。

JavaScript 中的原型对象的确是特殊的,因为他们遵从以下规则。当我们告诉 JavaScript 我们要调用一个对象的 push 方法,或读取对象的 x 属性时,运行时会首先查找对象本身。如果运行时找不到想要的东西,它就会循着 __proto__ 引用和对象原型寻找该成员。当我们  调用 myArray 的 push 方法时,JavaScript 并没有在 myArray 对象上发现 push 方法,而是在 myArray 的原型对象上找到了,于是 JavaScript 调用此方法(见图 3)。

图 3

上面所描述的行为是指一个对象本身继承了原型上的任何方法或属性。JavaScript 中其实不需要使用类语法也能实现继承。就像从赛车原型上继承了相应的技术的车,一个 JavaScript 对象也可以从原型对象上继承功能特性。

图 3 还展示了每个数组对象同时也可以维护自身的状态和成员。在请求得到 myArray 的 length 属性的情况下,JavaScript 会取得 myArray 中 length 属性的值,而不会去读取原型中的对应值。我们可以通过向对象上添加 push 这样的方法来“重写”push 方法。这样就会有效地隐藏原型中的 push 方法实现。

共享原型

JavaScript 中原型的真正神奇之处是多个对象如何维持对同一个原型对象的引用。例如,如果我们创建了这样的两个数组:

1
2
var myArray = [1, 2];
var yourArray = [4, 5, 6];

那么这两个数组将共享同一个原型对象,而下面的代码计算结果为 true:

1
Object.getPrototypeOf(myArray) === Object.getPrototypeOf(yourArray);

如果我们引用两个数组对象上的 push 方法,JavaScript 会去寻找原型上共享的 push 方法。

图 4

JavaScript 中的原型对象提供继承功能,同时也就实现了该方法实现的共享。原型也是链式的。换句话说,因为原型对象只是一个对象,所以一个原型对象可以维持到另一个原型对象的引用。如果你重新审视图 2 便可以看到,原型的 __proto__ 属性是一个指向另一个原型的非空值。当 JavaScript 查找像 push 方法这样的成员时,它会循着原型引用链检查每一个对象,直到找到该成员,或者抵达原型链的末端。原型链为继承和共享开辟了一条灵活的途径。

你可能会问的下一个问题是:我该如何设置那些自定义对象的原型引用呢?例如前面所使用的点对象,如何才能将 add 方法添加到原型对象中,并从多个点对象中继承方法呢?在回答这个问题之前,我们需要看看函数。

有关函数

JavaScript 中的函数也是对象。这样的表述带来了几个重要的结果,而我们并不会在本文中涉及所有的事项。这其中,能将一个函数赋值给一个变量,并且将一个函数作为参数传递给另一个函数的能力构成了现代 JavaScript 编程表达的基本范式。

我们需要关注的是,函数本身就是对象,因此函数可以有自身的方法,属性,并且引用一个原型对象。让我们来讨论下面的代码的含义。

1
2
3
4
5
6
// 这将返回 true:
typeof (Array) === "function"
// 这样的表达式也是:
Object.getPrototypeOf(Array) === Object.getPrototypeOf( function () { })
// 这样的表达式同样:
Array.prototype != null

代码中的第一行证明, JavaScript 中的数组是函数。稍后我们将看到如何调用 Array 函数创建一个新的数组对象。下一行代码,证明了 Array 对象使用与任何其他函数对象相同的原型,就像我们看到数组对象间共享相同的原型一样。最后一行代码证明了 Array 函数都有一个 prototype 属性,而这个 prototype 属性指向一个有效的对象。这个 prototype 属性十分重要。

JavaScript 中的每一个函数对象都有 prototype 属性。千万不要混淆这个 prototype 属性的 __proto__ 属性。他们用途并不相同,也不是指向同一个对象。

1
2
// 返回 true
Object.getPrototypeOf(Array) != Array.prototype

Array.__proto__ 提供的是 数组原型 – 请把它当作 Array 函数所继承的对象。

而 Array.protoype,提供的的是 所有数组的原型对象。也就是说,它提供的是像 myArray 这样数组对象的原型对象,也包含了所有数组将会继承的方法。我们可以写一些代码来证明这个事实。

1
2
3
4
// true
Array.prototype == Object.getPrototypeOf(myArray)
// 也是 true
Array.prototype == Object.getPrototypeOf(yourArray);

我们也可以使用这项新知识重绘之前的示意图。

图 5

基于所知道的知识,请想象创建一个新的对象,并让新对象表现地像数组的过程。一种方法是使用下面的代码。

1
2
3
4
5
6
// 创建一个新的空对象
var o = {};
// 继承自同一个原型,一个数组对象
o.__proto__ = Array.prototype;
// 现在我们可以调用数组的任何方法...
o.push(3);

虽然这段代码很有趣,也能工作,可问题在于,并不是每一个 JavaScript 环境都支持可写的 __proto__ 对象属性。幸运的是,JavaScript 确实有一个创建对象内建的标准机制,只需要一个操作符,就可以创建新对象,并且设置新对象的 __proto__ 引用 – 那就是“new”操作符。

1
2
var o = new Array();
o.push(3);

JavaScript 中的 new 操作符有三个基本任务。首先,它创建新的空对象。接下来,它将设置新对象的 __proto__ 属性,以匹配所调用函数的原型属性。最后,操作符调用函数,将新对象作为“this”引用传递。如果要扩展最后两行代码,就会变成如下情况:

1
2
3
4
var o = {};
o.__proto__ = Array.prototype;
Array.call(o);
o.push(3);

函数的 call 方法允许你在调用函数的情况下在函数内部指定“this”所引用的对象。当然,函数的作者在这种情况下需要实现这样的函数。一旦作者创建了这样的函数,就可以将其称之为构造函数。

构造函数

构造函数和普通的函数一样,但是具有以下两个特殊性质。

  1. 通常构造函数的首字母是大写的(让识别构造函数变得更容易)。
  2. 构造函数通常要和 new 操作符结合,用来构造新对象。

Array 就是一个构造函数的例子。Array 函数需要和 new 操作符一起使用,而且 Array 的首字母是大写的。JavaScript 将 Array 作为内置函数包括在内,而任何人都可以写出自己的构造函数。事实上,我们最后可以为先前创建的点对象编写出构造函数。

1
2
3
4
5
6
7
8
9
10
11
var Point = function (x, y) {
     this .x = x;
     this .y = y;
     this .add = function (otherPoint) {
         this .x += otherPoint.x;
         this .y += otherPoint.y;
     }
}
var p1 = new Point(3, 4);
var p2 = new Point(8, 6);
p1.add(p2);

在上面的代码中,我们使用了 new 操作符和 Point 函数来构造点对象,这个对象带有 x 属性和 y 属性和一个 add 方法。你可以将最后的结果想象成图 6 的样子。

图 6

现在的问题是我们的每个点对象中仍然有单独的 add 方法。使用我们学到的原型和继承的知识,我们更希望将点对象的 add 方法从每个点实例中转移到 Point.prototype 中。要达到继承 add 方法的效果,我们所需要做的,就是修改 Point.prototype 对象。

1
2
3
4
5
6
7
8
9
10
11
var Point = function (x, y) {
     this .x = x;
     this .y = y;
}
Point.prototype.add = function (otherPoint) {
     this .x += otherPoint.x;
     this .y += otherPoint.y;
}
var p1 = new Point(3, 4);
var p2 = new Point(8, 6);
p1.add(p2);

大功告成!我们刚刚在 JavaScript 中完成原型式的继承模式!

图 7

总结

我希望这篇文章能够帮助你揭开 JavaScript 原型概念的神秘面纱。开始看到的是原型怎样让一个对象从其他对象中继承功能,然后看到怎样结合 new 操作符和构造函数来构建对象。这里所提到的,只是开启对象原型力量和灵活性的第一步。本文鼓励你自己发现学习有关原型和 JavaScript 语言的新信息。

同时,请小心驾驶。你永远不会知道这些行驶在路上的车辆会从他们的原型继承到什么(有缺陷)的技术。








转自: http://www.cnblogs.com/manyiString/archive/2012/05/22/JS2.html


原型使用方式1

个人理解,之前写JS都是这样:

复制代码
 1 var decimalDigits = 2,
 2             tax = 5;
 3 
 4         function add(x, y) {
 5             return x + y;
 6         }
 7 
 8         function subtract(x, y) {
 9             return x - y;
10         }
11 
12         //alert(add(1, 3));
复制代码

但是,这个并不能体现OOP思想,看了原型与原型链之后觉得OOP一目了然:

1  var Calculator = function (decimalDigits, tax) {
2             this.decimalDigits = decimalDigits;
3             this.tax = tax;
4         };

然后给Calculator的prototype属性赋值对象字面量来设定Calculator对象的原型。(个人觉得这里的原型就如同C#中类的概念,prototype则是用来给类添加属性,方法的)

复制代码
 Calculator.prototype = {
            add: function (x, y) {
                return x + y;
            },

            subtract: function (x, y) {
                return x - y;
            }
        };
        //alert((new Calculator()).add(1, 3));
复制代码

这样,通过new 一个对象就可以调用里面的公开的方法,属性。

原型使用方式2

当我们把一堆方法写到Calculator中,但是有些方法我们不想对外公开,即实现public/private,那么我们只能返回公开的方法:
复制代码
 1 var Calculaotr = function(x, y) {
 2     this.x = x;
 3     this.y = y;
 4 };
 5 Calculaotr.prototype = function() {
 6     add= function (x,y) {
 7         return x + y;
 8     },
 9     subtract=function (x,y) {
10         return x - y;
11     }
12     return {
13         A:add,
14         S:subtract
15     } 
16 }();
复制代码

这里用利用函数自执行在加载文件同时,执行上面的JS代码,那么我们就可以访问对外公开的方法和属性,如果不通过自执行,则会报异常:

在C#中,我们可能会遇到这样的情况,类A的一个属性是类B型,在JS中,可以通过以下方式实现:

复制代码
 1 var BaseCalculator = function() {
 2     this.decimalDigits = 2;
 3 };
 4 BaseCalculator.prototype = {
 5     A: function(x, y) {
 6         return x + y;
 7     },
 8     S: function(x, y) {
 9         return x - y;
10     }
11 };
12 var Calculator = function() {
13     this.tax = 3;
14 };
15Calculator.prototype = new BaseCalculator();
复制代码

这里我们可以看到Calculator的原型是指向到BaseCalculator的一个实例上,目的是让Calculator集成它的add(x,y)和subtract(x,y)这2个function,

还有一点要说的是,由于它的原型是BaseCalculator的一个实例,所以不管你创建多少个Calculator对象实例,他们的原型指向的都是同一个实例。

如果我们不想让Calculator对象访问BaseCalculator的decimalDigits属性,可以这样:

复制代码
var BaseCalculator = function() {
    this.decimalDigits = 2;
};
BaseCalculator.prototype = {
    A: function(x, y) {
        return x + y;
    },
    S: function(x, y) {
        return x - y;
    }
};
var Calculator = function() {
    this.tax = 3;
};
Calculator.prototype =new prototype;
复制代码

 

通过以上两种原型使用方式,结合C#中的继承,不难想到JS中如何重写原型。

重写原型:

在项目中,引入外部JS库,但是有些方法并不是我们想要的,此时我们通过重写原型,就可以达到我们想要的结果:

//重写原型
Calculaotor.prototype.add = function(x, y) {
    return x + y + this.tax;
}

原型链

复制代码
 1 function Foo() {
 2     this.value = 42;
 3 }
 4 Foo.prototype = {
 5     method: function() {}
 6 };
 7 
 8 function Bar() {}
 9 
10 // 设置Bar的prototype属性为Foo的实例对象
11 Bar.prototype = new Foo();
12 Bar.prototype.foo = 'Hello World';
13 
14 // 修正Bar.prototype.constructor为Bar本身
15 Bar.prototype.constructor = Bar;
16 
17 var test = new Bar() // 创建Bar的一个新实例
18 
19 // 原型链
20 test [Bar的实例]
21     Bar.prototype [Foo的实例] 
22         { foo: 'Hello World' }
23         Foo.prototype
24             {method: ...};
25             Object.prototype
26                 {toString: ... /* etc. */};
复制代码

上面的例子中,test 对象从 Bar.prototype 和 Foo.prototype 继承下来;因此,它能访问 Foo 的原型方法 method。同时,它也能够访问那个定义在原型上的 Foo 实例属性 value。需要注意的是 new Bar() 不会创造出一个新的 Foo 实例,而是重复使用它原型上的那个实例;因此,所有的 Bar 实例都会共享相同的 value 属性。

属性查找

当查找一个对象的属性时,会遍历原型链,一直往顶层Object找,如果没有找到,则返回undefined.

复制代码
 1  function foo() {
 2             this.add = function (x, y) {
 3                 return x + y;
 4             }
 5         }
 6 
 7         foo.prototype.add = function (x, y) {
 8             return x + y + 10;
 9         }
10 
11         Object.prototype.subtract = function (x, y) {
12             return x - y;
13         }
14 
15         var f = new foo();
16         alert(f.add(1, 2)); //结果是3,而不是13
17         alert(f.subtract(1, 2)); //结果是-1
复制代码

以上add函数返回的是3,而不是13则说明,属性查找时,优先查找自己的属性。然后在往上一级找,最后找Object,这样看来,在遍历时用for in效率就是个问题。

还有一点,我们可以赋值任何类型的对象到原型上,但是不能赋值原子类型的值,比如如下代码是无效的:

function Foo() {}
Foo.prototype = 1; // 无效

hasOwnProperty函数

hasOwnProperty是判断一个对象是否包含自定义属性而不是原型链上的属性,是JS中唯一一个查找属性,但不查找原型链的函数。

但是JS不会保护hasOwnProperty函数,如果刚好某个对象中也有hasOwnProperty函数,则我们可以通过以下方式正确获得想要的结果:

 

alert({}.hasOwnProperty.call(c, 'tax'));//返回true

这里的c是Calculator的一个对象,tax是我们要找的属性。

当我面在for in loop 语句中查找属性时,用hasOwnProperty函数,提高效率:

复制代码
1  Object.prototype.bar = 1;
2     var foo={moo : 1}
3     for (var i in foo) {
4         if(foo.hasOwnProperty(i)) {
5             alert(console.log(i));
6         } 
7     }//此时只会输出moo属性
复制代码

 

原文地址:http://www.cnblogs.com/TomXu/archive/2012/01/05/2305453.html

 





浅谈JS原型链

原型链

ECMAScript中描述了原型链的概念。我们知道ECMAScript并不像C++,Java那样使用类,但是对象仍然可以通过多种方式创建,其中就有构造函数方式。每个构造函数都有一个原型对象,同时都有一个prototype属性, prototype属性指向构造函数的原型对象,它被用来实现基于原型的继承和共享。而原型对象又都默认会取得一个constructor属性,这个属性包含一个指向构造函数(prototype属性所在函数)的指针。每个通过调用构造函数创建的实例对象都拥有一个指向原型对象的指针,ECMA-262第5版中叫这个指针为[[prototype]],虽然在脚本上没有标准的方式访问[[prototype]],但Chrome、Firefox和Safari在每个对象上都支持一个属性_proto_,而在其他实现中,这个属性对脚本是完全不可见的。假如原型对象等于另一个类型的实例,那么它就拥有指向创建该实例的构造函数的原型对象的指针,依此类推,就形成了一条指针链,这就是原型链的概念。通过下面的图形我们可以更清晰地了解原型链的概念。

ECMA5中可以使用Object.getPrototypeOf()来获取实例的构造函数的prototype

事实上,上图所展示的原型链还少一环。我们知道,所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。函数是可调用的对象,所有函数的默认原型对象都是Object的实例,所以函数的原型对象都会包含一个指向Object构造函数的原型对象的指针,也即指向Object.prototype的指针[[prototype]]。这样就解释了为什么所有自定义对象类型都会继承toLocaleString()、toString()等Object原型对象的默认方法了。还是来上图吧。

当然,还有很重要的一点是我们需要注意的:对象实例中的指针[[prototype]]只指向原型对象,并不指向构造函数。

原型语法

通常,我们可以用一个包含所有属性和方法的对象字面量来重写整个原型对象。例如

1 function Person(){}
2 Person.prototype = {
3   name: "bella",
4   age: 21,
5   sayHello: function(){
6     alert(this.name);
7   }
8 }

不过,我们需要注意的是,重写之后,构造函数Person的原型对象的constructor属性不再指向Person了,因为该语法的本质是完全重写了默认的原型对象,所以constructor属性也就变成了新对象的constructor属性,指向Object构造函数,我们此时就不能通过constuctor来确定对象的类型了。

可以通过Person.prototype.constructor = Person恢复constructor的指针。

原型的动态性

原型在查找值的过程中是一次搜索,当我们想引用一个对象的某个属性时,所引用到的是原型链中包含该属性名的第一个对象所对应的属性值。换句话说,直接引用这个属性的对象会首先被查询是否包含该属性名,如果包含,该属性值就是我们想获取的,查询停止,如果不包含,会接着查询该对象的原型是否包含该属性,依此类推。

我们可以随时动态地为原型添加属性和方法,而且,基于这种搜索过程,我们对原型对象所做的任何修改都能立即从对象实例上看到,即使该修改是在创建实例之后。但如果是用上面提到的语法重写整个原型对象就另当别论了。因为重写原型对象会切断现有原型对象与原来已经存在的任何对象实例之间的联系,它们包含的指针[[prototype]]仍然指向原来的原型对象,我们可以看看下面的小例子。

01 function Person(){}
02 var person1 = new Person();
03 Person.prototype = {
04   name: "bella",
05   age: 21,
06   sayHello: function(){
07     alert(this.name);
08   }
09 }
10 person1.sayHello();  //error

上面的例子中,我们先创建了Person的一个实例对象person1,然后重写了Person的原型对象,之后再调用person1.sayHello()就会发生错误。因为person1中包含的指针[[prototype]]仍然指向原来的原型对象,并不包含新的原型对象中定义的sayHello属性。

原型的问题

原型模式使得所有对象实例在默认情况下取得相同的属性值,对于属性值为函数的情况,这正是我们希望看到的,所有对象实例共享这一函数而不需要重复定义,但是对于属性值为基本值的情况,我们通常希望不同的对象实例拥有不同的基本值,不过,我们可以通过在对象实例上添加同名属性来隐藏原型对象中的属性。但是,如果包含引用类型值的属性,问题就显现出来了。

01 function Person(){}
02 Person.prototype = {
03   name: "bella",
04   age: 21,
05   classmates: ["Lucy""Lily"],
06   sayHello: function(){
07     alert(this.name);
08   }
09 }
10 var person1 = new Person();
11 var person2 = new Person();
12 person1.classmates.push("Mark");
13 alert(person1.classmates === person2.classmates);  //true

这里,我们为Person.prototype对象添加了classmates属性,值为一个字符串数组,然后创建了两个对象实例person1, person2。由于person1, person2所拥有的classmates属性其实是共享原型对象Person.prototype的classmates属性得到的,也就是数组只存在于Person.prototype对象中,person1和person2引用的是同一个数组,对person1中classmates的修改也会从person2.classmates中反映出来,这样会导致所有对象实例共享一个数组,这往往不是我们想要的。

以上,我只是简单地分析了原型链的概念和原型对象的基本特性,希望能对大家有小小的帮助,想要更深刻地认识它,当然还是得靠大家在实际项目中去学习和体会。

 

参考资料:Standard ECMA-262,JavaScript高级程序设计。
















转自: http://www.cnblogs.com/manyiString/archive/2012/05/22/JS2.html


原型使用方式1

个人理解,之前写JS都是这样:

复制代码
 1 var decimalDigits = 2,
 2             tax = 5;
 3 
 4         function add(x, y) {
 5             return x + y;
 6         }
 7 
 8         function subtract(x, y) {
 9             return x - y;
10         }
11 
12         //alert(add(1, 3));
复制代码

但是,这个并不能体现OOP思想,看了原型与原型链之后觉得OOP一目了然:

1  var Calculator = function (decimalDigits, tax) {
2             this.decimalDigits = decimalDigits;
3             this.tax = tax;
4         };

然后给Calculator的prototype属性赋值对象字面量来设定Calculator对象的原型。(个人觉得这里的原型就如同C#中类的概念,prototype则是用来给类添加属性,方法的)

复制代码
 Calculator.prototype = {
            add: function (x, y) {
                return x + y;
            },

            subtract: function (x, y) {
                return x - y;
            }
        };
        //alert((new Calculator()).add(1, 3));
复制代码

这样,通过new 一个对象就可以调用里面的公开的方法,属性。

原型使用方式2

当我们把一堆方法写到Calculator中,但是有些方法我们不想对外公开,即实现public/private,那么我们只能返回公开的方法:
复制代码
 1 var Calculaotr = function(x, y) {
 2     this.x = x;
 3     this.y = y;
 4 };
 5 Calculaotr.prototype = function() {
 6     add= function (x,y) {
 7         return x + y;
 8     },
 9     subtract=function (x,y) {
10         return x - y;
11     }
12     return {
13         A:add,
14         S:subtract
15     } 
16 }();
复制代码

这里用利用函数自执行在加载文件同时,执行上面的JS代码,那么我们就可以访问对外公开的方法和属性,如果不通过自执行,则会报异常:

在C#中,我们可能会遇到这样的情况,类A的一个属性是类B型,在JS中,可以通过以下方式实现:

复制代码
 1 var BaseCalculator = function() {
 2     this.decimalDigits = 2;
 3 };
 4 BaseCalculator.prototype = {
 5     A: function(x, y) {
 6         return x + y;
 7     },
 8     S: function(x, y) {
 9         return x - y;
10     }
11 };
12 var Calculator = function() {
13     this.tax = 3;
14 };
15Calculator.prototype = new BaseCalculator();
复制代码

这里我们可以看到Calculator的原型是指向到BaseCalculator的一个实例上,目的是让Calculator集成它的add(x,y)和subtract(x,y)这2个function,

还有一点要说的是,由于它的原型是BaseCalculator的一个实例,所以不管你创建多少个Calculator对象实例,他们的原型指向的都是同一个实例。

如果我们不想让Calculator对象访问BaseCalculator的decimalDigits属性,可以这样:

复制代码
var BaseCalculator = function() {
    this.decimalDigits = 2;
};
BaseCalculator.prototype = {
    A: function(x, y) {
        return x + y;
    },
    S: function(x, y) {
        return x - y;
    }
};
var Calculator = function() {
    this.tax = 3;
};
Calculator.prototype =new prototype;
复制代码

 

通过以上两种原型使用方式,结合C#中的继承,不难想到JS中如何重写原型。

重写原型:

在项目中,引入外部JS库,但是有些方法并不是我们想要的,此时我们通过重写原型,就可以达到我们想要的结果:

//重写原型
Calculaotor.prototype.add = function(x, y) {
    return x + y + this.tax;
}

原型链

复制代码
 1 function Foo() {
 2     this.value = 42;
 3 }
 4 Foo.prototype = {
 5     method: function() {}
 6 };
 7 
 8 function Bar() {}
 9 
10 // 设置Bar的prototype属性为Foo的实例对象
11 Bar.prototype = new Foo();
12 Bar.prototype.foo = 'Hello World';
13 
14 // 修正Bar.prototype.constructor为Bar本身
15 Bar.prototype.constructor = Bar;
16 
17 var test = new Bar() // 创建Bar的一个新实例
18 
19 // 原型链
20 test [Bar的实例]
21     Bar.prototype [Foo的实例] 
22         { foo: 'Hello World' }
23         Foo.prototype
24             {method: ...};
25             Object.prototype
26                 {toString: ... /* etc. */};
复制代码

上面的例子中,test 对象从 Bar.prototype 和 Foo.prototype 继承下来;因此,它能访问 Foo 的原型方法 method。同时,它也能够访问那个定义在原型上的 Foo 实例属性 value。需要注意的是 new Bar() 不会创造出一个新的 Foo 实例,而是重复使用它原型上的那个实例;因此,所有的 Bar 实例都会共享相同的 value 属性。

属性查找

当查找一个对象的属性时,会遍历原型链,一直往顶层Object找,如果没有找到,则返回undefined.

复制代码
 1  function foo() {
 2             this.add = function (x, y) {
 3                 return x + y;
 4             }
 5         }
 6 
 7         foo.prototype.add = function (x, y) {
 8             return x + y + 10;
 9         }
10 
11         Object.prototype.subtract = function (x, y) {
12             return x - y;
13         }
14 
15         var f = new foo();
16         alert(f.add(1, 2)); //结果是3,而不是13
17         alert(f.subtract(1, 2)); //结果是-1
复制代码

以上add函数返回的是3,而不是13则说明,属性查找时,优先查找自己的属性。然后在往上一级找,最后找Object,这样看来,在遍历时用for in效率就是个问题。

还有一点,我们可以赋值任何类型的对象到原型上,但是不能赋值原子类型的值,比如如下代码是无效的:

function Foo() {}
Foo.prototype = 1; // 无效

hasOwnProperty函数

hasOwnProperty是判断一个对象是否包含自定义属性而不是原型链上的属性,是JS中唯一一个查找属性,但不查找原型链的函数。

但是JS不会保护hasOwnProperty函数,如果刚好某个对象中也有hasOwnProperty函数,则我们可以通过以下方式正确获得想要的结果:

 

alert({}.hasOwnProperty.call(c, 'tax'));//返回true

这里的c是Calculator的一个对象,tax是我们要找的属性。

当我面在for in loop 语句中查找属性时,用hasOwnProperty函数,提高效率:

复制代码
1  Object.prototype.bar = 1;
2     var foo={moo : 1}
3     for (var i in foo) {
4         if(foo.hasOwnProperty(i)) {
5             alert(console.log(i));
6         } 
7     }//此时只会输出moo属性
复制代码

 

原文地址:http://www.cnblogs.com/TomXu/archive/2012/01/05/2305453.html

 





浅谈JS原型链

原型链

ECMAScript中描述了原型链的概念。我们知道ECMAScript并不像C++,Java那样使用类,但是对象仍然可以通过多种方式创建,其中就有构造函数方式。每个构造函数都有一个原型对象,同时都有一个prototype属性, prototype属性指向构造函数的原型对象,它被用来实现基于原型的继承和共享。而原型对象又都默认会取得一个constructor属性,这个属性包含一个指向构造函数(prototype属性所在函数)的指针。每个通过调用构造函数创建的实例对象都拥有一个指向原型对象的指针,ECMA-262第5版中叫这个指针为[[prototype]],虽然在脚本上没有标准的方式访问[[prototype]],但Chrome、Firefox和Safari在每个对象上都支持一个属性_proto_,而在其他实现中,这个属性对脚本是完全不可见的。假如原型对象等于另一个类型的实例,那么它就拥有指向创建该实例的构造函数的原型对象的指针,依此类推,就形成了一条指针链,这就是原型链的概念。通过下面的图形我们可以更清晰地了解原型链的概念。

ECMA5中可以使用Object.getPrototypeOf()来获取实例的构造函数的prototype

事实上,上图所展示的原型链还少一环。我们知道,所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。函数是可调用的对象,所有函数的默认原型对象都是Object的实例,所以函数的原型对象都会包含一个指向Object构造函数的原型对象的指针,也即指向Object.prototype的指针[[prototype]]。这样就解释了为什么所有自定义对象类型都会继承toLocaleString()、toString()等Object原型对象的默认方法了。还是来上图吧。

当然,还有很重要的一点是我们需要注意的:对象实例中的指针[[prototype]]只指向原型对象,并不指向构造函数。

原型语法

通常,我们可以用一个包含所有属性和方法的对象字面量来重写整个原型对象。例如

1 function Person(){}
2 Person.prototype = {
3   name: "bella",
4   age: 21,
5   sayHello: function(){
6     alert(this.name);
7   }
8 }

不过,我们需要注意的是,重写之后,构造函数Person的原型对象的constructor属性不再指向Person了,因为该语法的本质是完全重写了默认的原型对象,所以constructor属性也就变成了新对象的constructor属性,指向Object构造函数,我们此时就不能通过constuctor来确定对象的类型了。

可以通过Person.prototype.constructor = Person恢复constructor的指针。

原型的动态性

原型在查找值的过程中是一次搜索,当我们想引用一个对象的某个属性时,所引用到的是原型链中包含该属性名的第一个对象所对应的属性值。换句话说,直接引用这个属性的对象会首先被查询是否包含该属性名,如果包含,该属性值就是我们想获取的,查询停止,如果不包含,会接着查询该对象的原型是否包含该属性,依此类推。

我们可以随时动态地为原型添加属性和方法,而且,基于这种搜索过程,我们对原型对象所做的任何修改都能立即从对象实例上看到,即使该修改是在创建实例之后。但如果是用上面提到的语法重写整个原型对象就另当别论了。因为重写原型对象会切断现有原型对象与原来已经存在的任何对象实例之间的联系,它们包含的指针[[prototype]]仍然指向原来的原型对象,我们可以看看下面的小例子。

01 function Person(){}
02 var person1 = new Person();
03 Person.prototype = {
04   name: "bella",
05   age: 21,
06   sayHello: function(){
07     alert(this.name);
08   }
09 }
10 person1.sayHello();  //error

上面的例子中,我们先创建了Person的一个实例对象person1,然后重写了Person的原型对象,之后再调用person1.sayHello()就会发生错误。因为person1中包含的指针[[prototype]]仍然指向原来的原型对象,并不包含新的原型对象中定义的sayHello属性。

原型的问题

原型模式使得所有对象实例在默认情况下取得相同的属性值,对于属性值为函数的情况,这正是我们希望看到的,所有对象实例共享这一函数而不需要重复定义,但是对于属性值为基本值的情况,我们通常希望不同的对象实例拥有不同的基本值,不过,我们可以通过在对象实例上添加同名属性来隐藏原型对象中的属性。但是,如果包含引用类型值的属性,问题就显现出来了。

01 function Person(){}
02 Person.prototype = {
03   name: "bella",
04   age: 21,
05   classmates: ["Lucy""Lily"],
06   sayHello: function(){
07     alert(this.name);
08   }
09 }
10 var person1 = new Person();
11 var person2 = new Person();
12 person1.classmates.push("Mark");
13 alert(person1.classmates === person2.classmates);  //true

这里,我们为Person.prototype对象添加了classmates属性,值为一个字符串数组,然后创建了两个对象实例person1, person2。由于person1, person2所拥有的classmates属性其实是共享原型对象Person.prototype的classmates属性得到的,也就是数组只存在于Person.prototype对象中,person1和person2引用的是同一个数组,对person1中classmates的修改也会从person2.classmates中反映出来,这样会导致所有对象实例共享一个数组,这往往不是我们想要的。

以上,我只是简单地分析了原型链的概念和原型对象的基本特性,希望能对大家有小小的帮助,想要更深刻地认识它,当然还是得靠大家在实际项目中去学习和体会。

 

参考资料:Standard ECMA-262,JavaScript高级程序设计。







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值