JS学习笔记

101 阅读14分钟

基本语法

  1. 变量提升:JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。
  2. 标识符:第一个字符,可以是任意 Unicode 字母(包括英文字母和其他语言的字母),以及美元符号($)和下划线(_)。第二个字符及后面的字符,除了 Unicode 字母、美元符号和下划线,还可以用数字0-9
  3. 三元运算符:(条件) ? 表达式1 : 表达式2; 如果“条件”为true,则返回“表达式1”的值,否则返回“表达式2”的值。

数据类型

  1. JavaScript 有三种方法,可以确定一个值到底是什么类型:

    • typeof 运算符,可以返回一个值的数据类型。undefined返回undefinednull返回object
    • instanceof 运算符
    • Object.prototype.toString 方法
  2. null和undefined

    • if语句中,它们都会被自动转为false
    • 相等运算符(==)甚至直接报告两者相等
    • null是一个表示“空”的对象,转为数值时为0
    • undefined是一个表示"此处无定义"的原始值,转为数值时为NaN
    • null表示空值,即该处的值现在为空
    • undefined表示“未定义”
  3. 布尔值,转换规则是除了下面六个值被转为false,其他值都视为true

    • undefined
    • null
    • false
    • 0
    • NaN
    • ""''(空字符串)
  4. 浮点数与整数,JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,11.0是相同的,是同一个数。这就是说,JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)。容易造成混淆的是,某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算。

  5. 数值精度,精度最多只能到53个二进制位,这意味着,绝对值小于2的53次方的整数,即-253到253,都可以精确表示。

  6. 数值范围,JavaScript 提供Number对象的MAX_VALUEMIN_VALUE属性,返回可以表示的具体的最大值和最小值。

    Number.MAX_VALUE // 1.7976931348623157e+308
    Number.MIN_VALUE // 5e-324
    
  7. 数值的表示法,可以用字面形式直接表示,比如35(十进制)和0xFF(十六进制),科学计数法允许字母eE的后面,跟着一个整数,表示这个数值的指数部分。

    • 十进制:没有前导0的数值。
    • 八进制:有前缀0o0O的数值,或者有前导0、且只用到0-7的八个阿拉伯数字的数值。
    • 十六进制:有前缀0x0X的数值。
    • 二进制:有前缀0b0B的数值。
  8. 特殊数值

    • 正零和负零,JavaScript 内部实际上存在2个0:一个是+0,一个是-0,区别就是64位浮点数表示法的符号位不同。它们是等价的。

    • NaN,NaN是 JavaScript 的特殊值,表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合。

      • NaN不等于任何值,包括它本身。
      NaN === NaN // false
      
      • 数组的indexOf方法内部使用的是严格相等运算符,所以该方法对NaN不成立。
      [NaN].indexOf(NaN) // -1
      
      • NaN在布尔运算时被当作false
      Boolean(NaN) // false
      
      • NaN与任何数(包括它自己)的运算,得到的都是NaN
      NaN + 32 // NaN
      NaN - 32 // NaN
      NaN * 32 // NaN
      NaN / 32 // NaN
      
    • Infinity,Infinity表示“无穷”,用来表示两种场景。一种是一个正的数值太大,或一个负的数值太小,无法表示;另一种是非0数值除以0,得到InfinityInfinity有正负之分,Infinity表示正的无穷,-Infinity表示负的无穷。

      • Infinity的四则运算,符合无穷的数学计算规则。
      5 * Infinity // Infinity
      5 - Infinity // -Infinity
      Infinity / 5 // Infinity
      5 / Infinity // 0
      
      • 0乘以Infinity,返回NaN;0除以Infinity,返回0Infinity除以0,返回Infinity
      0 * Infinity // NaN
      0 / Infinity // 0
      Infinity / 0 // Infinity
      
      • Infinity加上或乘以Infinity,返回的还是Infinity
      Infinity + Infinity // Infinity
      Infinity * Infinity // Infinity
      
      • Infinity减去或除以Infinity,得到NaN
      Infinity - Infinity // NaN
      Infinity / Infinity // NaN
      
      • Infinitynull计算时,null会转成0,等同于与0的计算。
      null * Infinity // NaN
      null / Infinity // 0
      Infinity / null // Infinity
      
      • Infinityundefined计算,返回的都是NaN
      undefined + Infinity // NaN
      undefined - Infinity // NaN
      undefined * Infinity // NaN
      undefined / Infinity // NaN
      Infinity / undefined // NaN
      

