《JavaScript语言精粹》笔记

据说《JavaScript语言精粹》是一本好书!学JS不得不看的一本书之一,然后买了一本花了一天时间一口气读完了,嗯…感觉还可以,虽然还不错但是没有感觉有什么特别的地方…还是JS高程不错,据说老尼最近出了一本ES6的书,有时间买来拜读拜读…

嗯…跑题了,看了一本书还是有点收获的,做个笔记记录一下。

复习一些知识点

  1. for in语句会枚举一个对象的所有属性名,可以通过object.hasOwnProperty()来检测该属性是该对象自身的还是来自原型链的。注意for in枚举属性名出现的顺序是不确定的,如果需要确保顺序出现,就不应该使用for in语句,可以创建一个数组,以特定的顺序包含属性名,再用for循环来顺序遍历。
  2. 对象通过引用传递,不会被复制。
  3. 委托:检索对象的属性值时,若不存在会向原型对象中检索,直至Object。
  4. 函数的arguments不是真正的数组,只是一个类数组(array-like),他有一个length属性,但是没有任何数组的方法。
  5. 检测数组和对象的方法:

    // 1 (识别不同的窗口window和frame里的数组会失败)
    var is_array = function(value) {
        return value && typeof value === 'object' && value.constructor === Array;
    }
    // 2 (更好的方法)
    var is_array = function(value) {
        return Object.prototype.toString.apply(value) === '[object Array]';
    }  
    
  6. array.slice(start, end)中如果任何一个参数为负数,array.length会与负数相加让他们变成非负数。
    array.sort(fn)如果第一个参数应该排在第二个参数前面则返回负数,否则返回正数,相等返回0;

一些感觉挺重要的知识点

  1. 函数的四种调用模式:方法调用、函数调用、构造器调用、apply调用

    • 方法调用模式:函数被保存为对象的属性时称为方法,方法调用时,this绑定到该对象。
    • 函数调用模式:当函数不是一个对象的属性时,就是作为函数调用的,以这种方式调用时,this被绑定到全局对象上。这是存在问题的,当内部函数被调用时,正常的this应该被绑定到外部函数的this变量上,实际上绑定到了全局。

      //函数调用模式 示例
      global.a = 4;
      var obj = {a: 5};
      obj.action = function() {
          var that = this; // 解决方法
          function f2() { // this绑定了全局对象
              console.log(this.a);// 4
              console.log(that.a);// 5
          }
          f2();
      }
      obj.action();
      
    • 构造器调用模式:函数前带上new来调用,则会背地里创建一个连接到该函数prototype的新对象,this指向这个新对象。(书上说的比较绕…我理解的就是this指向该函数的prototype对象)

      // 构造器调用模式 示例
      function A() {
          var value = 4;
          console.log(this.value);// 5
      }
      A.prototype = new B();
      
      function B() {
        this.value = 5;
      }
      
      new A(); // 5
      
    • Apply调用模式:applu方法接收两个参数,第一个是要绑定this的值,第二个就是一个参数数组。

  2. 尾递归优化:如果一个函数返回自身递归调用的结果,则调用过程会被替换为一个循环,可以显著的提高速度;javascript没有提供尾递归优化,深度递归的函数可能因为堆栈溢出二运行失败。(ES6支持尾调用优化,但只在严格模式下开启,正常模式是无效的)关于尾递归调用优化,可以参考阮一峰老师的博客尾调用优化

  3. 记忆:函数可以将每次计算得到的结果存储在某个对象中,以后每次计算时先查找这个对象,避免多余的计算;以计算斐波那契数列为例:

    // 没有优化的
    var count = 0;
    function fib(n) {
        count++;
        return n < 2 ? n : fib(n - 1) + fib(n - 2);
    }
    
    console.log("fib(i)\t\t结果\t调用次数")
    for(var i = 0; i <= 10;i++) {
        console.log("fib(" + i + ")\t\t" + fib(i) + "\t" + count)
    }
    
    // 有记忆功能的
    var count = 0;
    var fibWithMemo = (function() {
        var memo = [0, 1];
        var fib = function(n) {
          count++;
          var result = memo[n];
          if(typeof result !== 'number') {
            result = fib(n - 1) + fib(n - 2);
            memo[n] = result;
          }
          return result;
        }
        return fib;
    })();
    console.log("fibWithMemo(i)\t\t结果\t调用次数")
    for(var i = 0; i <= 10;i++) {
        console.log("fibWithMemo(" + i + ")\t\t" + fibWithMemo(i) + "\t" + count)
    }
    

