• JavaScript 语言精粹读书笔记


    最近在看 赵泽欣 / 鄢学鹍 翻译的 蝴蝶书, 把一些读后感言记录在这里。
    主要是把作者的建议跟 ES5/ES5.1/ES6 新添加的功能进行了对比

    涉及到的一些定义

    • IIFE: Immediately Invoked Function Expression
      立即执行函数表达式, 一般用来隐藏私有变量, 避免临时变量污染全局空间
      常见的形式为:
    (function(){
        // function body
    })()
    

    更多请参见 这篇文章

    • Hoisting: 变量定义提升

    请参考 JavaScript Scoping and Hoisting

    • 严格模式

    一个 JavaScript 子集用作提供更彻底的错误检查。
    更多内容请参考 MDN

    • ES6 - ECMAScript 2015

    ECMA组织制订的 JavaScript 语言的第6个标准
    JavaScript 详细的历史版本请参考 阮一峰大神写的 JavaScript语言的历史
    以及这篇文章 ECMAScript版本历史

    之前不知道的坑

    • 在块注释中使用正则表达式可能出问题

    如果在快注释中出现 "*/", JavaScript 解释器无法正常工作了。 下图代码执行时会报语法错误

    
    /*/d*/*/
    
    
    • 尽量不要使用位运算

    JavaScript 里面没有整数类型,只有双精度的浮点数。
    因此位运算符会把它们的运算数从浮点数转换为整数,接着执行运算,然后再转换会浮点数。
    在大多数语言中,这些运算符接近于硬件处理,所以非常快。
    但 JavaScript 的执行环境一般接触不到硬件,所以非常慢

    • parseInt

    如果一个字符串中有数字和其他字符,parseInt 会解析从字符串开头出现的数字,直到字符串结尾或者碰到不可解析的字符。

    var num = parseInt('3abcd'); // 3
    

    parseInt 接受参数,第二个参数表示要解析数字的基数。该值介于 2~36 之间。
    如果字符串以 '0x' 或者 '0X' 开头,praseInt函数就会把第一个参数当作十六进制字符串来解析。
    作者建议手动指定第二个参数来避免意外的转换。

    parseInt('0x3a'); // 58
    
    parseInt('0x3a', 10); // 0
    
    • 正则表达式函数对 g 标识的处理方法
    函数 处理方式
    RegExp.prototype.test 忽略 g 标识
    RegExp.prototype.exec 每次调用 exec 返回一次匹配
    String.prototype.search 忽略
    String.prototype.split 忽略
    String.prototype.replace - 如果函数参数是一个正则表达式并且带有 g 标识,它会替换所有的匹配。
    - 如果表达式没有带 g 标识,它会仅替换第一个匹配
    String.prototype.match - 没有 g 标识, 与调用regex.exec(string)相同
    - 表达式使用 g 标识 生成一个包含所有匹配(忽略捕获分组)的数组

    严格模式下已经改进的缺点

    • 全局的变量定义

    传统的JS中,如果在函数中定义变量时没有使用 var 关键词, 那么这个变量就会被作为全局对象( 浏览器中的 window 对象, 或者 NodeJS 里面的 global 对象) 的属性.
    严格模式下则不允许不适用 var/let/const 来定义变量。

    // 使用 IIFE, 在函数中定义 name 变量并赋值
    (function(){
        name = "david";
    })();
    
    // 使用 hasOwnProperty window 对象 判断是否含有 name 属性
    window.hasOwnProperty('name'); // true
    
    // 使用严格模式
    function func(){
        'use strict';
        name = 'david'; // Uncaught ReferenceError: a is not defined
    }
    
    • with 关键字不能使用

    DC 不推荐使用 with 关键字来访问对象属性, 严格模式下已经禁用 with 关键字。

    let

    ES2016 引入了 let/const 来定义局部变量/常量。
    let/const 的引入解决了书中的一些问题

    • 变量重复定义

    用户可以使用 var 在同一个函数作用于里面定义多个同名的变量。但是使用 let 不可以

    {
        let a = 1;
        let a = 10; // Uncaught SyntaxError: Identifier 'a' has already been declared
    }
    
    • 拥有局部作用域
    // 使用 var 定义的变量在 for 循环外可以引用
    for (var i = 0; i < 10; i++) {}
    console.log(i); //10
    
    // 使用 let 定义的变量在 for 循环外不可使用
    for(let j = 0; j < 10; j++) {}
    console.log(j);// Error: j is not define
    
    • 变量提升

    使用 let 初始化的变量不会进行 Hoisting 变量定义提升。

    console.log(foo); // 输出undefined
    console.log(bar); // 报错ReferenceError
    
    var foo = 2;
    let bar = 2;
    
    • 作用域

    使用 let 在全局作用于定义的对象,也不过作为全局变量的属性。

    let blog_link='http://www.cnblogs.com/zf-l/';
    window.hasOwnProperty('blog_link'); //false
    
    • 不需要使用 IIFE 来解决闭包问题

    在 JS 中,如果一个函数多次调用函数外部的变量。可能会出现不可预知的问题:

    // 在一个循环中使用定时器打印外层的变量值
    for (var i = 0; i < 5; i++) {
        setTimeout(function () {
            console.log(i); // 输出5次5
        },0);
    }
    
    // 通过给临时函数赋值来解决
    for (var k = 0; k < 5; k++) {
        (function (k) {
            setTimeout(function () {
            console.log(k); //输出0,1,2,3,4
            },0);
        })(k);
    }
    
    // 使用 let 关键字
    for (let j = 0; j < 5; j++) {
        setTimeout(function () {
          console.log(j); //输出0,1,2,3,4
        },0);
    }
    

    已经实现的建议添加的函数

    DC 书中列出了好多有用的函数,其中一些已经在 ES5.x/ES6 中实现

    • Object.create

    接受对象A作为参数,返回一个新的对象 B ,B 的原型是 A。
    ES5 中已经实现。

    Object.create = function(o){
        var F = function(){};
        F.prototype = o;
        return new F();
    }
    
    • Array.prototype.reduce
    Array.method('reduce', function(f, value){
        vai i;
        for(i = 0; i < this.length; i+=1){
            value = f(this[i], value);
            return value;
        }
    })
    

    除了 reduce 函数, ES5 中还定义了这些有用的高阶函数
    Array.prototype.reduceLeft
    Array.prototype.some
    Array.prototype.every
    Array.prototype.map
    Array.prototype.filter
    Array.prototype.forEach

    • 判断是否是数组

    ES5 中定义了 Array.isArray 方法。 下面是书中的实现。

     var is_array = function(value){
         return Object.prototype.toString.apply(value) === '[object Array]';
     }
    
    • 柯里化

    ES5 中可以通过 Function.prototype.bind 来实现函数的柯里化。下面是书中的实现方式

    
    Function.method('curry', function(){
        var slice = Array.prototype.slice,
            args = slice.apply(arguments),
            that = this;
        return function(){
            return that.apply(null, args.concat(slice.apply(arguments)));
        }
    })
    

    面向对象方面的改进

    在蝴蝶书里面,作者指出了 JavaScript 构造函数的几个缺点

    1. 没有私有环境,所有的属性都是公开的
    2. 无法访问 super (父类) 的方法
    3. 调用构造函数的时候忘记加 new 方法会造成严重的危害

    其中第一个缺点 ES6 中还没有解决,另外两个已经可以防止。

    • 使用 new 前缀调用构造函数

    在 JavaScript 中, 如果在调用构造函数时忘记了在前面加上 new 前缀,那么 this 将不会绑定到一个新对象上。而是绑定到全局对象。
    所以作者建议:

    类名使用首字母大写的形式,并且不以首字母大写的形式拼写任何其他的东西。这样至少可以通过目视检查去发现是否缺少了 new 前缀。
    一个更好的备选方案是不适用 new。

    ES6 新添加了 class 语法, 使用 class 前缀定义的 类必须使用 new 前缀来调用,否则解释器就会报错。

    
    class A{
        constructor(){
            console.log(new.target.name);
        }
    }
    
    A(); // Uncaught TypeError: Class constructor A cannot be invoked without 'new'
    

    注: 在构造方法调用中,new.target 指向被 new 调用的构造函数。

    • 调用父类的方法和属性

    ES6 提供了 super 关键字访问父类的构造函数和方法
    super 有两种使用模式:

    1. super 当作函数来使用,会调用父类的构造函数
    2. 使用 super.prop 来访问父类的属性和方法

    下面 MDN 中的例子说明了如何使用 super 调用父类的构造函数和方法

    
    class Rectangle{
      constructor(height, width) {
        this.name = 'Rectangle';
        this.height = height;
        this.width = width;
      }
      sayName() {
        console.log('Hi, I am a ', this.name + '.');
      }
    }
    
    class Square extends Rectangle{
      constructor(length) {
        this.height; // ReferenceError, super 必须在 this 之前调用
    
        // 调用父类的构造函数,提供长方形的长度和宽度
        super(length, length);
    
        // Note: 在子类中,super() 必须在使用 this 之前调用
        // 否则解释器会抛出 reference error.
        this.name = 'Square';
      }
    
      get area() {
        return this.height * this.width;
      }
    
      set area(value) {
        this.height = this.width = Math.sqrt(value);
      }
    
      sayName() {
        // 调用父类的同名方法
        super.sayName();
        console.log('Hi from Square .');
      }
    }
    
    

    ES6 的其他改进

    枚举数组

    for in 可以用来遍历一个数组的所有属性, 遗憾的是, for in 无法保证属性的顺序,而大多数要遍历数组的场合都希望按照阿拉伯数字顺序来产生元素

    ES6 提供了 for of 语法来遍历数组

    // 使用 for 循环遍历数组
    var i;
    for(i = 0; i < myArray.length; i += 1){
        console.log(myArray[i]);
    }
    
    // 使用 for of 遍历数组
    for(let i of myArray){
        console.log(i);
    }
    

    不定参数

    在 ES6 出现之前, JS 处理不定参数只能使用 arguments 关键字。在文中作者如是说

    因为语言的一个设计错误, arguments 并不是一个真正的数组。它只是一个 "类似数组 (array-like)" 的对象。arguments 拥有一个 length 属性,但它没有任何数组的方法

    下面的代码中分别使用 ES5 和 ES6 的语法实现了函数默认参数

    var sum = function(){
        var i, sum = 0;
        for (i=0; i < arguments.length; i+=1){
            sum += arguments[i];
        };
    
        return sum;
    }
    
    document.writeln(sum(4, 8, 15, 16, 23, 42)); // 108
    

    相比较之下 ES6 中不定参数的实现方式就比较优雅, 跟 Python 中的 *args 类似。

    var sum = function(...nums){
        // 判断是否是数组
        console.log(Array.isArray(nums)); // true
        return nums.reduce((x,y)=>x+y);
    }
    
    document.writeln(sum(4, 8, 15, 16, 23, 42)); // 108
    

    参数默认值

    ES6 之前函数不支持在参数列表中指定默认值。
    作者往往使用 || 给函数参数选择是否使用默认值。ES6 提供了在函数形参中指定默认参数的方法。
    下面的例子中分别使用 ES5 和 ES6 的语法定义默认参数

    // 使用 || 在函数体中定义默认值
    var factorial = function factorial(i, a){
        a = a || 1;
        if(i<2){
            return a;
        }
        return factorial(i-1, a*i);
    }
    
    console.log(factorial(4)); // 24
    
    // 使用 ES6 的默认参数重新定义 factorial 函数
    var factorial = function factorial(i, a=1){
        if(i<2){
            return a;
        }
        return factorial(i-1, a*i);
    }
    
    console.log(factorial(4)); // 24
    

    有争议的部分

    自动分号

    DC 建议 JS 语句每行的结尾都要加分号。但是随着技术的发展,有不同的声音提出来:

    其他语法建议

    • 使用字符串的 slice 替换 substring
      slice 可以接受负数作为参数,完全可以替代 substring

    ES6 入门资料

    下面的列表我整理的一些学习 ES6 的资料和书籍,供各位看官参考




    转载请注明出处: [zf-l](http://www.cnblogs.com/zf-l/p/notes_js_good_parts.html)
  • 相关阅读:
    艾伟_转载:学习 ASP.NET MVC (第三回)实战篇 狼人:
    艾伟_转载:40条ASP.NET开发Tip 狼人:
    艾伟_转载:20条.NET编码习惯 狼人:
    艾伟_转载:数组排序方法的性能比较(上):注意事项及试验 狼人:
    艾伟_转载:使用LINQ to SQL更新数据库(上):问题重重 狼人:
    艾伟_转载:学习 ASP.NET MVC (第四回)实战篇 狼人:
    艾伟_转载:学习 ASP.NET MVC (第五回)理论篇 狼人:
    艾伟_转载:ASP.NET MVC 2博客系列 狼人:
    艾伟_转载:Cookie是什么?用法是怎样?与SESSION有什么区别?(二) 狼人:
    艾伟_转载:ASP.NET MVC 2博客系列之一:强类型HTML辅助方法 狼人:
  • 原文地址:https://www.cnblogs.com/zf-l/p/notes_js_good_parts.html
Copyright © 2020-2023  润新知