JavaScript学习之路 — 函数、闭包与原型链

今天这个话题是由于这几天看了《JavaScript忍者秘籍》,感受这本书把这几个内容讲的蛮透彻了,特撰本文,以便往后翻阅。(应该都会以知识点的形式给出吧。)javascript

函数

1.【基本类型】

JavaScript中函数为first-class object,typeof的结果是object,没有function这个基本类型,但有能够调用的Function构造器。说到这个就列举一下JavaScript中的几个基本类型:java

* null                       (空值)
            * undefined           (未定义)
            * boolean              (布尔)
            * number              (数字)
            * string                  (字符串)
            * object                 (对象)
            * symbol               (符号)

除了null的typeof结果是object之外,其余类型的typeof都是本身的基本类型名称。这几个类型还能有一堆能够讲的地方、以后再写~正则表达式

2.【函数声明】

函数的字面量声明有四个部分组成,但有些能够省略,列举以下:数组

* function 关键字
    * 可选名称 ,能够匿名,但若是有必须为有效的JavaScript 标识符
    * 括号内部一个以逗号分隔的参数列表
    * 大括号括起来的函数体

3.【name属性】

有名称的函数name属性永远为本身的名称。关于匿名函数赋予变量后的name属性是什么。。。我这里试出来ES5和ES6都是赋予后的变量名。浏览器

4.【函数调用与this的绑定】

在这本《忍者秘籍》里给出了一个理解this绑定的方法:将this理解为运行上下文,this指的就是调用函数时的运行上下文。this的绑定实在运行时候肯定的,而不是编译时。(在《你不知道的JavaScript》中详细讲了四种this绑定,之后补~)缓存

这里讲了四种函数调用的方式:闭包

  • 做为一个函数进行调用app

    就是做为通常的函数,直接在全局上下文中调用。
  • 做为一个方法进行调用
    将函数做为一个对象中的方法进行调用,那么这时候this就会绑定在这个对象的上下文中。同时若是在全局中定义一个函数,赋值到对象的属性中,其能够对不一样对象进行操做,互不影响。函数

//全局的函数
function all () {
    console.log(this.a);
}
//第1个对象
let o1 = {
    a: "In1",
    func1: function () {
        return this.a
    },
    func2: all
 }
//第2个对象
let o2 = {
    a: "In2",
    func: all
}

let a = "Out" 

console.log(o1.func1())   //method in object    In1
o1.func2()      //this is o1      In1
o2.func()       //this is o2      In2
  • 做为构造器进行调用测试

    也就是利用new运算符进行调用。构造器调用的时候会进行如下步骤:

1.建立一个新的对象
      2.传递给构造器的参数的对象是this参数,从而成为构造器的函数上下文
      3.若是没有显示的返回值,建立的对象则做为构造器的返回值返回
      (即便有return值,做为3构造器调用的时候,
      也返回新建立的对象,若是return的object,那就返回这个object)
    
    Ex:
function Ninja () {
    this.ninjaName = function () {}
    return 3
}

let ninja1 = new Ninja()
let ninja2 = new Ninja()

 //利用构造器获得两个不一样的对象
console.log(ninja1 === ninja2)    //false
//做为构造器调用时,返回的是对象而不是return值
console.log(ninja1)        // Ninja { ninjaName: [Function] }
//做为通常函数调用时,返回的是返回值
console.log(Ninja())      //3
  • 经过apply()或call()方法进行调用

    能够随意改变函数的调用上下文,apply与call的区别是除第一个指定函数执行时
    this绑定参数以外的参数。apply传入参数数组,而call传入所有参数。

5.【函数参数】

函数实际传入的参数和声明时候的参数列表能够不一样。
若是传入的比声明的少,那么没有传入数据的声明将会是undefined,若多,则多出来的的传入数据将没法经过变量名的方式访问到。
参数能够经过函数的argument属性访问到,这是一个类数组,没法使用大多的数组的自带方法。

6.【匿名函数】

对象内的匿名函数若是被赋值给了另外一个对象,会产生引用丢失的问题。

7.【函数的属性】

函数做为对象,能够存储一些参数值,以方便作一些特殊的处理。主要用途有函数存储和自记忆函数。
函数存储能够用来存储要调用的函数,自记忆函数能够用来缓存函数已经运行过的结果,减小重复计算(自记忆函数能够经过闭包的方式进行再优化)

8.【函数的变长参数】

能够经过apply方法给出变长参数的数组。(ES6中可使用...解包)
如:

let list = [7,9,1,2,0,10]
Math.max(list[0],list[1]....) //此处省略,过于麻烦,要列出全部位置
Math.max.apply(Math, list)    //利用apply能够直接传入数组
Math.max(...list)            //ES6中能够经过解包符直接传入

经过闭包的特性,一样能够实现经过参数个数不一样的判断,进行函数的重载。