我们看两次运行的结果
带记忆功能的斐波那契数列

一些没有关注到的细节

  1. JS在被创建的时候,Unicode是一个16位的字符集,所以JS中的所有字符都是16位的,即2个字节
  2. 字符串是不可变的,一旦字符串被创建,就永远无法改变,但是可以通过+运算符来创建新的字符串
  3. JavaScript中 % 不是通常数学意义上的模运算,实际上是求余运算。

    关于求余和模运算的区别:两个运算数都是正数时,模运算和求余运算结果相同,但当有一个数是负数时,结果不同。

  4. 对象的属性名可以是包括空字符串在内的任意字符串,属性值可以使除undefined之外的任何值

  5. delete运算符可以用来删除对象的属性,但是不会触及原型链中的任何对象。
  6. 函数名的作用:1.可以递归的调用自己 2.可以被调试器和开发工具用来识别函数。函数参数是动态初始化的,不像其他变量被初始化为undefined,而是在函数被调用的时候初始化为实际提供的参数值。
    关于函数这一块,正巧今天做了一道题目,问以下输出的结果是什么:

    var f = function g() {
            return 23;
        };
    typeof g();
    

    A.”number” B.”undefined” C.”function” D.Error

    答案是D,正确的解释如下

    在 JS 里,声明函数只有 2 种方法:
    第 1 种: function foo(){…} (函数声明)
    第 2 种: var foo = function(){…} (等号后面必须是匿名函数,这句实质是函数表达式)
    除此之外,类似于 var foo = function bar(){…} 这样的东西统一按 2 方法处理,即在函数外部无法通过 bar 访问到函数,因为这已经变成了一个表达式。
    但为什么不是 “undefined”?
    这里如果求 typeof g ,会返回 undefined,但求的是 g(),所以会去先去调用函数 g,这里就会直接抛出异常,所以是 Error。

  7. throw语句中断函数执行,抛出exception对象,该对象包含一个用来识别异常类型的name属性和描述性的message属性,exception对象被传到catch从句。

  8. 级联:让方法返回this而不是undefined就可以启动级联。(级联即obj.fun1().fun2().fun3();这种形式)
  9. 数组的[]后置下标运算符把它所含的表达式转换成一个字符串,如果该表达式有toString()方法,则调用该方法的值。
  10. 直接给数组设置更大的length值不会给数组分配更大的空间。
  11. javascript没有多维数组,但是支持元素为数组的数组。
  12. Object.create()用在数组上面是没有意义的,它产生一个对象而不是一个数组,产生的对象继承数组的值和方法,但是没有length属性。
  13. 低版本浏览器中,Array.join()方法比字符串的+运算速度快;高版本浏览器对+运算符做了很多优化,性能远高于Array.join()。
  14. parseInt()将字符串转换成整数,如果该字符串第一个字符为0,则该字符串基于8进制转换。
  15. typeof null == object; typeof NaN == ‘number’
  16. 位运算符:js没有整数类型,只有双精度的浮点数。位操作符把他们的数字运算数先转换成整数,接着执行运算,再转换回去。在C/C++/Java等语言中,位运算符接近硬件处理,所以非常快;但js的执行环境一般接触不到硬件,所以非常慢。

编程习惯

  1. 尽量避免使用/**/注释,因为这个字符对也可能出现在正则表达式里面,所以块注释对于代码块来说是不安全的,尽量使用//注释(挺鸡肋的一点…编辑器都有自动注释的快捷键,就sublime来说默认使用的就是行注释)
  2. 最小化全局变量:只创建一个唯一的全局变量,其他变量都是这个全局变量的属性。
  3. 扩充基本类型的原型是比较危险的一件事情,在混用类库的时候需要更小心,在确定没有该方法的时候再添加

一些实践

  1. JSON解析器
  2. 对象数组的排序
  3. array.splice()的实现
  4. 构造带记忆功能的函数(生成器)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值