javaScript中的继承
函数预编译的过程
函数预编译发生在函数执行之前。
只有函数表达式才可以执行,函数声明并不可以执行。
预编译时函数会生成自己的执行上下文。每个执行上下文都是独一无二的,在函数执行完成时,销毁执行上下文。
预编译的过程可以分为以下4个步骤:
- 形成了自己的执行上下文,变量提升,这时执行上下文里存储所有作用域中的变量。
- 若有形参,找出所有形式参数,存到形成的执行上下文里中。若形参与变量名重复,则形参覆盖变量。
- 给形参赋值;
- 函数提升,若函数内部还声明了其它函数,则将函数存到执行上下文中;
Star.prototype = {};
var Star = function() {};
var star1 = new Star();
在javaScript 中我们通常并不会关心构造函数(Star)的原型指向谁(这里Star的原型应指向Function.prototype)。
javascript 中并没有真正的类,而我们所说的继承可以理解成委托。
当new的时候发生了什么
var Star = function() {};
var star1 = new Star();
new Star()时也会执行Star函数,所以会触发预编译过程。
Star函数在预编译时,在自己的函数作用域里声明了一个变量this
,此时this的值为{}。
函数都具有返回值所以在构造函数内部会 return this
。
当在构造函数中访问原型上的属性a时,实际上是将a属性复制了一份,当你在构造函数中改变a属性的值时,原型上a属性的值并不会改变。
__proto__和constructor
所有的对象上都有_prop_属性。
- 一般通过
Object.getPrototypeOf(对象)
来获取一个对象的_prop_. - 一般通过
Object.prototype.isPrototypeOf(对象)
判断当前对象是否在指定对象的原型链上。 - 一般通过
对象 instanof 函数
判断函数的原型是否在对象的原型链上。在有iframe时不建议使用这种方法,因为数组的指向会发生改变。
举例:
//判断一个值是否为数组 现在可以用Array.isArray()判断。
Array.prototype.isPrototypeOf(值);
//第二种法
[] instanceof Array
- 一般通过
Object.prototype.hasOwnProperty(属性名)
判断一个对象自身是否拥有某个属性。
Object.create() 创建一个对象,该对象的_prop_属性指向传入该函数的参数。
所有的函数都有prototype属性,值为对象。
在每个对象的原型上都有两个私有属性_proto_
和constructor
,这两个属性会随着原型链一直继承。
_proto_
:会沿着原型链向上查找,一直找到原型链的最顶端。
constructor
:找到原型对应的构造函数。
继承的几种方式
function Origin () {
};
function Target () {
}
下文我们会用Origin
和Target
两个函数做示例,默认 是Target
继承Origin
。
1 传统模式- 原型链继承
function Father (name,age) {
this.name = name;
this.age = age;
}
Origin.propotype = new Father();
Target.propotype = new Origin();
var target = new target();
弊端:会继承原型链上许多多余的东西,比如我们只需要Target函数
2 通过call和apply的方式
function Target (){
Origin.call(this);//只在了new的时候才有效
}
弊端:每 new
一次都会call
一次,都会多执行一次Origin
函数,造成性能与效率的浪费。并且不能继承原型,继承的是构造函数。
3 共享模式
Target.propotype = Origin.propotype;
弊端:修改Target.propotype
的同时也会修改Origin.propotype
。
所有的_prop_
最终都会指向Object.propotype
,因此Target.propotype
和Origin.propotype
都是引用值。
比如:
Target.propotype.lastName = 'li';
此时打印Origin.propotype.lastName的值:
console.log(Origin.propotype.lastName);
返回值为:
"li"
通常在我们开发过程中较为理想的状态是:Origin的原型 可以继承 Target原型 的方法和属性,并且修改其中任何一个原型都不会互相影响,即上述操作的理想状态为: console.log(Origin.propotype.lastName)
,返回值为‘undefined’
。
因为,我们构建了新的模式 圣杯模式 。
圣杯模式
var Star = function (){
}
var inhreit = (function (Target,Origin){
var F = function(){} ;
retutn function () {
F.prototype = Origin.propotype;
Target.prototype = new F();
Target.prototype.constructor = Target;
}
})
var obj = {};
Object.creat(obj);
Object.creat()方法将会创建一个对象,这个对象的prototype指向传入的参数(这里是
obj);
工厂模式
每个构造函数都相当于一个工厂,所有通过 new
被创造的对象都拥有其构造函数的方法和属性,我们又可以对每个对象扩展新的属性,并且这些对象之间并不会互相影响,这样就加工出许多相同的部件。当需要创造多个相同的实例时,可以利用构造函数的特性来开发。这种开发模式被称为“工厂模式”。