闭包

1.【闭包的定义】

闭包是一个函数在建立时容许自身访问并操做该函数以外的变量时所建立的做用域。->声明的函数何时均可以调用,即便是在做用域消失以后。
典型的闭包:

let outer = 'ninja'
let later

function outerFunction () {
    let inner = 'samurai'
    
    function innerFunction (paramValue) {
        console.log(inner)
        console.log(paramValue)
        console.log(toolate)
    }

    later = innerFunction
}
    console.log(toolate)    //undefined (用let会报错)
    var toolate = 'ronin'
    outerFunction()   //建立闭包
    later('wakizashi')    //ninja wakizashi ronin 都输出了

闭包建立了一个气泡,保护了函数声明那一时间点的做用域里的全部函数和变量,得到了执行操做所须要的全部东西。
有三个有趣的结论:

  • 内部函数的参数是包含在闭包中的

  • 做用域以外的全部变量,即便是函数声明以后,可是在函数被调用以前的那些声明也都包含在闭包中

  • 相同的做用域内,还没有声明的变量不能使用(let声明)、值为undefined(var声明)

2.【闭包的用处】

  • 建立私有变量:利用function的特性,能够建立一个变量没法在外部直接访问,须要用getter和setter,这两个函数就是闭包的做用

  • 回调和计时器:回调函数中能够经过闭包来访问外部的变量

3.【绑定函数上下文(bind)】bind函数的用法与apply和call不一样,

简化版的bind:

function bind (context, name) {
    return function () {
        return context[name].apply(context, arguments)
    }
}
//经过闭包的特性,来获得要绑定的函数
//系统bind的使用:
functionName.bind(newThis)()

4.【使用闭包实现的函数缓存记忆】

使用到的技巧:每一个函数都有本身的上下文,因此函数历来都不是闭包的一部分。可是能够经过建立一个变量引用到这个上下文中(let fn = this,后面的function中能够利用fn调用到以前的this指向的上下文),从而将上下文也经过闭包保存起来。

具体的实现能够参见《忍者秘籍》书P103

5.【函数的即时调用】

一般用于匿名函数,主要做用是能够保护做用域和变量名不污染全局。
而且能够解决迭代问题(一样能够用来解决变量名称过长的问题,将长名称传入即时调用的函数中,在函数中利用参数名进行操做):

for(let i = 0;i < div.length; i++) {
    (function(n) {
        div[n].addEventListener("click", function(){
            alert("div #" + n + "was clicked")
        }, false)
    })(i)
}
//经过当即执行函数,直接把i和对应的div绑定,若是不这样作,最后按全部的按钮都会是最后一个i值(由于没有实时绑定)

闭包确定不止这么多内容,之后还会补充《你不知道的JavaScript》的内容

原型链

JavaScript经过原型链实现继承。

1.【prototype与new操做的共同使用】

只有经过new操做产生的对象,可使用构造器函数原型链上的内容,不然对象只能使用本身原型链上的内容。利用这个能够得出,利用构造器函数能够将JavaScript产生相似于类的概念。

2.【prototype的使用】

prototype在对象建立以后若是有改动,所作的改动一样会影响到已经建立的对象上去。
对于对象中的引用,先检查是否在本身的声明中存在,若是存在则用本身声明的,若是不存在则循着原型链向上找,找到object根元素,若是仍然不存在,则返回undefined。
prototype实际上是object隐藏属性constructor的一个属性,因此能够利用这个进行原型链,原型是实时附加在对象上的。

3.【保持原型链】

用一个对象的实例做为另外一个对象的原型,调用方式以下

SubClass.prototype = new SuperClass()

这样SubClass的实例不只拥有原型,更有SuperClass中的全部属性。而且instanceOf SuperClass也会判断正确。

注意: 永远不要使用SubClass.prototype = SuperClass.prototype,若是这样作的话全部SubClass上的prototype修改都会影响到SuperClass上,会产生反作用。

4.【hasOwnProperty()方法】

利用这个方法能够检测属性是不是原生就有的,而不是经过检测原型链获得的。

5.【一些须要避免的场景】

  • 扩展Object的prototype,这样会影响到全部的对象

  • 扩展Number的prototype

  • 产生原生对象的子类(尽可能采用另外写类,但同名方法经过prototype来调用原生对象方法的方法)

  • 经过构造器建立对象不加new操做符(这样作会有可能产生错误得不到对象而且污染全局变量)

这些就是读完《忍者秘籍》的一部分感想和知识点,这本书最精华的部分应该就是这三块的精炼描述了,这本书还讲到了JavaScript的测试方法、正则表达式、定时器、运行时求值、事件与DOM操做以及一些跨浏览器的实践方法。是一本好书(常常参加促销的好书~),能够借来或者买来一读~。

以上。

相关文章
相关标签/搜索