Javascript函数执行、new机制以及继承

JS函数执行

一个JavaScript函数fn,被执行有三种途径:app

  • fn()
  • new fn()
  • fn.call()或fn.apply()

new机制以及继承

JavaScript中定义了一种对象,称之为ECMAScript对象,其内部实现包括如下:函数

  • __call__: 说明该对象能够被执行,具备function属性
  • __construct__: 说明该对象能够接受new操做,具备构造器属性
  • __prototype__: 指向对象的原型链。对于定义的函数,会指向Function.prototype

注意:__prototype__是原型链,对全部对象都有的。prototype是原型,是函数才有的,就是一个普通的对象而已,目的是为了接受new后给生成的对象提供原型链的。this

执行fn就是调用__call__prototype

执行new fn()会进行如下简化过程:code

  • 新建一个对象,记做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而已。ip

本例来看,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

相关文章
相关标签/搜索