美文网首页
es5--语法专题笔记

es5--语法专题笔记

作者: guyigg | 来源:发表于2018-11-15 17:37 被阅读0次

    数据类型的转换

    JavaScript 是一种动态类型语言,变量没有类型限制,可以随时赋予任意值。

    var x = y ? 1 : 'a';
    

    上面代码中,变量x到底是数值还是字符串,取决于另一个变量y的值。ytrue时,x是一个数值;yfalse时,x是一个字符串。这意味着,x的类型没法在编译阶段就知道,必须等到运行时才能知道。

    虽然变量的数据类型是不确定的,但是各种运算符对数据类型是有要求的。如果运算符发现,运算子的类型与预期不符,就会自动转换类型。比如,减法运算符预期左右两侧的运算子应该是数值,如果不是,就会自动将它们转为数值。

    • 强制转换

    强制转换主要指使用Number()String()Boolean()三个函数,手动将各种类型的值,分别转换成数字、字符串或者布尔值。
    使用Number函数,可以将任意类型的值转化成数值。分成两种情况讨论,一种是参数是原始类型的值,另一种是参数是对象。

    / 数值:转换后还是原来的值
    Number(324) // 324
    
    // 字符串:如果可以被解析为数值,则转换为相应的数值
    Number('324') // 324
    
    // 字符串:如果不可以被解析为数值,返回 NaN
    Number('324abc') // NaN
    
    // 空字符串转为0
    Number('') // 0
    
    // 布尔值:true 转成 1,false 转成 0
    Number(true) // 1
    Number(false) // 0
    
    // undefined:转成 NaN
    Number(undefined) // NaN
    
    // null:转成0
    Number(null) // 0
    

    Number函数将字符串转为数值,要比parseInt函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN。另外,parseIntNumber函数都会自动过滤一个字符串前导和后缀的空格。

    Number方法的参数是对象时,将返回NaN,除非是包含单个数值的数组。之所以会这样,是因为Number背后的转换规则比较复杂。

    第一步,调用对象自身的valueOf方法。如果返回原始类型的值,则直接对该值使用Number函数,不再进行后续步骤。
    第二步,如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。
    第三步,如果toString方法返回的是对象,就报错。

    var obj = {x: 1};
    Number(obj) // NaN
    
    // 等同于
    if (typeof obj.valueOf() === 'object') {
      Number(obj.toString());
    } else {
      Number(obj.valueOf());
    }
    

    上面代码中,Number函数将obj对象转为数值。背后发生了一连串的操作,首先调用obj.valueOf方法, 结果返回对象本身;于是,继续调用obj.toString方法,这时返回字符串[object Object],对这个字符串使用Number函数,得到NaN。如果toString方法返回的不是原始类型的值,结果就会报错。

    var obj = {
      valueOf: function () {
        return {};
      },
      toString: function () {
        return {};
      }
    };
    Number(obj)
    // TypeError: Cannot convert object to primitive value
    
    Number({
      valueOf: function () {
        return 2;
      }
    })
    // 2
    
    Number({
      toString: function () {
        return 3;
      }
    })
    // 3
    
    Number({
      valueOf: function () {
        return 2;
      },
      toString: function () {
        return 3;
      }
    })
    // 2
    

    从上例还可以看到,valueOftoString方法,都是可以自定义的。上面代码对三个对象使用Number函数。第一个对象返回valueOf方法的值,第二个对象返回toString方法的值,第三个对象表示valueOf方法先于toString方法执行。

    String函数可以将任意类型的值转化成字符串,转换规则如下。

    • 原始类型>> 数值:转为相应的字符串;字符串:转换后还是原来的值;布尔值:true转为字符串“true”,false转为字符串“false”;undefined:转为字符串“undefined”;null:转为字符串“null”
    • 对象>> String方法的参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。
    String({a: 1}) // "[object Object]"
    String([1, 2, 3]) // "1,2,3"
    

    String方法背后的转换规则,与Number方法基本相同,只是互换了valueOf方法和toString方法的执行顺序。

    1. 先调用对象自身的toString方法。如果返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
    2. 如果toString方法返回的是对象,再调用原对象的valueOf方法。如果valueOf方法返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
    3. 如果valueOf方法返回的是对象,就报错。
    String({
      toString: function () {
        return 3;
      }
    })
    // "3"
    
    String({
      valueOf: function () {
        return 2;
      }
    })
    // "[object Object]"
    
    String({
      valueOf: function () {
        return 2;
      },
      toString: function () {
        return 3;
      }
    })
    // "3"
    

    上面是通过自定义toString方法,改变返回值的例子。上面代码对三个对象使用String函数。第一个对象返回toString方法的值(数值3),第二个对象返回的还是toString方法的值([object Object]),第三个对象表示toString方法先于valueOf方法执行。

    Boolean函数可以将任意类型的值转为布尔值。它的转换规则相对简单:除了以下五个值的转换结果为false,其他的值全部为true。注意,所有对象(包括空对象)的转换结果都是true,甚至连false对应的布尔对象new Boolean(false)也是true

    undefined、null、-0或+0、NaN、‘ ’(空字符串)为false

    Boolean(undefined) // false
    Boolean(null) // false
    Boolean(0) // false
    Boolean(NaN) // false
    Boolean('') // false
    

    自动转换,它是以强制转换为基础的。遇到以下三种情况时,JavaScript 会自动转换数据类型,即转换是自动完成的,用户不可见。
    第一种情况,不同类型的数据互相运算。
    第二种情况,对非布尔值类型的数据求布尔值。
    第三种情况,对非数值类型的值使用一元运算符(即+-)。

    第一种情况
    123 + 'abc' // "123abc"
    
    第二种情况
    if ('abc') {
      console.log('hello')
    }  // "hello"
    
    第三种情况
    + {foo: 'bar'} // NaN
    - [1, 2, 3] // NaN
    

    自动转换的规则是这样的:预期什么类型的值,就调用该类型的转换函数。比如,某个位置预期为字符串,就调用String函数进行转换。如果该位置即可以是字符串,也可能是数值,那么默认转为数值。

    JavaScript 遇到预期为布尔值的地方(比如if语句的条件部分),就会将非布尔值的参数自动转换为布尔值。系统内部会自动调用Boolean函数。下面两种写法,有时也用于将一个表达式转为布尔值。它们内部调用的也是Boolean函数。

    // 写法一
    expression ? true : false
    
    // 写法二
    !! expression
    

    JavaScript 遇到预期为字符串的地方,就会将非字符串的值自动转为字符串。具体规则是,先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串。

    字符串的自动转换,主要发生在字符串的加法运算时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。

    '5' + 1 // '51'
    '5' + true // "5true"
    '5' + false // "5false"
    '5' + {} // "5[object Object]"
    '5' + [] // "5"
    '5' + function (){} // "5function (){}"
    '5' + undefined // "5undefined"
    '5' + null // "5null"
    

    JavaScript 遇到预期为数值的地方,就会将参数值自动转换为数值。系统内部会自动调用Number函数。除了加法运算符(+)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值。

    '5' - '2' // 3
    '5' * '2' // 10
    true - 1  // 0
    false - 1 // -1
    '1' - 1   // 0
    '5' * []    // 0
    false / '5' // 0
    'abc' - 1   // NaN
    null + 1 // 1
    undefined + 1 // NaN
    

    注意:null转为数值时为0,而undefined转为数值时为NaN

    错误处理机制

    • Error 实例对象

    JavaScript 解析或运行时,一旦发生错误,引擎就会抛出一个错误对象。JavaScript 原生提供Error构造函数,所有抛出的错误都是这个构造函数的实例。

    var err = new Error('出错了');
    err.message // "出错了"
    

    上面代码中,我们调用Error构造函数,生成一个实例对象errError构造函数接受一个参数,表示错误提示,可以从实例的message属性读到这个参数。抛出Error实例对象以后,整个程序就中断在发生错误的地方,不再往下执行。

    JavaScript 语言标准只提到,Error实例对象必须有message属性,表示出错时的提示信息,没有提到其他属性。大多数 JavaScript 引擎,对Error实例还提供namestack属性,分别表示错误的名称和错误的堆栈,但它们是非标准的,不是每种实现都有。
    message:错误提示信息
    name:错误名称(非标准属性)
    stack:错误的堆栈(非标准属性)

    if (error.name) {
      console.log(error.name + ': ' + error.message);
    }
    
    function throwit() {
      throw new Error('');
    }
    
    function catchit() {
      try {
        throwit();
      } catch(e) {
        console.log(e.stack); // print stack trace
      }
    }
    catchit()
    // Error
    //    at throwit (~/examples/throwcatch.js:9:11)
    //    at catchit (~/examples/throwcatch.js:3:9)
    //    at repl:1:5
    

    上面代码中,错误堆栈的最内层是throwit函数,然后是catchit函数,最后是函数的运行环境。

    • 原生错误类型

    Error实例对象是最一般的错误类型,在它的基础上,JavaScript 还定义了其他6种错误对象。也就是说,存在Error的6个派生对象。
    SyntaxError对象是解析代码时发生的语法错误。下面代码的错误,都是在语法解析阶段就可以发现,所以会抛出SyntaxError。第一个错误提示是“token 非法”,第二个错误提示是“字符串不符合要求”。

    // 变量名错误
    var 1a;
    // Uncaught SyntaxError: Invalid or unexpected token
    
    // 缺少括号
    console.log 'hello');
    // Uncaught SyntaxError: Unexpected string
    

    ReferenceError对象是引用一个不存在的变量时发生的错误。另一种触发场景是,将一个值分配给无法分配的对象,比如对函数的运行结果或者this赋值。

    // 使用一个不存在的变量
    unknownVariable
    // Uncaught ReferenceError: unknownVariable is not defined
    
    // 等号左侧不是变量
    console.log() = 1
    // Uncaught ReferenceError: Invalid left-hand side in assignment
    
    // this 对象不能手动赋值
    this = 1
    // ReferenceError: Invalid left-hand side in assignment
    

    上面代码对函数console.log的运行结果和this赋值,结果都引发了ReferenceError错误。

    RangeError对象是一个值超出有效范围时发生的错误。主要有几种情况,一是数组长度为负数,二是Number对象的方法参数超出范围,以及函数堆栈超过最大值。

    // 数组长度不得为负数
    new Array(-1)
    // Uncaught RangeError: Invalid array length
    

    TypeError对象是变量或参数不是预期类型时发生的错误。比如,对字符串、布尔值、数值等原始类型的值使用new命令,就会抛出这种错误,因为new命令的参数应该是一个构造函数。

    new 123
    // Uncaught TypeError: number is not a func
    
    var obj = {};
    obj.unknownMethod()
    // Uncaught TypeError: obj.unknownMethod is not a function
    

    上面代码的第二种情况,调用对象不存在的方法,也会抛出TypeError错误,因为obj.unknownMethod的值是undefined,而不是一个函数。

    URIError对象是 URI 相关函数的参数不正确时抛出的错误,主要涉及encodeURI()decodeURI()encodeURIComponent()decodeURIComponent()escape()unescape()这六个函数。

    decodeURI('%2')
    // URIError: URI malformed
    

    eval函数没有被正确执行时,会抛出EvalError错误。该错误类型已经不再使用了,只是为了保证与以前代码兼容,才继续保留。

    以上这6种派生错误,连同原始的Error对象,都是构造函数。开发者可以使用它们,手动生成错误对象的实例。这些构造函数都接受一个参数,代表错误提示信息(message)。

    var err1 = new Error('出错了!');
    var err2 = new RangeError('出错了,变量超出有效范围!');
    var err3 = new TypeError('出错了,变量类型无效!');
    
    err1.message // "出错了!"
    err2.message // "出错了,变量超出有效范围!"
    err3.message // "出错了,变量类型无效!"
    
    • 自定义错误

    除了 JavaScript 原生提供的七种错误对象,还可以定义自己的错误对象。

    function UserError(message) {
      this.message = message || '默认信息';
      this.name = 'UserError';
    }
    
    UserError.prototype = new Error();
    UserError.prototype.constructor = UserError;
    
    new UserError('这是自定义的错误!');
    

    上面代码自定义一个错误对象UserError,让它继承Error对象。然后,就可以生成这种自定义类型的错误了。

    • throw语句

    throw语句的作用是手动中断程序执行,抛出一个错误。下面代码中,如果变量x小于等于0,就手动抛出一个错误,告诉用户x的值不正确,整个程序就会在这里中断执行。可以看到,throw抛出的错误就是它的参数,这里是一个Error实例。

    if (x <= 0) {
      throw new Error('x 必须为正数');
    }
    // Uncaught ReferenceError: x is not defined
    

    throw也可以抛出自定义错误。

    function UserError(message) {
      this.message = message || '默认信息';
      this.name = 'UserError';
    }
    
    throw new UserError('出错了!');
    // Uncaught UserError {message: "出错了!", name: "UserError"}
    

    上面代码中,throw抛出的是一个UserError实例。实际上,throw可以抛出任何类型的值。也就是说,它的参数可以是任何值。对于 JavaScript 引擎来说,遇到throw语句,程序就中止了。引擎会接收到throw抛出的信息,可能是一个错误实例,也可能是其他类型的值。

    // 抛出一个字符串
    throw 'Error!';
    // Uncaught Error!
    
    // 抛出一个数值
    throw 42;
    // Uncaught 42
    
    // 抛出一个布尔值
    throw true;
    // Uncaught true
    
    // 抛出一个对象
    throw {
      toString: function () {
        return 'Error!';
      }
    };
    // Uncaught {toString: ƒ}
    
    • try...catch结构

    一旦发生错误,程序就中止执行了。JavaScript 提供了·try...catch·结构,允许对错误进行处理,选择是否往下执行。

    try {
      throw new Error('出错了!');
    } catch (e) {
      console.log(e.name + ": " + e.message);
      console.log(e.stack);
    }
    // Error: 出错了!
    //   at <anonymous>:3:9
    //   ...
    

    上面代码中,try代码块抛出错误(上例用的是throw语句),JavaScript 引擎就立即把代码的执行,转到catch代码块,或者说错误被catch代码块捕获了。catch接受一个参数,表示try代码块抛出的值。catch代码块之中,还可以再抛出错误,甚至使用嵌套的try...catch结构。

    var n = 100;
    
    try {
      throw n;
    } catch (e) {
      if (e <= 50) {
        // ...
      } else {
        throw e;
      }
    }
    // Uncaught 100
    

    为了捕捉不同类型的错误,catch代码块之中可以加入判断语句。下面代码中,catch捕获错误之后,会判断错误类型(EvalError还是RangeError),进行不同的处理。

    try {
      foo.bar();
    } catch (e) {
      if (e instanceof EvalError) {
        console.log(e.name + ": " + e.message);
      } else if (e instanceof RangeError) {
        console.log(e.name + ": " + e.message);
      }
      // ...
    }
    
    • finally 代码块

    try...catch结构允许在最后添加一个finally代码块,表示不管是否出现错误,都必需在最后运行的语句。

    function cleansUp() {
      try {
        throw new Error('出错了……');
        console.log('此行不会执行');
      } finally {
        console.log('完成清理工作');
      }
    }
    
    cleansUp()
    // 完成清理工作
    // Uncaught Error: 出错了……
    //    at cleansUp (<anonymous>:3:11)
    //    at <anonymous>:10:1
    

    上面代码中,由于没有catch语句块,一旦发生错误,代码就会中断执行。中断执行之前,会先执行finally代码块,然后再向用户提示报错信息。

    例一
    function idle(x) {
      try {
        console.log(x);
        return 'result';
      } finally {
        console.log('FINALLY');
      }
    }
    idle('hello')
    // hello
    // FINALLY
    例二
    var count = 0;
    function countUp() {
      try {
        return count;
      } finally {
        count++;
      }
    }
    countUp()
    // 0
    count
    // 1
    

    上面代码例一中,try代码块没有发生错误,而且里面还包括return语句,但是finally代码块依然会执行。而且,这个函数的返回值还是result。上面例二说明,return语句的执行是排在finally代码之前,只是等finally代码执行完毕后才返回。return语句里面的count的值,是在finally代码块运行之前就获取了。

    openFile();
    try {
      writeFile(Data);
    } catch(e) {
      handleError(e);
    } finally {
      closeFile();
    }
    //执行顺序
    function f() {
      try {
        console.log(0);
        throw 'bug';
      } catch(e) {
        console.log(1);
        return true; // 这句原本会延迟到 finally 代码块结束再执行
        console.log(2); // 不会运行
      } finally {
        console.log(3);
        return false; // 这句会覆盖掉前面那句 return
        console.log(4); // 不会运行
      }
    
      console.log(5); // 不会运行
    }
    
    var result = f();
    // 0
    // 1
    // 3
    
    result
    // false
    

    上面代码首先打开一个文件,然后在try代码块中写入文件,如果没有发生错误,则运行finally代码块关闭文件;一旦发生错误,则先使用catch代码块处理错误,再使用finally代码块关闭文件。

    编程风格

    代码缩进;建议总是使用大括号明确区块;圆括号;建议总是在行尾加上分号(forwhiledo...while不需要分号);全局变量建议用大写来区分;尽量不要是用with语句;相等和严格相等尽量使用严格相等;建议不要将不同目的的语句,合并成一行;建议自增(++)和自减(--)运算符尽量使用+=-=代替;尽量不要使用switch...case,用其他方式来代替。

    function doAction(action) {
      switch (action) {
        case 'hack':
          return 'hack';
          break;
        case 'slash':
          return 'slash';
          break;
        case 'run':
          return 'run';
          break;
        default:
          throw new Error('Invalid action.');
      }
    }
    //可以改写成对象结构来代替
    function doAction(action) {
      var actions = {
        'hack': function () {
          return 'hack';
        },
        'slash': function () {
          return 'slash';
        },
        'run': function () {
          return 'run';
        }
      };
    
      if (typeof actions[action] !== 'function') {
        throw new Error('Invalid action.');
      }
      return actions[action]();
    }
    

    console 对象与控制台

    console对象是 JavaScript 的原生对象,它有点像 Unix 系统的标准输出stdout和标准错误stderr,可以输出各种信息到控制台,并且还提供了很多有用的辅助方法。console的常见用途有两个:调试程序,显示网页代码运行时的错误信息;提供了一个命令行接口,用来与网页代码互动。

    打开开发者工具以后,顶端有多个面板。

    • Elements:查看网页的 HTML 源码和 CSS 代码。
    • Resources:查看网页加载的各种资源文件(比如代码文件、字体文件 CSS 文件等),以及在硬盘上创建的各种内容(比如本地缓存、Cookie、Local Storage等)。
    • Network:查看网页的 HTTP 通信情况。
    • Sources:查看网页加载的脚本源码。
    • Timeline:查看各种网页行为随时间变化的情况。
    • Performance:查看网页的性能情况,比如 CPU 和内存消耗。
    • Console:用来运行 JavaScript 命令。
    • console对象提供的各种静态方法,用来与控制台窗口互动。console.log()console.info()console.debug()

    console.log方法用于在控制台输出信息。它可以接受一个或多个参数,将它们连接起来输出。
    如果第一个参数是格式字符串(使用了格式占位符),console.log方法将依次用后面的参数替换占位符,然后再进行输出。console.log方法支持以下占位符,不同类型的数据必须使用对应的占位符。
    %s 字符串、%d 整数、%i 整数、%f浮点数、%o 对象的链接、%c CSS 格式字符串

    console.log(' %s + %s = %s', 1, 1, 2)
    //  1 + 1 = 2
    
    var number = 11 * 9;
    var color = 'red';
    console.log('%d %s balloons', number, color);
    // 99 red balloons
    
    //使用%c占位符时,对应的参数必须是 CSS 代码,用来对输出内容进行 CSS 渲染。
    console.log(
      '%cThis text is styled!',
      'color: red; background: yellow; font-size: 24px;'
    )
    

    console.infoconsole.log方法的别名,用法完全一样。只不过console.info方法会在输出信息的前面,加上一个蓝色图标。

    console.debug方法与console.log方法类似,会在控制台输出调试信息。但是,默认情况下,console.debug输出的信息不会显示,只有在打开显示级别在verbose的情况下,才会显示。console.warn方法和console.error方法也是在控制台输出信息,它们与log方法的不同之处在于,warn方法输出信息时,在最前面加一个黄色三角,表示警告;error方法输出信息时,在最前面加一个红色的叉,表示出错。同时,还会高亮显示输出文字和错误发生的堆栈。其他方面都一样。对于某些复合类型的数据,console.table方法可以将其转为表格显示。

    var languages = [
      { name: "JavaScript", fileExtension: ".js" },
      { name: "TypeScript", fileExtension: ".ts" },
      { name: "CoffeeScript", fileExtension: ".coffee" }
    ];
    console.table(languages);
    

    console.count方法用于计数,输出它被调用了多少次。该方法可以接受一个字符串作为参数,作为标签,对执行次数进行分类。

    function greet(user) {
      console.count();
      return 'hi ' + user;
    }
    greet('bob')
    //  : 1
    // "hi bob"
    greet('alice')
    //  : 2
    // "hi alice"
    greet('bob')
    //  : 3
    // "hi bob"
    
    function greet(user) {
      console.count(user);
      return "hi " + user;
    }
    greet('bob')
    // bob: 1
    // "hi bob"
    greet('alice')
    // alice: 1
    // "hi alice"
    greet('bob')
    // bob: 2
    // "hi bob"
    

    console.dir()console.dirxml()方法用来对一个对象进行检查(inspect),并以易于阅读和打印的格式显示。dir方法对于输出 DOM 对象非常有用,因为会显示 DOM 对象的所有属性。dirxml方法主要用于以目录树的形式,显示 DOM 节点。如果参数不是 DOM 节点,而是普通的 JavaScript 对象,console.dirxml等同于console.dir

    console.dir(document.body);
    console.dirxml(document.body);
    

    console.time()console.timeEnd()这两个方法用于计时,可以算出一个操作所花费的准确时间。time方法表示计时开始,timeEnd方法表示计时结束。它们的参数是计时器的名称。调用timeEnd方法之后,控制台会显示“计时器名称: 所耗费的时间”。

    console.time('Array initialize');
    var array= new Array(1000000);
    for (var i = array.length - 1; i >= 0; i--) {
      array[i] = new Object();
    };
    console.timeEnd('Array initialize');
    // Array initialize: 1914.481ms
    

    console.groupconsole.groupEnd这两个方法用于将显示的信息分组。它只在输出大量信息时有用,分在一组的信息,可以用鼠标折叠/展开。console.groupCollapsed方法与console.group方法很类似,唯一的区别是该组的内容,在第一次显示时是收起的(collapsed),而不是展开的。

    console.trace方法显示当前执行的代码在堆栈中的调用路径。console.clear方法用于清除当前控制台的所有输出,将光标回置到第一行。如果用户选中了控制台的“Preserve log”选项,console.clear方法将不起作用。

    浏览器控制台中,除了使用console对象,还可以使用一些控制台自带的命令行方法。(了解)。

    debugger语句主要用于除错,作用是设置断点。如果有正在运行的除错工具,程序运行到debugger语句时会自动停下。如果没有除错工具,debugger语句不会产生任何结果,JavaScript 引擎自动跳过这一句。

    for(var i = 0; i < 5; i++){
      console.log(i);
      if (i === 2) debugger;
    }
    

    相关文章

      网友评论

          本文标题:es5--语法专题笔记

          本文链接:https://www.haomeiwen.com/subject/ugmyfqtx.html