最近在看一些博客的时候,内容经常是来自javascript忍者秘籍,最近虽然一直在面试实习,但是不能把注意力一直放在应付面试上,提升自己的能力也很重要,所以一直抽空看看忍者秘籍。总体来说,这本书,初学者就不用看了,有一定基础之后来看,感悟会比较多。我在这一遍阅读3-5章的过程中,其实并没有很难,有些概念,之前就懂了,这次的阅读,其实更多程度上是对js一个系统的梳理过程。

第三章 函数

函数为什么这么重要?

  1. 函数可以被调用,通常是异步的。
    传统的gui编程界面,通常要做以下几件事:
    1. 创建用户界面
    2. 进入轮询,等待事件触发
    3. 调用事件的处理程序(也成为监听器,listener)
      而浏览器不同,代码不需要负责事件的轮询和事件派发,而是浏览器帮忙处理,而我们的职责是为浏览器发生的各种事件建立处理的程序handler,这些事件的调用被放置在一个事件队列,然后浏览器调用这些handler
  2. 函数声明和函数表达式的异同之一:勘误,已经改变。函数表达式也是有name的,但是匿名函数的name是空
    function test(){};
    var test2 = function(){};
    console.log(test2.name);//test2
    console.log(test.name);//test
    (function(){console.log(arguments.callee.name)})();//""
    
  3. 函数作用域:
    1. 变量声明的作用域开始于声明的地方,结束于所在函数的结尾,与代码的嵌套无关
    2. 函数声明的作用域是指声明该函数的整个函数范围,与代码嵌套无关,
    3. 对于作用域声明,全局上下文就像一个包含页面所有代码的超大型函数
  4. this依赖于函数的调用方式
    1. 作为函数调用
    2. 作为方法调用
    3. 作为构造器调用
      构造器的特点:
      1. 创建一个空对象
      2. 构造函数的原型赋值给空对象的原型
      3. 如果没有显示返回值,新创建的对象将作为构造器的返回值进行返回
    4. call,apply,bind
      可以在回调函数中强制指定上下文,也就是this

第四章 挥舞函数

  1. 递归的时候,匿名函数起到很大的作用
    1. 做为对象的属性定义的函数,在递归的时候可以通过this.属性名来避免别的对象的引用递归失败的问题,但是又限制了新对象的属性名必须与之前相同的问题
    2. 通过内联函数可以很好的解决这个问题。
    3. 当然,arguments.callee也能很好的解决这个问题,但是callee在严格模式下不能使用,这一点不是很清楚为什么。
  2. 函数也是对象
    很好用的一个特点就是函数也可以像对象那样拥有属性和属性值
    1. 函数的存储
      将函数存储起来是一个常见的场景,通过数组来存储也行,但是并不优雅,而且相同函数的判断也是很尴尬,采取对象存储的方法更为直观高效。

      1. 新建一个对象store,中间的属性有id,cache以及add方法。
      2. add(someFunc)的时候通过检测someFunc.id是否存在,不存在就增加id属性,同时将其存入cache中。
      3. 通过对id的判断就可以知道函数有没有添加到cache中
    2. 自记忆函数,这种函数能够记住之前的结果,来避免已经执行过的不必要的计算,原理就是暗中为函数增加属性
      缓存之前的计算结果

      1. 计算之前检测是否之前计算过,计算过,直接输出结果。没有则进行计算,并将结果进行缓存
        优点:
      2. 性能好
      3. 发生在幕后,开发人员不需要进行相关的特殊操作。
        缺点:
      4. 必然牺牲掉内存
      5. 有些纯粹主义者可能认为一个函数做一件事就可以了,这样容易造成混乱
      6. 很难测试一个算法的性能
        缓存记忆dom元素
        通过元素的标签查询dom元素是很常见的操作,但是性能并不是特别好,可以利用缓存来保存已经匹配到的元素,性能提升很大
      function getElements(name){
        if(!getElements.cache) getElements.cache = {};
        return getElements.cache[name] =
           getElements.cache[name] ||
           document.getElementsByTagName(name);
      }
      

      这两个实例可以看出,作为函数的属性来对一些信息进行存储,可以提高性能,同时又不会对别的代码或者全局环境造成干扰,是一个很好的特性。

    3. 伪造数组方法,并没有看懂有什么好处。以后再读
      首先,数组也是对象,是可以添加属性的。

  3. 可变长度的参数列表
    js一个灵活的特性就是接收任意长度的参数
    1. Math.max,Math.min方法不支持数组形式的参数,怎么办?通过Math.max.apply(null,arr)就可以。apply的一个灵活运用。太简单。
    2. 函数重载
      js没有函数重载,但是通过参数的长度其实是可以实现一些类似重载的功能的。
      函数的功能,可以根据输入参数的数目以及类型来进行判断,进而实现意义上的函数重载
      1. 函数的length属性,表明了函数定义的时候的形参个数,而不是arguments.length
        通过length属性,可以知道函数定义的形参个数,arguments.length可以知道函数调用的时候的实际参数个数
      2. addmethod方法的写法,经典,牛逼,闭包的经典应用
  4. 判断一个对象是不是函数?
    正常情况下:typeof 可以解决问题。但是存在某些浏览器不兼容的问题
    可以采用 Object.prototype.toString.call(func) === “[object Function]”来判断,也可以用来验证别的如reg,math等

第五章 闭包

  1. 闭包其实不难,重点在于,当函数声明的时候,不仅声明了函数,还确定了它的作用域链,这个作用域链和函数在什么时候执行无关,只和函数定义或者声明的地方有关。这个是理解闭包的关键。当函数执行的时候,根据函数内部定义的变量确定了它内部最优先访问到的变量,如果不存在,会依次向上查找。同时,闭包保存的是变量的引用,而不是变量的值。

  2. 闭包的作用

    1. 私有变量:封装一些信息作为私有变量,只能通过实例的方法对其进行访问。
    2. 回调和计时器:回调函数通常希望访问一些外部变量,所以可以在回调的外部定义变量,回调的函数因为声明的时候确定外部作用域,自然就将那些变量包含进去,从而在回调中访问到这些变量,计时器的道理与之相同
    3. 绑定函数上下文:这本书写的时候应该是没有bind函数,所以作者直接写了一个bind,其实也并不难,返回值是一个函数,外部定义了私有变量保留了上下文而已。
    4. 偏函数应用:就是函数的柯里化,预先填充几个原函数的参数值,返回一个已经包含了这几个参数的新函数。
    5. 函数记忆:之前的文章其实提到了,就是函数本身也是一个对象,可以有属性,利用这个特性可以缓存之前计算的结果。这里给出了在函数的原型上定义一个memorize的方法,使用函数的时候,调用memorize就可以实现缓存。
      这里提出的改进是利用闭包来实现函数的记忆功能:采用立即执行函数将原函数进行打包,从而使用者不必每次调用memorize来进行缓存。
  3. 立即执行函数

    1. 创建临时作用域,将变量名做为参数传递,换成一个短的参数,可以让代码更加简洁
    2. 循环绑定:利用立即执行函数创建一个闭包从而达到循环绑定想要的效果
    3. 类库包装:
    (function(){
      var jQuery = window.jQuery = function(){
        //
      }
      //
    })
    
    //方式2
    var jQuery = function(){
      function jQuery(){
        //init
      }
      //yewu code
      return jQuery;
    }