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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值