JavaScript在ES6之前没有类似class,extend的继承机制,JavaScript的继承主要是通过原型和原型链实现的。
在说原型和原型链之前 ,我们首先要了解以下几个概念
一.私有变量和私有函数
在函数内部定义的变量和函数,如果不对外提供接口,那么外部是无法访问到的。这个被定义的变量和函数就叫做该函数的私有变量和私有函数。
function Foo() {
var name = "yiMu"; //私有变量
var fn = function() { //私有函数
console.log("hello word");
};
}
var bar = new Foo();
console.log(bar.name); //undefined
console.log(bar.fn); //undefined
因为变量name和函数fn都是在Foo函数内部定义局部变量,所以在函数外部新创建的实例无法访问;
二、静态变量和静态函数
当定义一个函数后通过"."的方式为其添加属性和函数,通过对象本身可以访问到,但是其实例却无法访问到,这样的变量和函数叫做静态变量和静态函数。
function Foo(){}
Foo.num = 10; //静态变量
Foo.fn = function() { //静态函数
console.log("hello word");
};
console.log(Foo.num); //10
console.log(typeof Foo.fn); //function
var bar = new Foo();
console.log(bar.num); //undefined
console.log(typeof bar.fn); //undefined
三、实例属性和实例方法
在面向对象编程中除了一些库函数,我们还是希望在定义一个对象的时候同时定义一些属性和方法并在实例化后能够访问,这些添加的属性和方法就叫做实例属性和实例方法。
function Foo() {
this.num = []; //实例属性
this.fn = function() { //实例方法
console.log("hello word");
};
}
console.log(Foo.num); //undefined
console.log(typeof Foo.fn); //undefined
var bar = new Foo();
console.log(bar.num); //[]
console.log(typeof bar.fn); //function
我们也可以为实例属性和实例方法添加属性和方法:
function Foo() {
this.num = []; //实例属性
this.fn = function() { //实例方法
console.log("hello word");
}
}
var oneBar = new Foo();
oneBar.num.push(1);
oneBar.fn = {};
console.log(oneBar.num); //[1]
console.log(typeof oneBar.fn); //Object
var twoBar = new Foo();
console.log(twoBar.num); //[]
console.log(typeof twoBar.fn); //function
从上面的代码可以看到,当我们在oneBar中修改了num的值和fn的类型,但是在twoBar中却没有发生改变,这是由于数组和函数都是对象,属于引用类型。oneBar和twoBar中的属性和方法名称虽然相同但是却不是同一个引用,它们只是对Foo对象定义的属性和方法的一个复制。
如果一个构造函数对象有上千的实例方法,那么它的每个实例都要对这个构造函数的上千个实例方法进行复制,这显然是不科学的,那么这种情况下我们就必须使用prototype了。好,接下来我们正式说一下什么是原型,什么是原型链?
首先,什么是原型?
JavaScript 中,万物皆对象!但对象也是有区别的。分为普通对象和函数对象,Object ,Function 是JS自带的函数对象。每个对象都有原型(null和undefined除外),你可以把它理解为对象的默认属性和方法。
普通对象和函数对象
var o1 = {};
var o2 =new Object();
var o3 = new f1();
function f1(){};
var f2 = function(){};
var f3 = new Function('str','console.log(str)');
console.log(typeof Object); //function
console.log(typeof Function); //function
console.log(typeof f1); //function
console.log(typeof f2); //function
console.log(typeof f3); //function
console.log(typeof o1); //object
console.log(typeof o2); //object
console.log(typeof o3); //object
在上面的例子中 o1 o2 o3 为普通对象,f1 f2 f3 为函数对象。怎么区分,其实很简单,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。f1,f2,归根结底都是通过 new Function()的方式进行创建的。Function Object 也都是通过 New Function()创建的。
- Object:Object是一个函数对象,Object的原型就是一个Object对象,它里面存在着一些对象的方法和属性,例如最常见的toString方法。
- 新建对象:用new Object或者{}建的对象是普通对象,它没有prototype属性,只有proto属性,它指向Object.prototype。
- Array: Array也是一个函数对象,它的原型就是Array.prototype,它里面存在着一些数组的方法和属性,例如常见的push,pop等方法。
- Function:Function也是一个函数对象,但它有点特殊,它的原型就是一个function空函数。
- 自定义函数:它的原型就是你给它指定的那个东西。如果你不指定,那它的原型就是一个Object.prototype。
实例就是通过构造函数创建的。实例一创建出来就具有constructor属性(指向构造函数)和proto属性(指向原型对象)。构造函数中有一个prototype属性,这个属性是一个指针,指向它的原型对象。原型对象内部也有一个指针(constructor属性),指向构造函数Person.prototype.constructor = Person。实例可以访问原型对象上定义的属性和方法。
普通对象的proto指向这个对象(this)的构造函数的prototype;
函数对象的proto全部都是指向Function的prototype。这是一个空函数
然后无论是Function的prototype还是构造器的prototype的proto都指向object.prototype,然后最终object.prototype指向null,原型链结束;
总的来说:
在Javascript中,万物皆对象,然而对象又分为普通对象和函数对象,至于怎么区分普通对象和函数对象,很简单,没有通过new Function()创建的都为普通对象,Object ,Function 是JS自带的函数对象,每个对象都具有原型属性prototype指向它的原型对象,每个实例都是通过构造函数创建的,一旦创建就拥有constructor属性,指向它的构造函数,和_ proto _属性,指向原型对象,也就是说
实例对象._ proto _属性===构造函数.prototype
所有普通对象的_ proto 属性等于它构造器的prototype;
所有函数对象的 proto 属性等于Function的prototype;
无论是FUnction还是构造器,构造函数的prototype的 proto _都等于object.prototype;
Function.prototype._ proto _ === Object.prototype
Object.prototype._ proto _===null
则原型链完成
function Person(name) {
this.name = name
}
// 重写原型
Person.prototype = {
getName: function() {}
}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false
这里直接重写了 Person.prototype。输出结果可以看出p._ proto 仍然指向的是Person.prototype,而不是p.constructor.prototype。这也很好理解,给Person.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器Object_,Object.prototype是一个空对象{},{}自然与{getName: function(){}}不等。
什么是原型链
在JavaScript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接。这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链(prototype chain)。
JavaScript 对象是动态的属性“包”(指其自己的属性)。JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依此层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
var A = function(){};
var a = new A();
console.log(a.__proto__); //A{}(即构造器function A 的原型对象)
console.log(a.__proto__.__proto__); //Object{}(即构造器function Object 的原型对象)
console.log(a.__proto__.__proto__.__proto__); //null
参考:
作者:Yi罐可乐
最详尽的 JS 原型与原型链终极详解,没有「可能是」。(一)
最详尽的 JS 原型与原型链终极详解,没有「可能是」。(二)
最详尽的 JS 原型与原型链终极详解,没有「可能是」。(三)