构造函数与原型
JavaScript为了看起来比较像java,再声明一个实例中也加入了new操作符
var obj = new f()
但是与java不同的是,JavaScript中的new操作符后面跟的并非类名而是函数名。
例如:
function dog() {
this.name = "wangcai"
this.bark = () => {
console.log('wangwang~')
}
}
var dog1 = new dog()
var dog2 = new dog()
上面就是JavaScript的一个构造函数,dog1,dog2分别是声明的dog实例。
但是上面有很大的问题每次创建一个实例,总是要重新创建bark函数,这无疑浪费了很多的空间,这个时候就引用了原型prototype.
先看一段原型的官方解释:
每一个构造函数都拥有一个prototype属性,这个属性指向一个对象,也就是原型对象。当使用这个构造函数创建实例的时候,prototype属性指向的原型对象就成为实例的原型对象。
原型对象默认拥有一个constructor属性,指向指向它的那个构造函数(也就是说构造函数和原型对象是互相指向的关系)。
每个对象都拥有一个隐藏的属性[[prototype]],指向它的原型对象,这个属性可以通过
Object.getPrototypeOf(obj) 或 obj.proto 来访问。
实际上,构造函数的prototype属性与它创建的实例对象的[[prototype]]属性指向的是同一个对象,即 对象.proto === 函数.prototype 。
如上文所述,原型对象就是用来存放实例中共有的那部分属性。
在JavaScript中,所有的对象都是由它的原型对象继承而来,反之,所有的对象都可以作为原型对象存在。
访问对象的属性时,JavaScript会首先在对象自身的属性内查找,若没有找到,则会跳转到该对象的原型对象中查找。
可以了解到原型是为了解决同一方法不同实例而出现的,把上面的代码进行改进一下。
function dog() {
this.name = "wangcai"
}
Dog.prototype.bark = () => {
console.log('wangwang~')
}
这样将实例中共有的属性放到原型对象中,让所有实例共享这部分属性。如果想要统一修改所有实例继承的属性,只需要直接修改原型对象中的属性即可。而且每个实例仍然可以重写原型中已经存在的属性来覆盖这个属性,并且不会影响到其他的实例。
原型链
JavaScript中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链(prototype chain)
所有原型链的终点都是Object函数的prototype属性,因为在JavaScript中的对象都默认由Object()构造。Objec.prototype指向的原型对象同样拥有原型,不过它的原型是null,而null则没有原型。
例如继承就是由原型链衍生出来的:
// call继承
// 特点:只能继承父的私有属性
// Parent.call(this);
function Parent() { // 父类
this.x = 100;
}
Parent.prototype.getX = function () { // 子类
return this.x;
}
function Child() {
// 1) 让this指向一个空对象
// 2)this.xx = xx;
// 3)返回这个空对象
Parent.call(this);
this.y = 200;
}
var c1 = new Child();
console.log(c1.x);
这里利用了call改变了Parent的this指针,并成功调用了其方法,即时原型链的一种使用方式。
当然还有组合式继承等:
// 组合式继承:
// 1)Parent.call(this); // 继承父的私有属性
// 2)Child.prototype = Object.create(Parent.prototype); 子类继承父类的公有属性
function Parent() { // 父类
this.x = 100;
}
Parent.prototype.getX = function () { // 子类
return this.x;
}
function Child() {
Parent.call(this); // 继承父的私有属性
this.y = 200;
}
// Child.prototype = Parent.prototype; 不好
// 为什么?如果再你写了Child.prototype.xx = function(){}
// 意味着父中也可以访问xx 说白了 子类可以影响父类
// 想法:把Parent.prototype对象copy一份 让它俩的原型是彼此独立
// Child.prototype = Parent.prototype;
// 为了不影响父类的原型对象 copy一份赋值给了Child.prototype
Child.prototype = Object.create(Parent.prototype);
// 最好手动的修改一个Child原型对象上constructor指向
Child.prototype.constructor = Child; // 手动修改constructor的指向
var c = new Child();
console.log(c.x);
console.log(c.getX())
结语
原型和原型链密不可分。只要理解prototype这一属性,并仔细掌握mdn上的其使用情形,相信不是太难。