JS函数执行
一个JavaScript函数fn,被执行有三种途径:
- fn()
- new fn()
- fn.call()或fn.apply()
new机制以及继承
JavaScript中定义了一种对象,称之为ECMAScript对象,其内部实现包括以下:
- __call__: 说明该对象可以被执行,具有function属性
- __construct__: 说明该对象可以接受new操作,具有构造器属性
- __prototype__: 指向对象的原型链。对于定义的函数,会指向Function.prototype
注意:__prototype__是原型链,对所有对象都有的。prototype是原型,是函数才有的,就是一个普通的对象而已,目的是为了接受new后给生成的对象提供原型链的。
执行fn就是调用__call__
执行new fn()会进行以下简化过程:
- 新建一个对象,记作o
- 把o.__prototype__指向fn.prototype(如果fn.prototype不是一个Object,则指向Object.prototype)
- 执行fn,并用o作为this(即内部实现的fn.call(this))。如果fn返回是一个object,则返回object, 否则把o返回
fn.call(obj)或者fn.apply(obj)就是将obj作为this,执行fn。本质是调用__call__,只是传入了obj作为this.
//定义一个函数,正常函数会具有__call__, __construct__
//同时Parent.__proto__指向Function.prototype
function Parent() {
this.sayAge = function() {
console.log("age is: " + this.age);
}
}
//原型上添加一个方法
Parent.prototype.sayParent = function(){
console.log("this is Parent Method");
}
//定义另一个函数
function Child(firstname){
//这里就是调用Parent的__call__, 并且传入this
//而这里的this,是Child接受new时候生成的对象
//因此,这一步会给生成的Child生成的实例添加一个sayAge属性
Parent.call(this);
this.fname = firstname;
this.age = 40;
this.saySomething = function() {
console.log(this.fname);
this.sayAge();
}
}
//这一步就是new的调用,按原理分步来看
//1. 新建了个对象,记作o
//2. o.__prototype__ = Parent.prototype, 因此o.sayParent会访问到o.__prototype__.sayParent(原型链查找机制)
//3. Parent.call(o), 因此o也会有个sayAge属性(o.sayAge)
//4. Child.prototype = o, 因此 Child.prototype 通过o.__prototype__ 这个原型链具有了o.sayParent属性,同时通过o.sayAge 具有了sayAge属性(也就是说Child.prototype上具有sayAge属性,但没有sayParent属性,但是通过原型链,也可以访问到sayParent属性)
Child.prototype = new Parent();
//这也是一步new调用
//1. 新建对象,记作s
//2. s.__prototype__ = Child.prototype, 此时s会具有sayAge属性以及sayParent这个原型链上的属性
//3. Child.call(s), 执行后, 增加了fname, age, saySomething属性, 同时由于跑了Parent.call(s), s还具有sayAge属性, 这个属性是s身上的, 上面那个sayAge是Child.prototype上的, 即s.__prototype__上的。
//4. child = s
var child = new Child("张")
//child本身属性就有,执行
child.saySomething();
//child本身属性没有, 去原型链上看, child.__prototype__ = s.__prototype__ = Child.prototype = o, 这里没找到sayParent, 继续往上找, o.__prototype__ = Parent.prototype, 这里找到了, 执行(第二层原型链找到)
child.sayParent();
原理来看写得有些繁琐,本身其实是比较简单的东西。
重点是new的过程,原型prototype和原型链__prototype__
也正是new的原理,导致了原型链的继承,本质是生成的对象的__prototype__指向了函数的原型prototype
更复杂的调用继承之类的,都可以通过这个原理来理解。说白了,原型链继承就是复用了prototype而已。
本例来看,Child中的Parent.call(this)看似没有必要,但本质上是有区别的。如果去掉这一句,则Child的实例本身将没有sayAge属性,而Child.prototype具有sayAge属性,因此实例的__prototype__具有sayAge属性,因此还可以执行。
但目的是为了继承,因此属性是需要对象上本身持有,而非是通过原型链上来访问,所以加上这一句是原理上的严谨要求。可以通过下面的例子来检验:
function Parent() {
this.sayAge = function() {
console.log("age is: " + this.age);
}
}
Parent.prototype.sayParent = function(){
console.log("this is Parent Method");
}
function Child(firstname){
Parent.call(this);
this.fname = firstname;
this.age = 40;
this.saySomething = function() {
console.log(this.fname);
this.sayAge();
}
}
Child.prototype = new Parent();
var child = new Child("张")
child.saySomething();
child.sayParent();
console.log(child.hasOwnProperty('sayAge')); // true
child.sayAge(); //能调用,此时调用的是自身的属性
delete child.sayAge; // delete只能删除自身的属性,不能删除原型链属性
console.log(child.hasOwnProperty('sayAge')); // false,自身没有这个属性了
child.sayAge(); //还能调用,此时调用的是原型链上的方法
如果删掉Parent.call(this), 上面两句child.hasOwnProperty('sayAge'), 都将返回false
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。