很多新手包括我自己,在学习js的过程中总会被prototype和__proto__混淆,不知道具体的含义,更别说使用方法。因此我们有必要先仔细了解prototype和__proto__概念,这在学习js语法显得极为重要。
1.什么是prototype和__proto__
①“我们创建的每个函数都有一个prototype(原型)属性(除了Function.bind()返回的函数),这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。”
–摘自人民邮电出版社出版的Nicholas C.Zakas著的《JavaScript高级程序设计》(第2版)
上面这句话什么意思呢?比如我们创建一个函数叫做foo,在创建完成的时候系统默认会给这个函数增加一个prototype属性,这个属性是一个指针,指向一个浏览器默认开辟的内存空间,这个空间则叫做原型对象。原型对象的主要用途是可以包含公有的属性和方法,比如我们可以设置一个foo.prototype.name,这个name属性就可以被该函数所有实例调用。具体怎么完成这一步骤,我们会在第二节解释。
②那么__proto__又是什么呢?
其实,上述的prototype我们称为显式原型,__proto__则被称为隐式原型,javascript中任意的对象都有一个内置属性[[prototype]],也就是现在的__proto__,该属性指向的是创建这个对象的函数(constructor)的原型(prototype)。进一步解释,__proto__是对象具有的属性,它指向创建这个对象函数的原型。比如我们 var Foo = new foo(),那么Foo称为构造函数foo的一个对象实例(通过实例化,此时的构造函数就会变成一个类,以后函数中的this代表的就是Foo对象,而且默认会返回这个对象),这个对象具有一个隐式原型__proto__,它指向foo.prototype。上面我们说了函数foo的所有实例可以调用name属性,在这里就是Foo.name == foo.prototype.name,Foo首先会查找私有的属性和方法,发现没有私有的name属性,它就会通过__proto__找到foo.prototye,可以调用所有的公有属性和方法,这一过程则被称为原型链。
function foo (){ } foo.prototype.name = '唐吉sir';var Foo = new foo //在不需要传递参数,我们可以省略() console.log(Foo.name) //输出结果:唐吉sir
2.画图表述prototype和__proto__
简单来说,在js语法里,每个新创建的函数都会有一个prototype(原型)属性,这时浏览器会自动开辟一个内存空间,prototype指向这个内存空间,这个自动开辟的空间里还储存着一个constructor属性指向原函数本身,我们把这个prototype指向的内存空间叫做原型对象。为帮助理解,我们简单输出一下prototype和prototype.constructor。
function foo(){ console.log(foo.pototype); //输出结果:{constructor:f} console.log(foo.pototype.constructor) // 输出结果:f foo(){}
}
foo();
这个过程用图例来表示就是:
如上图所示,我们输出foo.prototype得到的是一个原型对象,这个对象包括了constructor属性和__proto__属性,其中constructor属性指向原函数,__proto__指向上一级的prototype。因为js中一切的对象都是Objec函数的一个实例,所以在这个例子中,__proto__指向的是Object.prototype,而这种通过__proto__原型上的指向关系,我们就称为原型链。这里要注意,因为Object.prototype已经是最上层的对象,因此Object.prototype.__proto__是不存在的,这也解释了③中没有__proto__。
3.常用__proto__指向
通过上面的例子和图画我们知道,__proto__指向的是上一级的prototype,一般情况下相当于 (__proto__ === construtor.prototype),下面我们介绍三种常用方式创建对象的__proto__指向。
①字面量方式
var foo = {}; console.log(foo.__proto__) //Object{} console.log(foo.__proto__ === foo.constructor.prototype) //true
②构造器方式
var foo = function(){}; var Foo = new foo; console.log(Foo._proto__) //foo{} console.log(Foo.proto__ === Foo.constructor.prototype) //true
③Object.create()
var foo = {a:1}; var faa = Object.create(foo); console.log(faa.__proto__); //Object {a:1} console.log(faa.__proto__ === faa.constructor.prototype) //false
上面两种方式很容易理解,我们主要介绍一下Object.create()这个方法
Object.create(O[,Properties])
The create function creates a new object with a specified prototype. When the create function is called, the following steps are taken:
1.If Type(O) is not Object or Null throw a TypeError exception.
2.Let obj be the result of creating a new object as if by the expression new Object() where Object is the standard built-in constructor with that name
3.Set the [[Prototype]] internal property of obj to O.
4.If the argument Properties is present and not undefined, add own properties to obj as if by calling the standard built-in function Object.defineProperties with arguments obj and Properties.
5.Return obj.
create函数创建了一个带有特殊原型的新对象,当create函数被调用时,按照下列步骤实行:
1.如果参数O的类型不是一个对象,或者不是null值就会报错
2.让obj成为创建新对象的结果,就像通过表达式new object()一样,其中object是具有该名称的标准内置构造函数
3.将obj的[[Prototype]]内部属性设置为O。
4.如果参数属性存在且未定义,则通过使用参数obj和属性调用标准内置函数Object.defineProperties,向obj添加自己的属性
5.返回obj
这是javascript中都支持的一种单继承,在我们的例子中,(faa.__proto__ === foo)为“ture”,faa.__proto指向的就是foo的内存空间,而faa.constructor指向的依然是Object函数。通过__proto__原型链可以调用foo的属性和方法,实现单继承。
在实际使用中,我们一般这样实现
function foo(){ this.age = 18; }; foo.prototype.name = '唐吉sir'; function faa(){ this.name = '老王' foo.call(this) }; faa.prototype = Object.create(foo.prototype); faa.prototype.constructor = faa; var A = new faa; console.log(A instanceof foo); //ture console.log(A instanceof faa); // ture console.log(A.name); //老王 console.log(A.age); //name console.log(A.__proto__.name); //唐吉sir
通过画图的方式可以很直观地把原型对象和原型链的指向关系表现出来,而且不容易混淆,在往后的学习过程中,应该学会将复杂的代码分成一个个小部分应用图形的方式加强理解,这样不仅自学的效率高,基础也可以更扎实。
ps:本人也是前端学习的新手,文章难免有出错的地方,望请各位读者批评指正...