Studenten, die JavaScript verwendet haben, müssen mit Prototypen vertraut sein, aber Anfänger wissen nicht, was es ist. Sie wissen nur, dass Funktionen zum Beispiel hinzugefügt werden können Ich habe mir kürzlich einige fortgeschrittene JavaScript-Programme angesehen und schließlich sein Geheimnis gelüftet.
Jede Funktion verfügt über ein Prototypattribut, das einen Verweis auf ein Objekt darstellt. Das Prototypobjekt enthält Methoden und Eigenschaften, die von Funktionsinstanzen gemeinsam genutzt werden, was bedeutet, dass die Funktion als Konstruktoraufruf verwendet wird. Wenn es mit dem neuen Operator aufgerufen wird, erbt das neu erstellte Objekt Eigenschaften und Methoden vom Prototypobjekt. Im Gegensatz zu herkömmlichen objektorientierten Sprachen basiert der Vererbungsmechanismus von Javascript auf Prototypen und nicht auf Klassenklassen.
1. Private Variablen und Funktionen
Bevor wir ausführlich über Prototypen sprechen, wollen wir über einige verwandte Dinge sprechen, damit wir die Designabsicht des Prototyps besser verstehen können. Bevor Sie die JavaScript-Prototypkette verstehen, müssen Sie zunächst die JavaScript-Bereichskette verstehen. Wenn im Funktionsumfang von JavaScript die innerhalb der Funktion definierten Variablen und Funktionen keine Schnittstelle zur Außenwelt bereitstellen, sind sie von außen nicht zugänglich, dh sie werden zu privaten Variablen und privaten Funktionen.
function Obj(){ var a=0; //私有变量 var fn=function(){ //私有函数 } }
Auf diese Weise kann nicht außerhalb des Funktionsobjekts Obj auf die Variable a und die Funktion fn zugegriffen werden. Sie werden privat und können nur innerhalb von Obj verwendet werden. Auch Instanzen der Funktion Obj können weiterhin nicht auf diese Variablen und Funktionen zugreifen
var o=new Obj(); console.log(o.a); //undefined console.log(o.fn); //undefined
2. Statische Variablen und Funktionen
Wenn eine Funktion definiert ist und die durch „.“ hinzugefügten Attribute und Funktionen weiterhin über das Objekt selbst zugänglich sind, auf ihre Instanzen jedoch nicht zugegriffen werden kann, werden solche Variablen und Funktionen als statische Variablen bzw. statische Funktionen bezeichnet Wenn Sie Java und C# verwenden, können Sie die Bedeutung von Statik leicht verstehen.
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. Instanzvariablen, Funktionen
Bei der objektorientierten Programmierung hoffen wir, bei der Objektdefinition noch einige Eigenschaften und Methoden definieren zu können, auf die nach der Instanziierung zugegriffen werden kann
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
Damit kann der oben genannte Zweck jedoch erreicht werden
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
Das laufende Ergebnis des obigen Codes entspricht vollständig den Erwartungen, zeigt aber auch, dass a und fn in o1 geändert wurden, in o2 gibt es jedoch keine Änderung, da Arrays und Funktionen sowohl Objekte als auch Referenztypen sind Dies bedeutet, dass die Eigenschaften und Methoden in o1 zwar denselben Namen haben wie die in o2, jedoch keine Referenz, sondern eine Kopie der vom Obj-Objekt definierten Eigenschaften und Methoden sind.
Dies ist kein Problem für Attribute, aber es ist ein großes Problem für Methoden, da die Methoden genau die gleiche Funktion ausführen, aber zweimal kopiert werden. Wenn ein Funktionsobjekt Tausende und Instanzen hat, dann jede Instanz von Es muss eine Kopie von Tausenden von Methoden beibehalten werden, was offensichtlich unwissenschaftlich ist. Was können wir tun? Schauen wir uns zunächst die Bedeutung des Objekts an:
4. Gewöhnliche Objekte und Funktionsobjekte
In JavaScript ist alles ein Objekt! Aber auch die Objekte sind unterschiedlich. Objekt und Funktion sind in gewöhnliche Objekte und Funktionsobjekte unterteilt und die Funktionsobjekte, die mit JS geliefert werden. Hier sind einige Beispiele
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
Im obigen Beispiel sind o1 o2 o3 gewöhnliche Objekte und f1 f2 f3 Funktionsobjekte. Die Unterscheidung ist eigentlich sehr einfach. Alle durch new Function() erstellten Objekte sind Funktionsobjekte, andere sind gewöhnliche Objekte. f1, f2 werden letztendlich durch new Function() erstellt. Funktionsobjekte werden auch durch New Function() erstellt.
5. Prototyp Prototyp
Wenn in JavaScript ein Objekt (eine Funktion) definiert wird, enthält das Objekt einige vordefinierte Eigenschaften. Eine der Eigenschaften des Funktionsobjekts ist das Prototypobjekt. Hinweis: Gewöhnliche Objekte haben keinen Prototyp, aber das Attribut _proto_.
Prototypobjekte sind eigentlich gewöhnliche Objekte (außer Function.prototype, das ein Funktionsobjekt ist, aber etwas ganz Besonderes. Es hat kein Prototypattribut (wie bereits erwähnt, haben Funktionsobjekte alle Prototypattribute)). Schauen Sie sich das Beispiel unten an:
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
Aus der Ausgabe dieses console.log(f1.prototype) //f1 {} können wir ersehen, dass f1.prototype ein Instanzobjekt von f1 ist (hier ist das Prototypobjekt von f1). Beim Erstellen von f1 wird ein Instanzobjekt erstellt und seinem Prototyp zugewiesen. Der grundlegende Prozess ist wie folgt:
var temp = new f1(); f1. prototype = temp;
Es ist also leicht zu verstehen, warum Function.prototype ein Funktionsobjekt ist. Wie oben erwähnt, sind alle von new Function () generierten Objekte Funktionsobjekte, also ist temp1 ein Funktionsobjekt.
var temp1 = new Function (); Function.prototype = temp1;
Wofür wird das Prototypobjekt verwendet? Wird hauptsächlich zur Vererbung verwendet. Geben Sie ein Beispiel:
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(实例对象) 判断实例对象的原型是不是当前对象。
以上就是本文的全部内容,希望对大家的学习有所帮助。