P2M4T3:继承和函数进阶

164 阅读5分钟

对象之间的继承#1

已有的属性保留,没有的属性继承。

封装一个对象之间拷贝继承的函数:

function extend(parent,child){
    for(var i in parent){
        if(child[i]){
        continue;
        }
        child[i] = parent[i];
    }
}

原型继承#2

完整的继承,应该是

原型链上层的类是更广泛的类,下层的类作为上层的分化,其中含有一部分公共属性是从上层继承而来。

方法:将上层构造函数创建的实例对象赋值给下层的原型对象
e.g. Lower.prototype = new Upper(a,b,c);
这样,Lower 生成的实例是可以调用到 Upper 的属性的。

此方法存在的问题:

  • 需要重定向 constructor
    Lower.prototype.constructor = Lower;
  • 继承的值会相同

函数的 call 方法#3

call 是一种执行函数的方法,它的作用有两个:

  • 更改函数内部的 this 指向
  • 调用函数执行内部代码 详见【#10】 e.g. afunc.call(toThis,a); call 方法第一个参数用来指定内部 this 指向,第二个及以后的参数为函数原本的参数。

借用构造函数继承属性#4

进一步:
将上层的构造函数写进下层的构造函数,同时使用 call 方法修改上层构造函数的 this 指向下层构造函数的 this(不修改的话,指向的是window)。

function Lower (a,b,c,s) {
    Upper.call(this,a,b,c);
    this.special = s;
}

构造函数方法的继承#5

前述的内容只完成了属性的继承,上层原型对象的方法也需要继承。
同样的,方法的继承也有两种做法。

  1. 对象拷贝继承
    for (var i in Upper.prototype) {
        if(i === constructor){
        continue;
        }
        Lower.prototype[i] = Upper.prototype[i];
    }
    
    除 constructor 以外的原型方法都复制过来。
  2. 原型继承(同【#2】)

组合继承#6

属性使用构造函数继承,方法使用原型继承

【新小结】函数定义方式#7

函数的定义方法:

  1. 函数声明
    function f(){};

  2. 函数赋值
    var f = function (){};

    二者区别在于当函数调用语句写在之前时,声明的会因函数声明提升而正常调用,而赋值的只有变量声明被提升未被赋值,调用 undefined 出错。
    因此,推荐使用函数声明。

    但是,if 语句中的函数声明存在兼容性问题

    if(true){
       function f(){ft()};
    }else{
      function f(){ff()};
    }
    

    现代浏览器会认为是函数赋值,低版本浏览器则是函数声明
    前调用,现代:undefined;低版本:ff()(后被提升者)
    后调用,现代:ft()(符合逻辑);低版本:ff()
    因此,此类情况使用函数赋值:

    var f;
    if(true){
       f = ft;
    }else{
       f = ff;
    }
    
  3. 【新】使用构造函数创建#8
    var f = new Function();
    新建函数的 形参内部代码 均以字符串形式传入构造函数
    e.g. var func = new Function('a','b','console.log(a+b)');
    得到的函数为:function (a,b) {console.log(a+b)}

    【注意】不推荐使用

【小结】函数的调用和 this 的指向#9

  1. () 运算符执行;this 默认指向 window
  2. new 关键字调用,并加 () 执行;this 指向将来创建的实例对象
  3. 对象中的方法,打点调用,同时也得加 ();this 指向对象
  4. 事件函数在被触发时自动执行;this 指向事件源
  5. 定时器、延时器中的函数在规定的时间自动执行;this 指向 window

this 的指向与函数的执行方法密切相关,具体情况具体分析。

指定函数内 this 的三个方法#10

  • call
    • 功能:
    • 参数:第一个指定 this,第二个及以后的是函数的实参
  • apply
    • 功能:指定函数的 this,并调用
    • 参数:第一个指定 this,第二个是函数实参组成的数组
  • bind
    • 功能:指定函数的 this,不执行函数
    • 参数:第一个指定 this,第二个及以后的是函数的实参
    • 返回值:返回了重新指定了 this 的新函数

func.call(newThis);
func.apply(newThis);
func.bind(newThis)(); 这三个方法的使用效果一样,各自有对应的应用。

call 的应用#11

常用于类数组的对象。

e.g.借用数组的方法来处理对象: Array.prototype.push.call(aObj,50);
其中,aObj 是一个对象

apply 的应用#12

多用于数组,省去拆分数组的麻烦。

e.g.将对象的 Math.max 方法应用到数组 Math.max.apply(null,arr);
Math.max 方法本是需要多个参数,现在利用apply 直接对数组 arr 求最大值。
第一个参数传 null,表示不修改 this。

e.g. console.log.apply(null,arr);
将数组 arr 以单个的形式在控制台输出。

bind 的应用#13

多用于不想执行函数的情况。

e.g.修改定时器内部的函数
setInterval(function(){}.bind(this),1000);

函数的其他成员#14

  • arguments【常用】
    由所有实参组成的一个类数组
    它是一个关键字,在函数内可以直接使用,省略function.
    arguments 下也有属性:
    • callee 指向函数自身
    • length 实参个数
  • caller
    函数的调用者
    函数在哪个作用域调用,caller 就指向谁
    全局作用域下调用,caller 为 null
  • length 形参的个数
  • name 函数名

高阶函数#15

能够作为其他函数的 参数返回值 的函数,便是高阶函数。

函数闭包(Closure)#16

函数定义时的作用域和函数自己所形成的一个密闭环境,称为闭包,包含了函数本省和它需要的变量。

不论函数以任何方式在任何地方进行调用,都会回到闭包内执行。

闭包内的变量能够因函数的调用而改变。

闭包的用途/好处:

  • 在函数外部读取函数内部成员
  • 让函数内成员因闭包的保留而始终活在内存中

闭包的问题:

  • 循环体中的函数,结束后调用,不能记住当时的中间值,只能是循环最终值

解决办法:
将循环体内部的函数改为自调用函数,并将参数传入,那么它的闭包为自调用内部,只跟传入的实参有关,而非循环的变量。