字符串

  1. 单引号字符串的内部,可以使用双引号。双引号字符串的内部,可以使用单引号。
  2. 由于 HTML 语言的属性值使用双引号,所以很多项目约定 JavaScript 语言的字符串只使用单引号。当然,只使用双引号也完全可以。重要的是坚持使用一种风格,不要一会使用单引号表示字符串,一会又使用双引号表示。
  3. 反斜杠(\)在字符串内有特殊含义,用来表示一些特殊字符,所以又称为转义符。
    • \0 :null(\u0000
    • \b :后退键(\u0008
    • \f :换页符(\u000C
    • \n :换行符(\u000A
    • \r :回车键(\u000D
    • \t :制表符(\u0009
    • \v :垂直制表符(\u000B
    • ' :单引号(\u0027
    • " :双引号(\u0022
    • \ :反斜杠(\u005C
  4. 字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。但是,字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。

对象

  1. 对象的引用,如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。

  2. 表达式还是语句?

    • 为了避免这种歧义,JavaScript 引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块。
    { console.log(123) } // 123
    
    • 上面的语句是一个代码块,而且只有解释为代码块,才能执行。
    • 如果要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面,只能是表达式,所以确保大括号只能解释为对象。
    ({ foo: 123 }) // 正确
    ({ console.log(123) }) // 报错
    
  3. 属性的读取。

    • 读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。
    • 请注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理。
    • 方括号运算符内部还可以使用表达式。
    • 点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。
  4. 属性的删除:delete 命令

    • delete命令用于删除对象的属性,删除成功后返回true
    • 只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除。
    • delete命令只能删除对象本身的属性,无法删除继承的属性
  5. 属性是否存在:in 运算符

    • in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true,否则返回false
    var obj = { p: 1 };
    'p' in obj // true
    'toString' in obj // true
    
    • in运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。这时,可以使用对象的hasOwnProperty方法判断一下,是否为对象自身的属性。

函数

  1. 函数名的提升,JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。
  2. 闭包,可以把闭包简单理解成“定义在一个函数内部的函数”。
  3. 闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
  4. 注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。

比较运算符

  1. JavaScript 引擎内部首先比较首字符的 Unicode 码点。如果相等,再比较第二个字符的 Unicode 码点,以此类推。
  2. NaN的比较。任何值(包括NaN本身)与NaN使用非相等运算符进行比较,返回的都是false
  3. 如果运算子是对象,会转为原始类型的值,再进行比较。对象转换成原始类型的值,算法是先调用valueOf方法;如果返回的还是对象,再接着调用toString方法。
  4. 两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址。
  5. 注意,对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值。
  6. undefinednull与自身严格相等。
  7. undefinednull只有与自身比较,或者互相比较时,才会返回true;与其他类型的值比较时,结果都为false

布尔运算符

  1. 可以这样记忆,以下六个值取反后为true,其他值都为false
    • undefined
    • null
    • false
    • 0
    • NaN
    • 空字符串(''
  2. 且运算符(&&):它的运算规则是:如果第一个运算子的布尔值为true,则返回第二个运算子的值(注意是值,不是布尔值);如果第一个运算子的布尔值为false,则直接返回第一个运算子的值,且不再对第二个运算子求值。如果所有表达式的布尔值都为true,则返回最后一个表达式的值。
  3. 或运算符(||):如果第一个运算子的布尔值为true,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为false,则返回第二个运算子的值。如果所有表达式都为false,则返回最后一个表达式的值。
  4. 或运算符常用于为一个变量设置默认值。
    function saveText(text) {
      text = text || '';
      // ...
    }
    
    // 或者写成
    saveText(this.text || '')
    
    上面代码表示,如果函数调用时,没有提供参数,则该参数默认设置为空字符串。
  5. 三元条件运算符:三元条件运算符由问号(?)和冒号(:)组成,分隔三个表达式。它是 JavaScript 语言唯一一个需要三个运算子的运算符。如果第一个表达式的布尔值为true,则返回第二个表达式的值,否则返回第三个表达式的值。

二进制位运算符

  1. 二进制位运算符用于直接对二进制位进行计算,一共有7个。

    • 二进制或运算符(or):符号为|,表示若两个二进制位都为0,则结果为0,否则为1
    • 二进制与运算符(and):符号为&,表示若两个二进制位都为1,则结果为1,否则为0。
    • 二进制否运算符(not):符号为~,表示对一个二进制位取反。
    • 异或运算符(xor):符号为^,表示若两个二进制位不相同,则结果为1,否则为0。
    • 左移运算符(left shift):符号为<<
    • 右移运算符(right shift):符号为>>
    • 头部补零的右移运算符(zero filled right shift):符号为>>>
  2. 使用二进制否运算取整,是所有取整方法中最快的一种。

    ~~2.9 // 2
    ~~47.11 // 47
    ~~1.9999 // 1
    ~~3 // 3
    
  3. 对字符串进行二进制否运算,JavaScript 引擎会先调用Number函数,将字符串转为数值。

    // 相当于~Number('011')
    ~'011'  // -12
    
    // 相当于~Number('42 cats')
    ~'42 cats' // -1
    
    // 相当于~Number('0xcafebabe')
    ~'0xcafebabe' // 889275713
    
    // 相当于~Number('deadbeef')
    ~'deadbeef' // -1
    
  4. “异或运算”有一个特殊运用,连续对两个数ab进行三次异或运算,a^=b; b^=a; a^=b;,可以互换它们的值。这意味着,使用“异或运算”可以在不引入临时变量的前提下,互换两个变量的值。

    var a = 10;
    var b = 99;
    
    a ^= b, b ^= a, a ^= b;
    
    a // 99
    b // 10
    

    这是互换两个变量的值的最快方法。

  5. 异或运算也可以用来取整。

    12.9 ^ 0 // 12
    
  6. 左移运算符: 左移运算符(<<)表示将一个数的二进制值向左移动指定的位数,尾部补0,即乘以2的指定次方。向左移动的时候,最高位的符号位是一起移动的。如果左移0位,就相当于将该数值转为32位整数,等同于取整,对于正数和负数都有效。

  7. 右移运算符: 右移运算符(>>)表示将一个数的二进制值向右移动指定的位数。如果是正数,头部全部补0;如果是负数,头部全部补1。右移运算符基本上相当于除以2的指定次方(最高位即符号位参与移动)。

  8. 头部补零的右移运算符: 头部补零的右移运算符(>>>)与右移运算符(>>)只有一个差别,就是一个数的二进制形式向右移动时,头部一律补零,而不考虑符号位。所以,该运算总是得到正值。对于正数,该运算的结果与右移运算符(>>)完全一致,区别主要在于负数。

  9. 运算顺序: 根据语言规格,这五个运算符的优先级从高到低依次为:小于等于(<=)、严格相等(===)、或(||)、三元(?:)、等号(=)。

  10. 左结合与右结合: JavaScript 语言的大多数运算符是“左结合”,请看下面加法运算符的例子。

    x + y + z
    
    // 引擎解释如下
    (x + y) + z
    
  11. 左结合与右结合: 少数运算符是“右结合”,其中最主要的是赋值运算符(=)和三元条件运算符(?:)。

    w = x = y = z;
    q = a ? b : c ? d : e ? f : g;
    

    上面代码的解释方式如下。

    w = (x = (y = z));
    q = a ? b : (c ? d : (e ? f : g));
    

    上面的两行代码,都是右侧的运算数结合在一起。

    另外,指数运算符(**)也是右结合。

    2 ** 3 ** 2
    // 相当于 2 ** (3 ** 2)
    // 512
    

数据类型转换

  1. Number()、String()、Boolean()

错误处理机制

  1. SyntaxError对象是解析代码时发生的语法错误。
  2. ReferenceError对象是引用一个不存在的变量时发生的错误。
  3. RangeError对象是一个值超出有效范围时发生的错误。主要有几种情况,一是数组长度为负数,二是Number对象的方法参数超出范围,以及函数堆栈超过最大值。
  4. TypeError对象是变量或参数不是预期类型时发生的错误。
  5. URIError对象是 URI 相关函数的参数不正确时抛出的错误,主要涉及encodeURI()decodeURI()encodeURIComponent()decodeURIComponent()escape()unescape()这六个函数。
  6. 对于 JavaScript 引擎来说,遇到throw语句,程序就中止了。引擎会接收到throw抛出的信息,可能是一个错误实例,也可能是其他类型的值。
  7. try代码块抛出错误,JavaScript 引擎就立即把代码的执行,转到catch代码块,或者说错误被catch代码块捕获了。catch接受一个参数,表示try代码块抛出的值。
  8. catch代码块捕获错误之后,程序不会中断,会按照正常流程继续执行下去。
  9. catch代码块之中,还可以再抛出错误,甚至使用嵌套的try...catch结构。
  10. try...catch结构允许在最后添加一个finally代码块,表示不管是否出现错误,都必需在最后运行的语句。