Les étudiants qui ont utilisé JavaScript doivent être familiers avec le prototype, mais les débutants ne savent pas de quoi il s'agit. Ils savent seulement que les fonctions ont un attribut de prototype, et les fonctions peuvent être ajoutées par exemple, d'autres ne sont pas claires. a récemment examiné une programmation JavaScript avancée et a finalement dévoilé son mystère.
Chaque fonction a un attribut prototype, qui est une référence à un objet. Cet objet est appelé objet prototype. L'objet prototype contient des méthodes et des propriétés partagées par les instances de fonction, ce qui signifie que la fonction est utilisée comme appel de constructeur ( Lorsqu'il est appelé à l'aide de l'opérateur new), l'objet nouvellement créé héritera des propriétés et des méthodes de l'objet prototype. Contrairement aux langages traditionnels orientés objet, le mécanisme d'héritage de Javascript est basé sur des prototypes plutôt que sur des classes de classe.
1. Variables et fonctions privées
Avant de parler du prototype en détail, parlons de quelques éléments connexes afin que nous puissions mieux comprendre l'intention de conception du prototype. Avant de comprendre la chaîne de prototypes JavaScript, il est nécessaire de comprendre d'abord la chaîne de portée JavaScript. Dans le cadre des fonctions de JavaScript, si les variables et fonctions définies dans la fonction ne fournissent pas d'interface avec le monde extérieur, elles ne seront pas accessibles de l'extérieur, c'est-à-dire qu'elles deviendront des variables privées et des fonctions privées.
function Obj(){ var a=0; //私有变量 var fn=function(){ //私有函数 } }
De cette façon, la variable a et la fonction fn ne sont pas accessibles en dehors de l'objet fonction Obj. Elles deviennent privées et ne peuvent être utilisées qu'à l'intérieur d'Obj. Même les instances de la fonction Obj ne peuvent toujours pas accéder à ces variables et fonctions
.var o=new Obj(); console.log(o.a); //undefined console.log(o.fn); //undefined
2. Variables et fonctions statiques
Lorsqu'une fonction est définie et que les attributs et fonctions ajoutés par "." sont toujours accessibles via l'objet lui-même, mais que ses instances ne sont pas accessibles, ces variables et fonctions sont appelées respectivement variables statiques et fonctions statiques, les étudiants qui l'ont. Java et C# utilisés peuvent facilement comprendre la signification de statique.
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. Variables d'instance, fonctions
En programmation orientée objet, en plus de certaines fonctions de la bibliothèque, nous espérons toujours définir certaines propriétés et méthodes lorsque l'objet est défini, accessibles après instanciation. JavaScript peut également le faire
.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
Cependant, cela peut atteindre l'objectif ci-dessus
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
Le résultat de l'exécution du code ci-dessus est tout à fait conforme aux attentes, mais il illustre également un problème. a et fn sont modifiés dans o1, mais il n'y a aucun changement dans o2 puisque les tableaux et les fonctions sont à la fois des objets et des types référence. , cela signifie que bien que les propriétés et méthodes de o1 aient le même nom que celles de o2, elles ne sont pas une référence, mais une copie des propriétés et méthodes définies par l'objet Obj.
Ce n'est pas un problème pour les attributs, mais c'est un gros problème pour les méthodes, car les méthodes font exactement la même fonction, mais elles sont copiées deux fois. Si un objet fonction a des milliers et des instances de méthode, alors chaque instance de. il doit conserver une copie de milliers de méthodes, ce qui n'est évidemment pas scientifique. Que pouvons-nous faire ? Voyons d'abord la signification de l'objet :
4. Objets ordinaires et objets fonctionnels
En JavaScript, tout est objet ! Mais les objets sont également différents. Divisés en objets ordinaires et objets fonction, Object et Function sont les objets fonction fournis avec JS. Voici quelques exemples
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
Dans l'exemple ci-dessus, o1 o2 o3 sont des objets ordinaires et f1 f2 f3 sont des objets fonction. Comment distinguer est en fait très simple. Tous les objets créés via new Function() sont des objets fonction, et les autres sont des objets ordinaires. f1, f2 sont finalement créés via new Function(). Les objets de fonction sont également créés via New Function().
5. prototype prototype
En JavaScript, chaque fois qu'un objet (fonction) est défini, l'objet contiendra des propriétés prédéfinies. L'une des propriétés de l'objet fonction est l'objet prototype. Remarque : les objets ordinaires n'ont pas de prototype, mais ont l'attribut _ proto _.
Les objets prototypes sont en fait des objets ordinaires (sauf Function.prototype, qui est un objet fonction, mais il est très spécial. Il n'a pas d'attribut prototype (comme mentionné précédemment, les objets fonction ont tous des attributs prototype)). Regardez l'exemple ci-dessous :
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
D'après la sortie de ce console.log(f1.prototype) //f1 {}, nous pouvons voir que f1.prototype est un objet instance de f1 (voici l'objet prototype de f1). Lorsque f1 est créé, un objet instance est créé et attribué à son prototype. Le processus de base est le suivant :
var temp = new f1(); f1. prototype = temp;
Il est donc facile de comprendre pourquoi Function.prototype est un objet fonction. Comme mentionné ci-dessus, tous les objets générés par new Function () sont des objets fonction, donc temp1 est un objet fonction.
var temp1 = new Function (); Function.prototype = temp1;
A quoi sert l'objet prototype ? Principalement utilisé pour l'héritage. Donnez un exemple :
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(实例对象) 判断实例对象的原型是不是当前对象。
以上就是本文的全部内容,希望对大家的学习有所帮助。