ホームページ >ウェブフロントエンド >jsチュートリアル >me_javascript スキルを使って JavaScript プロトタイプとプロトタイプ チェーンを学習する
JavaScript を使用したことのある学生はプロトタイプに精通しているはずですが、初心者は関数にプロトタイプ属性があり、アクセスなどの関数を追加できることしか知りません。最近、いくつかの高度な JavaScript プログラミングを検討し、ついにその謎を明らかにしました。
各関数には、オブジェクトへの参照であるプロトタイプ属性があります。このオブジェクトはプロトタイプ オブジェクトと呼ばれます。プロトタイプ オブジェクトには、関数インスタンスによって共有されるメソッドとプロパティが含まれます。これは、関数がコンストラクター呼び出しとして使用されることを意味します。 new 演算子を使用して呼び出すと、新しく作成されたオブジェクトはプロトタイプ オブジェクトからプロパティとメソッドを継承します。従来のオブジェクト指向言語とは異なり、JavaScript の継承メカニズムはクラス クラスではなくプロトタイプに基づいています。
1. プライベート変数と関数
プロトタイプについて詳しく話す前に、プロトタイプの設計意図をよりよく理解できるように、関連するいくつかのことについて話しましょう。 JavaScript プロトタイプ チェーンを理解する前に、まず JavaScript スコープ チェーンを理解する必要があります。 JavaScriptの関数スコープでは、関数内に定義された変数や関数が外部へのインターフェースを提供していないと、外部からアクセスできなくなり、プライベート変数やプライベート関数となります。
function Obj(){ var a=0; //私有变量 var fn=function(){ //私有函数 } }
このように、変数 a と関数 fn は関数オブジェクト Obj の外部ではアクセスできません。これらはプライベートになり、関数 Obj のインスタンスでもこれらの変数と関数にアクセスできなくなります。
var o=new Obj(); console.log(o.a); //undefined console.log(o.fn); //undefined
2. 静的変数と関数
関数が定義され、「.」で追加された属性と関数はオブジェクト自体からアクセスできますが、そのインスタンスにはアクセスできない場合、そのような変数と関数はそれぞれ静的変数と静的関数と呼ばれます。 Java と C# を使用すると、static の意味が簡単に理解できます。
function Obj(){} Obj.a=0; //静态变量 Obj.fn=function(){ //静态函数 } console.log(Obj.a); //0 console.log(typeof Obj.fn); //function var o=new Obj(); console.log(o.a); //undefined console.log(typeof o.fn); //undefined
3. インスタンス変数、関数
オブジェクト指向プログラミングでは、いくつかのライブラリ関数に加えて、オブジェクトの定義時にいくつかのプロパティとメソッドを定義することが望まれます。これらはインスタンス化後にアクセスできます。
function Obj(){ this.a=[]; //实例变量 this.fn=function(){ //实例方法 } } console.log(typeof Obj.a); //undefined console.log(typeof Obj.fn); //undefined var o=new Obj(); console.log(typeof o.a); //object console.log(typeof o.fn); //function
これでも上記の目的は達成できますが、
function Obj(){ this.a=[]; //实例变量 this.fn=function(){ //实例方法 } } var o1=new Obj(); o1.a.push(1); o1.fn={}; console.log(o1.a); //[1] console.log(typeof o1.fn); //object var o2=new Obj(); console.log(o2.a); //[] console.log(typeof o2.fn); //function
上記のコードの実行結果は完全に期待どおりですが、a と fn は o1 で変更されていますが、配列と関数はどちらもオブジェクトであり参照型であるため、問題も示されています。これは、o1 のプロパティとメソッドは o2 のものと同じ名前ですが、それらは参照ではなく、Obj オブジェクトによって定義されたプロパティとメソッドのコピーであることを意味します。
これは属性にとっては問題ではありませんが、メソッドにとっては大きな問題です。メソッドはまったく同じ機能を実行しますが、関数オブジェクトに 1000 のメソッドとインスタンスのメソッドがある場合、それぞれのインスタンスが 2 回コピーされるからです。何千ものメソッドのコピーを維持する必要がありますが、これは明らかに非科学的です。プロトタイプが誕生しました。まずオブジェクトの意味を見てみましょう:
4. 通常のオブジェクトと関数オブジェクト
JavaScript では、すべてがオブジェクトです。しかし、オブジェクトも異なります。通常のオブジェクトと関数オブジェクトに分かれており、Object と Function は JS に付属する関数オブジェクトです。以下にいくつかの例を示します
function f1(){}; var f2 = function(){}; var f3 = new Function('str','console.log(str)'); var o3 = new f1(); var o1 = {}; var o2 =new Object(); console.log(typeof Object); //function console.log(typeof Function); //function console.log(typeof o1); //object console.log(typeof o2); //object console.log(typeof o3); //object console.log(typeof f1); //function console.log(typeof f2); //function console.log(typeof f3); //function
上記の例では、o1 o2 o3 は通常のオブジェクト、f1 f2 f3 は関数オブジェクトです。区別する方法は実際には非常に簡単です。new Function() によって作成されたオブジェクトはすべて関数オブジェクトであり、その他は通常のオブジェクトです。 f1、f2 は最終的に new Function() を通じて作成されます。 Function オブジェクトは New Function() によっても作成されます。
5. プロトタイプ プロトタイプ
JavaScript では、オブジェクト (関数) が定義されるたびに、そのオブジェクトには事前定義されたプロパティがいくつか含まれます。関数オブジェクトのプロパティの 1 つはプロトタイプ オブジェクトです。注: 通常のオブジェクトにはプロトタイプはありませんが、_ proto _ 属性があります。
プロトタイプ オブジェクトは、実際には通常のオブジェクトです (関数オブジェクトである Function.prototype を除きますが、これは非常に特殊です。プロトタイプ属性はありません (前述したように、関数オブジェクトはすべてプロトタイプ属性を持ちます))。以下の例を見てください:
function f1(){}; console.log(f1.prototype) //f1{} console.log(typeof f1.prototype) //Object console.log(typeof Function.prototype) // Function,这个特殊 console.log(typeof Object.prototype) // Object console.log(typeof Function.prototype.prototype) //undefined
この console.log(f1.prototype) //f1 {} の出力から、f1.prototype が f1 のインスタンス オブジェクトであることがわかります (ここに f1 のプロトタイプ オブジェクトがあります)。 f1 が作成されると、インスタンス オブジェクトが作成され、そのプロトタイプに割り当てられます。基本的なプロセスは次のとおりです。
var temp = new f1(); f1. prototype = temp;
var temp1 = new Function (); Function.prototype = temp1;
var person = function(name){ this.name = name }; person.prototype.getName = function(){ return this.name; // 这里this指向原型对象person ==>person.name } var xpg = new person(‘xiaopingguo'); xpg.getName(); //xiaopingguo
从这个例子可以看出,通过给person.prototype设置了一个函数对象的属性,那有person实例(例中:xpg)出来的普通对象就继承了这个属性。具体是怎么实现的继承,就要讲到下面的原型链了。
在深入的讲一遍:无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性(同时它也是一个对象),默认情况下prototype属性(对象)会默认获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针,有些绕了啊,写代码、上图!
function Person(){ }
根据上图可以看出Person对象会自动获得prototyp属性,而prototype也是一个对象,会自动获得一个constructor属性,该属性正是指向Person对象。
当调用构造函数创建一个实例的时候,实例内部将包含一个内部指针(很多浏览器这个指针名字为_ proto _ )指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间。
function Person(name){ this.name=name; } Person.prototype.printName=function(){ alert(this.name); } var person1=new Person('Byron'); var person2=new Person('Frank');
Person的实例person1中包含了name属性,同时自动生成一个_ proto _属性,该属性指向Person的prototype,可以访问到prototype内定义的printName方法,大概就是这个样子的:
写段程序测试一下看看prototype内属性、方法是能够共享
function Person(name){ this.name=name; } Person.prototype.share=[]; Person.prototype.printName=function(){ alert(this.name); } var person1=new Person('Byron'); var person2=new Person('Frank'); person1.share.push(1); person2.share.push(2); console.log(person2.share); //[1,2]
果不其然!实际上当代码读取某个对象的某个属性的时候,都会执行一遍搜索,目标是具有给定名字的属性,搜索首先从对象实例开始,如果在实例中找到该属性则返回,如果没有则查找prototype,如果还是没有找到则继续递归prototype的prototype对象,直到找到为止,如果递归到object仍然没有则返回错误。同样道理如果在实例中定义如prototype同名的属性或函数,则会覆盖prototype的属性或函数。—-这就是Javascript的原型链。
function Person(name){ this.name=name; } Person.prototype.share=[]; var person=new Person('Byron'); person.share=0; console.log(person.share); //0;而不是prototype中的[]
6.原型链
JS在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做_ proto _的内置属性,用于指向创建它的函数对象的原型对象prototype。以上面的例子
同样,person.prototype对象也有_ proto _属性,它指向创建它的函数对象(Object)的prototype
继续,Object.prototype对象也有_ proto _属性,但它比较特殊,为null
这个有_ proto _ 串起来的直到Object.prototype._ proto _为null的链叫做原型链。如下图:
原型链中属性查找:
当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部 - 也就是 Object.prototype - 但是仍然没有找到指定的属性,就会返回 undefined,我们来看一个例子:
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
通过代码运行,我们发现subtract是安装我们所说的向上查找来得到结果的,但是add方式有点小不同,这也是我想强调的,就是属性在查找的时候是先查找自身的属性,如果没有再查找原型,再没有,再往上走,一直插到Object的原型上,所以在某种层面上说,用 for in语句遍历属性的时候,效率也是个问题。
还有一点我们需要注意的是,我们可以赋值任何类型的对象到原型上,但是不能赋值原子类型的值,比如如下代码是无效的:
function Foo() {} Foo.prototype = 1; // 无效
7、构造函数、实例和原型对象的区别
实例就是通过构造函数创建的。实例一创造出来就具有constructor属性(指向构造函数)和proto属性(指向原型对象),
构造函数中有一个prototype属性,这个属性是一个指针,指向它的原型对象。
原型对象内部也有一个指针(constructor属性)指向构造函数:Person.prototype.constructor = Person;
实例可以访问原型对象上定义的属性和方法。
在这里person1和person2就是实例,prototype是他们的原型对象。
再举个栗子:
<script type="text/javascript"> function Animal(name) //积累构造函数 { this.name = name;//设置对象属性 } Animal.prototype.behavior = function() //给基类构造函数的prototype添加behavior方法 { alert("this is a "+this.name); } var Dog = new Animal("dog");//创建Dog对象 var Cat = new Animal("cat");//创建Cat对象 Dog.behavior();//通过Dog对象直接调用behavior方法 Cat.behavior();//output "this is a cat" alert(Dog.behavior==Cat.behavior);//output true; </script>
8、原型的使用
原型使用方式1:
在使用原型之前,我们需要先将代码做一下小修改:
var Calculator = function (decimalDigits, tax) { this.decimalDigits = decimalDigits; this.tax = tax; };
然后,通过给Calculator对象的prototype属性赋值对象字面量来设定Calculator对象的原型。
Calculator.prototype = { add: function (x, y) { return x + y; }, subtract: function (x, y) { return x - y; } }; //alert((new Calculator()).add(1, 3));
我们就可以new Calculator对象以后,就可以调用add方法来计算结果了。
原型使用方式2:
第二种方式是,在赋值原型prototype的时候使用function立即执行的表达式来赋值,即如下格式:
Calculator.prototype = function () { add = function (x, y) { return x + y; }, subtract = function (x, y) { return x - y; } return { add: add, subtract: subtract } } (); //alert((new Calculator()).add(11, 3));
同样的方式,我们可以new Calculator对象以后调用add方法来计算结果了。
分步声明:
上述使用原型的时候,有一个限制就是一次性设置了原型对象,我们再来说一下如何分来设置原型的每个属性吧。
var BaseCalculator = function () { //为每个实例都声明一个小数位数 this.decimalDigits = 2; }; //使用原型给BaseCalculator扩展 BaseCalculator.prototype.add = function (x, y) { return x + y; }; BaseCalculator.prototype.subtract = function (x, y) { return x - y; };
声明了一个BaseCalculator对象,构造函数里会初始化一个小数位数的属性decimalDigits,然后通过原型属性设置2个function,分别是add(x,y)和subtract(x,y),当然你也可以使用前面提到的2种方式的任何一种,我们的主要目的是看如何将BaseCalculator对象设置到真正的Calculator的原型上。
var BaseCalculator = function() { this.decimalDigits = 2; }; BaseCalculator.prototype = { add: function(x, y) { return x + y; }, subtract: function(x, y) { return x - y; } };
重写原型:
在使用第三方JS类库的时候,往往有时候他们定义的原型方法是不能满足我们的需要,但是又离不开这个类库,所以这时候我们就需要重写他们的原型中的一个或者多个属性或function,我们可以通过继续声明的同样的add代码的形式来达到覆盖重写前面的add功能,代码如下:
//覆盖前面Calculator的add() function Calculator.prototype.add = function (x, y) { return x + y + this.tax; }; var calc = new Calculator(); alert(calc.add(1, 1));
这样,我们计算得出的结果就比原来多出了一个tax的值,但是有一点需要注意:那就是重写的代码需要放在最后,这样才能覆盖前面的代码。
9、hasOwnProperty函数
hasOwnProperty是Object.prototype的一个方法,它可是个好东西,他能判断一个对象是否包含自定义属性而不是原型链上的属性,因为hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。
// 修改Object.prototype Object.prototype.bar = 1; var foo = {goo: undefined}; foo.bar; // 1 'bar' in foo; // true foo.hasOwnProperty('bar'); // false foo.hasOwnProperty('goo'); // true
只有 hasOwnProperty 可以给出正确和期望的结果,这在遍历对象的属性时会很有用。 没有其它方法可以用来排除原型链上的属性,而不是定义在对象自身上的属性。
但有个恶心的地方是:JavaScript 不会保护 hasOwnProperty 被非法占用,因此如果一个对象碰巧存在这个属性,就需要使用外部的 hasOwnProperty 函数来获取正确的结果。
var foo = { hasOwnProperty: function() { return false; }, bar: 'Here be dragons' }; foo.hasOwnProperty('bar'); // 总是返回 false // 使用{}对象的 hasOwnProperty,并将其上下为设置为foo {}.hasOwnProperty.call(foo, 'bar'); // true
当检查对象上某个属性是否存在时,hasOwnProperty 是唯一可用的方法。同时在使用 for in loop 遍历对象时,推荐总是使用 hasOwnProperty 方法,这将会避免原型对象扩展带来的干扰,我们来看一下例子:
// 修改 Object.prototype Object.prototype.bar = 1; var foo = {moo: 2}; for(var i in foo) { console.log(i); // 输出两个属性:bar 和 moo }
我们没办法改变for in语句的行为,所以想过滤结果就只能使用hasOwnProperty 方法,代码如下:
// foo 变量是上例中的 for(var i in foo) { if (foo.hasOwnProperty(i)) { console.log(i); //moo } }
这个版本的代码是唯一正确的写法。由于我们使用了 hasOwnProperty,所以这次只输出 moo。如果不使用 hasOwnProperty,则这段代码在原生对象原型(比如 Object.prototype)被扩展时可能会出错。
总结:推荐使用 hasOwnProperty,不要对代码运行的环境做任何假设,不要假设原生对象是否已经被扩展了
10、拓展
_ ptoto _属性
_ ptoto _属性(IE浏览器不支持)是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor,通过这两个属性,就可以访问原型里的属性和方法了。
Javascript中的对象实例本质上是由一系列的属性组成的,在这些属性中,有一个内部的不可见的特殊属性——_ proto _,该属性的值指向该对象实例的原型,一个对象实例只拥有一个唯一的原型。
function Box(){ //大写,代表构造函数 Box.prototype.name = "trigkit4";//原型属性 Box.prototype.age = "21"; Box.prototype.run = function()//原型方法 { return this.name + this.age + 'studying'; } } var box1 = new Box(); var box2 = new Box(); alert(box1.constructor);//构造属性,可以获取构造函数本身, //作用是被原型指针定位,然后得到构造函数本身
_ proto _属性和prototype属性的区别
prototype是原型对象中专有的属性。
_ proto _ 是普通对象的隐式属性,在new的时候,会指向prototype所指的对象;
_ ptoto _ 实际上是某个实体对象的属性,而prototype则是属于构造函数的属性。_ ptoto _只能在学习或调试的环境下使用。
原型模式的执行流程
1.先查找构造函数实例里的属性或方法,如果有,就立即返回。
2.如果构造函数的实例没有,就去它的原型对象里找,如果有,就立即返回
原型对象的
function Box(){ //大写,代表构造函数 Box.prototype.name = "trigkit4";//原型属性 Box.prototype.age = "21"; Box.prototype.run = function()//原型方法 { return this.name + this.age + 'studying'; } } var box1 = new Box(); alert(box1.name);//trigkit4,原型里的值 box1.name = "Lee"; alert(box1.name);//Lee,就进原则 var box2 = new Box(); alert(box2.name);//trigkit4,原型的值,没有被box1修改
构造函数的
function Box(){ this.name = "Bill"; } Box.prototype.name = "trigkit4";//原型属性 Box.prototype.age = "21"; Box.prototype.run = function()//原型方法 { return this.name + this.age + 'studying'; } var box1 = new Box(); alert(box1.name);//Bill,原型里的值 box1.name = "Lee"; alert(box1.name);//Lee,就进原则
综上,整理一下:
function Person(){}; Person.prototype.name = "trigkit4"; Person.prototype.say = function(){ alert("Hi"); } var p1 = new Person();//prototype是p1和p2的原型对象 var p2 = new Person();//p2为实例化对象,其内部有一个__proto__属性,指向Person的prototype console.log(p1.prototype);//undefined,这个属性是一个对象,访问不到 console.log(Person.prototype);//Person console.log(Person.prototype.constructor);//原型对象内部也有一个指针(constructor属性)指向构造函数 console.log(p1.__proto__);//这个属性是一个指针指向prototype原型对象 p1.say();//实例可以访问到在原型对象上定义的属性和方法
- 原型对象.isPrototypeof(实例对象) 判断实例对象的原型是不是当前对象。
以上就是本文的全部内容,希望对大家的学习有所帮助。