您的当前位置:首页正文

你不知道的JavaScript(八):类型和语法

2024-11-09 来源:个人技术集锦

类型

内置类型

typeof null === 'object'; // true

// 判断是否为null的合理方法
let a = null;
(!a && typeof a === 'object'); // true

值和类型

变量在未持有值的时候为undefined,此时tyoeof返回字符串格式的"undefined"

类型描述
undefined已在作用域中声明但还没有赋值的变量,是 undefined
undeclared还没有在作用域中声明过的变量,是 undeclared 的、是 is not defined
// 例如:
var a;
console.log(a); // undefined
console.log(b); // ReferenceError: b is not defined
// 例如:
var a;
console.log(typeof a); // "undefined"
console.log(typeof b); // "undefined"
  • 注意:
    • undefined 并不等于 b is not defined
    • 如果浏览器的报错换成 b is not found 或者 “b is not declared 才更加准确
    • 对于a``b变量使用typeof均返回"undefined",这是因为typeof有一个特殊的安全防范机制,此时typeof如果能返回 undeclared(而非 undefined)的话,情况会好很多
    • typeof的安全防范机制可用于检查变量是否被定义了
      if(typeof DEBUG !== "undefined") console.log('Hello');
      
    • 访问对象上不存在的字段不会报 ReferenceError 错误,另一种判断就可以是:
      if(window.DEBUG) console.log('Hello');
      

数组

  • delete运算符可以将单元从数组中删除,删除后数组的length并不会改变
    var list = [1, 2, 3];
    delete list[1];
    console.log(list); // (3) [0: 1, 2: 3, length: 3]
    
  • 虽然在Chrome中list[1]等于undefined,但与list[1] = undefined这种赋值的方式还是存在区别的
    • 注意:永远不要创建和使用空单元数组
    // 以下数组存在空单元(不带有索引1和值)
    var list = [1, 2, 3];
    delete list[1];
    console.log(list); // (3) [0: 1, 2: 3, length: 3]
    
    // 以下数组不存在空单元(带有索引1和值)
    var list = [1, 2, 3];
    list[1] = undefined;
    console.log(list); // (3) [0: 1, 1: undefined, 2: 3, length: 3]
    
  • 数组也是对象,所以可以包含字符串类型的key,但这些并不计算在数组长度内(不建议,存放键值建议使用对象)
    var list = [1, 2, 3];
    list.name = '123';
    console.log(list); // (3) [1, 2, 3, name: '123']
    console.log(list.length); // 3
    
  • 当数组中定义的key能被强制转换成十进制数字时,会被当做数字索引值处理
    var list = [1, 2, 3];
    list['5'] = 5;
    console.log(list); // (6) [0: 1, 1: 2, 2: 3, 5: 5, length: 6]
    console.log(list.length); // 6
    
  • 将类数组转换为数组
    function foo(){
        // 类数组
        console.log(arguments); // Arguments(2) ['foo', 'bar', callee: ƒ, Symbol(Symbol.iterator): ƒ]
        
        // 类数组转换为数组,方式一:
        console.log(Array.prototype.slice.call(arguments)); // ['foo', 'bar']
        
        // 类数组转换为数组,方式二:
        console.log(Array.from(arguments)); // ['foo', 'bar']
    }
    foo('foo', 'bar');
    

字符串

字符串不是字符数组,字符串无法借用数组的一些方法

数字

语法

var a = 0.12;
var a = .12;
var b = 0.10;
var b = 0.1;
var c = 1.0;
var c = 1.;
// 指数显示法
var d = 5E8;
d; // 500000000
d.toExponential(); // "5e+8"
// toPrecision 用来指定有效数位的显示位数
var e = 42.59;
e.toPrecision(1); // "4e+1" 
e.toPrecision(2); // "43" 
e.toPrecision(3); // "42.6" 
e.toPrecision(4); // "42.59" 
e.toPrecision(5); // "42.590" 
e.toPrecision(6); // "42.5900"
// tofixed 方法可指定小数部分的显示位数
42.toFixed(2); // SyntaxError: Invalid or unexpected token
(42).toFixed(2); // "42.00"
42 .toFixed(2); // "42.00"
42..toFixed(2); // "42.00"

较小的数值

判断两个数相等

  • 由于js数字精度导致的问题:
    0.1 + 0.2 === 0.3; // false
    
  • 解决方法
    // 兼容写法
    if (!Number.EPSILON) {
        Number.EPSILON = Math.pow(2,-52);
    }
    // 绝对值相减是否小于精度引发的偏差值
    function numbersCloseEnoughToEqual(n1,n2) {
        return Math.abs( n1 - n2 ) < Number.EPSILON;
    }
    numbersCloseEnoughToEqual(0.1 + 0.2, 0.3); // true
    numbersCloseEnoughToEqual(0.1E-10, 0.2E-10); // false
    numbersCloseEnoughToEqual(0.1E-1000, 0.2E-1000); // true 当数字相差过小还是会出现精度问题
    

整数的安全范围

数字的呈现方式决定了“整数”的安全值范围远远小于 Number.MAX_VALUE

Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MAX_SAFE_INTEGER; // 9007199254740991
Number.MIN_SAFE_INTEGER; // -9007199254740991

整数检测

  • 整数检测
    Number.isInteger(42); // true 
    Number.isInteger(42.000); // true 
    Number.isInteger(42.3); // false
    
    // 兼容写法
    if (!Number.isInteger) { 
        Number.isInteger = function(num) { 
            return typeof num == "number" && num % 1 == 0; 
        }; 
    }
    
  • 安全整数检测
Number.isSafeInteger(Number.MAX_SAFE_INTEGER); // true 
Number.isSafeInteger(Math.pow(2, 53)); // false 
Number.isSafeInteger(Math.pow(2, 53) - 1); // true

// 兼容写法
if (!Number.isSafeInteger) { 
    Number.isSafeInteger = function(num) { 
        return Number.isInteger(num) && Math.abs(num) <= Number.MAX_SAFE_INTEGER; 
    }; 
}

特殊的值

不是值的值

undefined 和 null 常被用来表示“空的”值或“不是值”的值。二者之间有一些细微的差别

描述
undefinedundefined 指没有值(missing value); undefined 指从未赋值;
nullnull 指空值(empty value); null 指曾赋过值,但是目前没有值;
  • void 运算符
    void 1; // undefined
    void undefined; // undefined
    

特殊的数字

  • NaN
    NaN === NaN; // false
    typeof NaN; // 'number'
    isNaN(NaN); // true
    
  • 无穷数
    1 / 0; // Infinity
    -1 / 0; // -Infinity
    1 / 0 === Infinity; // true
    -1 / 0 === -Infinity; // true
    Number.MAX_VALUE + 1E1000; // Infinity
    
  • 零值
    • 加法和减法运算不会得到负零
      0 / -3; // -0
      0 * -3; // -0
      -0 === 0; // true
      
    • -0字符串后变为"0"
      var a = 0 / -3;
      a; // -0
      a.toString(); // '0'
      a + ''; // '0'
      JSON.stringify(a); // '0'
      
    • "-0"将字符串转换为数字后变为-0
      +"-0"; // -0
      Number( "-0" ); // -0
      JSON.parse( "-0" ); // -0
      
    • 判断是否为-0(比如用于判断动画方向需要使用0的符号位)
      function isNegZero(n) {
          n = Number( n );
          return (n === 0) && (1 / n === -Infinity);
      }
      isNegZero( -0 ); // true
      isNegZero( 0 / -3 ); // true
      isNegZero( 0 ); // false
      

特殊等式

判断两个值是否绝对相等

Object.is(null, undefined); // false
Object.is(-0, 0); // false
Object.is(-0, -0); // true
Object.is(NaN, NaN); // true
Object.is(NaN, 0 / 'str'); // true

原生函数

new String('abc')'abc'String('abc')

// new String('abc') 
let str1 = new String('abc');
// String {0: "a", 1: "b", 2: "c", length: 3, [[Prototype]]: String, [[PrimitiveValue]]: "abc"}
str1; 
str1.toString(); // 'abc' 
str1.length; // 3
typeof str1; // 'object'
str1 instanceof String; // true
Object.prototype.toString.call(str1); // [object String]

// 'abc'
let str2 = 'abc';
str2; // abc
str2.toString(); // 'abc'
str2.length; // 3
typeof str2; // 'string'
str2 instanceof String; // false
Object.prototype.toString.call(str2); // '[object String]'

// String('abc')
let str3 = String('abc');
str3; // abc
str3.toString(); // 'abc'
str3.length; // 3
typeof str3; // 'string'
str3 instanceof String; // false
Object.prototype.toString.call(str3); // '[object String]'

str1 === str2; // false
str1 == str2; // true

str1 === str3; // false
str1 == str3; // true

str2 === str3; // true
str2 == str3; // true
  • 总结:
    • new String("abc") 创建的是字符串 "abc" 的封装对象,而非基本类型值 "abc"
    • 'abc'String('abc') 一致

内部属性[[CLass]]

认为每个对象的内部都包含一个内部属性[[CLass]],这个属性无法直接进行访问需要通过Object.prototype.toString.call(..)这种方式来查看

// 示例:
let arr = new Array(1, 2, 3);
Object.prototype.toString.call(arr); // '[object Array]'

封装对象包装、拆封

由于基本类型值没有 .length.toString() 这样的属性和方法,需要通过封装对象才能访问,此时 JavaScript 会自动为基本类型值包装(box 或者 wrap)一个封装对象

一般情况下,我们不需要直接使用封装对象。最好的办法是让 JavaScript 引擎自己决定什么时候应该使用封装对象

// 示例:
let str = 'abc';
str.length; // 3
  • 注意:
    • 一般不推荐使用封装对象,比如Boolean
      let a = new Boolean(false);
      if(!a) console.log('abc');
      else console.log('Lee');
      
    • 以上代码最终返回 Lee,并不返回 abc, 因为对象返回
    • 解决办法:
      let a = new Boolean(false);
      if(!a.valueOf()) console.log('abc');
      else console.log('Lee');
      
    • 提示:如果想要浏览器自行的去封装基本类型的值,可以使用 Object(a)
      let a = Object('abc'); 
      a; // String {'abc'}
      a.valueOf(); // abc
      
      let b = Object(12345); 
      b; // Number {12345}
      b.valueOf(); // 12345
      
      let c = Object(false); 
      c; // Boolean {false}
      c.valueOf(); // false
      
      let d = Object([1, 2]); 
      d; // (2) [1, 2]
      d.valueOf(); // (2) [1, 2]
      

原生函数作为构造函数

关于数组(array)对象(object)函数(function)正则表达式,我们通常喜欢以常量的形式来创建它们。实际上,使用常量和使用构造函数的效果是一样的(创建的值都是通过封装对象来包装)

Array(..)

  • 构造函数 Array(…) 不要求必须带 new 关键字。不带时,它会被自动补上。因此 Array(1,2,3) 和 new Array(1,2,3) 的效果是一样的。
    var a = new Array( 1, 2, 3 );
    a; // [1, 2, 3]
    
    var b = [1, 2, 3];
    b; // [1, 2, 3]
    
  • 重点:永远不要创建和使用空单元数组

Object(..)Function(..)RegExp(..)

除非万不得已,否则尽量不要使用 Object(..)/Function(..)/RegExp(..)

不要把 Function(..) 当作 eval(..) 的替代品,你基本上不会通过这种方式来定义函数

Date(..)Error(..)

Date(..)Error(..) 没有对应的常量形式来作为他们的替代

错误对象通常与 throw 一起使用

function foo(x) {
    if (!x) {
        let err = new Error( "x wasn’t provided" );
        console.log({err});
        throw err;
    }
}
foo();

Error(..) 之外,还有一些针对特定错误类型的原生构造函数,如 EvalError(..)RangeError(..)ReferenceError(..)SyntaxError(..)TypeError(..)URIError(..)。这些构造函数很少被直接使用,它们在程序发生异常(比如试图使用未声明的变量产生 ReferenceError 错误)时会被自动调用。

符号-Symbol(..)

符号是具有唯一性的特殊值(并非绝对),用它来命名对象属性不容易导致重名

符号可以用作属性名,但无论是在代码还是开发控制台中都无法查看和访问它的值,只会显示为诸如 Symbol(Symbol.create) 这样的值

符号并非对象,而是一种简单标量基本类型

var obj = {}
obj[Symbol.iterator] = function (a){return a+1;}

obj[Symbol(Symbol.iterator)]; // TypeError: Cannot convert a Symbol value to a string

obj['Symbol(Symbol.iterator)']; // undefined

Object.getOwnPropertySymbols(obj); // [Symbol(Symbol.iterator)]

obj[Object.getOwnPropertySymbols(obj)[0]]; // ƒ (a){return a+1;}

通过 Object.getOwnPropertySymbols(..) 便可以公开获得对象中的所有符号

很多开发人员喜欢用它来替代有下划线(_)前缀的属性,而下划线前缀通常用于命名私有或特殊属性

原生原型

  • 将原型作为默认值,对未赋值的变量来说,它们是很好的默认值
    // Function.prototype 是一个空函数
    Function.prototype; // ƒ () { [native code] }
    let a;
    let fun = a || Function.prototype;
    
    // RegExp.prototype 是一个“空”的正则表达式(无任何匹配)
    RegExp.prototype; // /(?:)/
    let a;
    let reg = a || RegExp.prototype;
    
    // Array.prototype 是一个空数组
    Array.prototype; // []
    let a;
    let arr = a || Array.prototype;
    

强制类型转换

值类型转换

显式类型转换(显式强制类型转换):将值从一种类型显示转换为另一种类型;

强制类型转换(隐式强制类型转换): 将值从一种类型隐式转换为另一种类型;

let a = 123;

let b = a + '';     // 隐式强制类型转换

let c = String(a);  // 显式强制类型转换

注意: 理解上,显式隐式 并没有明确的分界线,理解其含义那么它就是显式,不理解其意它也可以是隐式

抽象值操作

ToString

toString() 可以被显式调用,或者在需要字符串化时自动调用

  • 对于极大或极小的值,toString方法在转换时会将数字转换为指数形式
    let num = 0.123 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
    num.toString(); // 1.23e+23
    
  • 对普通对象来说,除非自行定义,否则 toString()Object.prototype.toString())返回内部属性 [[Class]] 的值,如 "[object Object]"
    Object.prototype.toString(); // '[object Object]'
    
  • 数组的默认 toString() 方法经过了重新定义,将所有单元字符串化以后再用 “,” 连接起来
    var a = [1,2,3];
    a.toString(); // "1,2,3"
    
  • 安全的JSON值:
    • 所有安全的 JSON 值(JSON-safe)都可以使用 JSON.stringify(..) 字符串化
    • 安全的JSON 值是指能够呈现为有效 JSON 格式的值
  • 不安全的JSON值:
    • undefinedfunctionsymbol包含循环引用(对象之间相互引用,形成一个无限循环)的对象
    JSON.stringify(undefined); // undefined
    JSON.stringify(function(){}); // undefined
    JSON.stringify(Symbol); // undefined
    JSON.stringify(Symbol.iterator); // undefined
    JSON.stringify([1, undefined, function(){}, 4]); // '[1,null,null,4]'
    JSON.stringify({a: 2, b: function(){}}); // '{"a":2}'
    
    // 对包含循环引用的对象执行 JSON.stringify(..) 会出错
    let a = {};
    let b = {a};
    a.b = b;
    JSON.stringify(b); // TypeError: Converting circular structure to JSON
    
  • 如果对象中定义了 toJSON() 方法,JSON 字符串化时会首先调用该方法,然后用它的返回值来进行序列化
    • toJSON() 应该“返回一个能够被字符串化的安全的 JSON 值”,而不是“返回一个 JSON 字符串”
    let obj = {
        name: 'Lee',
        toJSON(){
            return {name1: this.name};
        } 
    }
    JSON.stringify(obj); // '{"name1":"Lee"}'
    
  • 所以 深拷贝JSON.parse(JSON.stringify(...)) 本质上不一样
  • JSON.stringify()可以接收3个参数
    JSON.stringify(
        // 需要序列化的数据
        value: any,
        /**
         * 用来指定对象序列化过程中哪些属性应该被处理,哪些应该被排除,和 toJSON() 很像
         *      数组:必须是一个字符串数组,其中包含序列化要处理的对象的属性名称,除此之外其他的属性则被忽略
         *      函数:它会对对象本身调用一次,然后对对象中的每个属性各调用一次,每次传递两个参数,键和值。如果要忽略某个键就返回 undefined,否则返回指定的值
         */ 
        replacer?: (key: string, value: any) => any, 
        // 缩进
        space?: string | number
    ): string
    
    • replacer数组示例:

      var obj = {
          a: 123,
          b: "123",
          c: [1, 2, 3],
          d: {
              a: 123,
              b: "123",
              c: [1, 2, 3],
          }
      };
      let json1 = JSON.stringify(obj, ['a', 'b', 'd'], 1);
      /**
       * {
       *  "a": 123,
       *  "b": "123",
       *  "d": {
       *   "a": 123,
       *   "b": "123"
       *  }
       * }
       */
      console.log(json1);
      
    • replacer函数示例:

      var obj = {
          a: 123,
          b: "123",
          c: [1, 2, 3],
          d: {
              a: 123,
              b: "123",
              c: [1, 2, 3],
          }
      };
      let json1 = JSON.stringify(obj, (k, v)=>{
          console.log('【key】', k, '【value】', v);
          return v;
      }, null);
      // {"a":123,"b":"123","c":[1,2,3],"d":{"a":123,"b":"123","c":[1,2,3]}}
      console.log(json1);
      
    • space缩进示例:

      var obj = {
          b: 123,
          c: "123",
          d: [1, 2, 3]
      };
      let json1 = JSON.stringify(obj, null, 4);
      /**
       * {
       *     "b": 123,
          *     "c": "123",
          *     "d": [
          *         1,
          *         2,
          *         3
          *     ]
          * }
          */
      console.log(json1);
      
      let json2 = JSON.stringify(obj, null, '--');
      /**
       * {
       * --"b": 123,
          * --"c": "123",
          * --"d": [
          * ----1,
          * ----2,
          * ----3
          * --]
          * }
          */
      console.log(json2);
      

ToNumber

Number(true); // 1
Number(false); // 0
Number(undefined); // NaN
Number(null); // 0
Number(''); // 0
Number([]); // 0
Number(['a', 1]); // NaN
Number([1, 2, 3]); // NaN
Number({}); // NaN
  • ToNumber 对以 0 开头的十六进制数并不按十六进制处理(而是按十进制)
    Number(0x10); // 16 解析成十进制
    Number(010); // 8 解析成十进制
    

为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会

首先(通过内部操作 DefaultValue)检查该值是否有 valueOf() 方法。

如果有并且返回基本类型值,就使用该值进行强制类型转换。

如果没有就使用 toString()的返回值(如果存在)来进行强制类型转换。

如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。

Number({ valueOf(){ return '123' } }); // 123
Number({ valueOf(){ return 'abc' } }); // NaN
Number({ valueOf(){ return '' } }); // 0

let a = [1, 2, 3];
a.valueOf = () => '111';
a.toString = () => '456';
Number(a); // 111

let a = [1, 2, 3];
a.toString = () => '456';
Number(a); // 456

ToBoolean

  • 假值(可以被强制类型转换为false的值)
    • undefined null false +0 -0 NaN '' ""
  • 真值(真值就是假值列表之外的值)

显式强制类型转换

字符串和数字之间的显式转换

  • 日期对象显式转换为时间戳
    let timestamp = new Date();
    +timestamp; // 时间戳
    
  • ~运算符(字位运算符)(不推荐使用,难以理解)
    • ~x 大致等同于 -(x+1)大致等同于 不等于 等于
    let arr = [1, 2, 3];
    
    // arr.indexOf(2); // 1
    // ~arr.indexOf(2); // -2
    
    // arr.indexOf(4); // -1
    // ~arr.indexOf(4); // 0
    
    function isOfData1(x){
        if(arr.indexOf(x) !== -1)
            console.log(x, '存在');
        else
            console.log(x, '不存在');
    }
    
    function isOfData2(x){
        if(~arr.indexOf(x))
            console.log(x, '存在');
        else
            console.log(x, '不存在');
    }
    
    isOfData1(2); // 2 '存在'
    isOfData2(2); // 2 '存在'
    
    isOfData1(4); // 4 '不存在'
    isOfData2(4); // 4 '不存在'
    
  • 字位截除
    ~~12.123; // 12
    

显式解析数字字符串

  • parseInt只支持字符串和数字类型的参数,尽量不要传入非这两种类型的参数
    Number({ valueOf: _ => 123 }); // 123
    parseInt('123'); // 123
    parseInt({ valueOf: _ => 123 }); // NaN
    parseInt({}); // NaN
    
  • parseInt
    • 如果没有第二个参数来指定转换的数(又称为 radix),parseInt(…) 会根据字符串的第一个字符来自行决定基数
      parseInt('0x10'); // 16
      parseInt('0x10', 10); // 0
      
    • 解析非字符串
      parseInt(1/0, 19);      // 18
      parseInt(Infinity, 19); // 18
      
    • parseInt 一些奇怪的返回值
      parseInt(0.000008);     // 0    ("0" 来自于 "0.000008")
      parseInt(0.0000008);    // 8    ("8" 来自于 "8e-7")
      parseInt(false, 16);    // 250  ("fa" 来自于 "false")
      parseInt(parseInt, 16); // 15   ("f" 来自于 "function..")
      parseInt("0x10");       // 16
      parseInt("103", 2);     // 2
      
  • 注意:合理使用 parseInt

隐式强制类型转换

隐式强制类型转换的作用是减少冗余,让代码更简洁

字符串和数字之间的隐式强制类型转换

let a = [1, 2];
let b = [3, 4];
a + b; // "1,23,4"

var a = {
    valueOf: function() { return 42; },
    toString: function() { return 4; }
}
a + ''; // '42'
String(a); // '4'

var a = '123';
a + 0; // '1230'
0 + a; // '0123'
a - 0; // 123
0 - a; // -123

{} + []; // 0
[] + {}; // '[object Object]'

隐式强制类型转换为布尔值

相对布尔值,数字和字符串操作中的隐式强制类型转换还算比较明显

  • 下面的情况会发生布尔值隐式强制类型转换:
    • if (..) 语句中的条件判断表达式
    • for ( .. ; .. ; .. ) 语句中的条件判断表达式(第二个)
    • while (..)do..while(..) 循环中的条件判断表达式
    • ? : 中的条件判断表达式
    • 逻辑运算符 ||(逻辑或)&&(逻辑与)左边的操作数(作为条件判断表达式)

|| 和 &&

短路语句

  • && : 返回第一个为假的表达式,否则返回最后一个表达式
  • || : 返回第一个为真的表达式,否则返回最后一个表达式

符号(Symbol)的强制类型转换

从符号到字符串的显式强制类型转换

从符号到字符串的隐式强制类型转换会产生错误

var s1 = Symbol( "cool" );
String( s1 ); // "Symbol(cool)"
var s2 = Symbol( "not cool" );
s2 + ""; // TypeError: Cannot convert a Symbol value to a string

宽松相等(==) 和 严格相等(===)

推荐 === ,原因:严格

相等比较操作的性能

有人觉得 == 会比 === 慢,实际上虽然强制类型转换确实要多花点时间,但仅仅是微秒级(百万分之一秒)的差别而已。

如果进行比较的两个值类型相同,则 ===== 使用相同的算法,所以除了 JavaScript 引擎实现上的细微差别之外,它们之间并没有什么不同。

如果两个值的类型不同,我们就需要考虑有没有强制类型转换的必要,有就用 ==,没有就用 ===,不用在乎性能

抽象相等

  • 字符串和数字之间的相等比较
    • (1) 如果 Type(x) 是数字,Type(y) 是字符串,则返回 x == ToNumber(y) 的结果;
    • (2) 如果 Type(x) 是字符串,Type(y) 是数字,则返回 ToNumber(x) == y 的结果;
  • 其他类型和布尔类型之间的相等比较
    • (1) 如果 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果;
    • (2) 如果 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果;
  • nullundefined 之间的相等比较
    • (1) 如果 xnullyundefined,则结果为 true;
    • (2) 如果 xundefinedynull,则结果为 true;
  • 对象和非对象之间的相等比较(ToPrimitive 抽象操作特性(如 toString()valueOf()))
    • (1) 如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果;
    • (2) 如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPrimitive(x) == y 的结果;

比较少见的情况

  • 返回其他数字
    Number.prototype.valueOf = function() {
        return 3;
    };
    new Number(2) == 3; // true
    
  • 假值的相等比较
    "0" == null;            // false
    "0" == undefined;       // false
    "0" == false;           // true -- 晕!
    "0" == NaN;             // false
    "0" == 0;               // true
    "0" == "";              // false
    false == null;          // false
    false == undefined;     // false
    false == NaN;           // false
    false == 0;             // true -- 晕!
    false == "";            // true -- 晕!
    false == [];            // true -- 晕!
    false == {};            // false
    "" == null;             // false
    "" == undefined;        // false
    "" == NaN;              // false
    "" == 0;                // true -- 晕!
    "" == [];               // true -- 晕!
    "" == {};               // false
    0 == null;              // false
    0 == undefined;         // false
    0 == NaN;               // false
    0 == [];                // true -- 晕!
    0 == {};                // false
    
  • 极端情况
    [] == [];               // false
    [] == ![];              // true
    2 == [2];               // true
    "" == [null];           // true
    0 == "\n";              // true
    42 == "43";             // false
    "foo" == 42;            // false
    "true" == true;         // false
    42 == "42";             // true
    "foo" == [ "foo" ];     // true
    
  • 安全运用隐式强制类型转换
    • 如果两边的值中有 true 或者 false,千万不要使用 ==
    • 如果两边的值中有 []"" 或者 0,尽量不要使用 ==

抽象关系比较

  • 双方首先调用 ToPrimitive,如果结果出现非字符串,就根据 ToNumber 规则将双方强制类型转换为数字来进行比较
    var a = [ 42 ];
    var b = [ "43" ];
    
    a < b; // true
    b < a; // false
    
  • 比较双方都是字符串,则按字母顺序来进行比较
    var a = [ "42" ];
    var b = [ "043" ];
    a < b; // false
    
  • 在js中<=不解释为小于等于,而是解释为不大于
    var a = { b: 42 };
    var b = { b: 43 };
    
    a < b;  // false
    a == b; // false
    a > b;  // false
    
    a <= b; // true
    a >= b; // true
    

语法

语句和表达式

语句不是表达式

语句相当于“句子”,而表达式相当于“短语”。“句子”由多个“短语”组成。

let a; // 声明语句
a = 3 * 2; // 赋值表达式
a; // 表达式语句

语句的结果值

语句都有一个结果值, undefined 也算

var a, b;
a = if (true) {b = 4 + 38;}; // SyntaxError: Unexpected token 'if'

// eval 不推荐
var a, b;
a = eval( "if (true) { b = 4 + 38; }" );
a; // 42

表达式的“副作用”

  • a++ 首先返回变量 a 的当前值 42(再将该值赋给 b),然后将 a 的值加 1
    var a = 42;
    var b = a++;
    a; // 43
    b; // 42
    
  • a++++a
    var a = 42;
    a++;    // 42
    a;      // 43
    ++a;    // 44
    a;      // 44
    

上下文规则

  • 标签语句(不推荐使用)
    // obj对象
    var obj = {
        foo: () => 123
    }
    
    • 当去掉 var obj = 去掉后,并不会报错,而是变成了一个标签语句,如下:
      // 代码块
      {
          /**
           * 标签语句
          *  结果:
          *      0 0
          *      1 0
          *      2 0
          */
          foo: for (let i = 0; i < 3; i++) {
              for (let j = 0; j < 3; j++) {
                  console.log(i, j);
                  continue foo;
              }
          }
      }
      
  • 标签语句用于非循环代码块(但只有 break 才可以)
    (()=>{
        foo: {
        console.log('Hello');
            break foo;
            console.log(234);
        }
        console.log('Lee');
    })()
    // Hello
    // Lee
    
  • 代码块
    • [] + {};{} + [];
      [] + {}; // '[object Object]' 原因:[]被处理成了'',而{}在表达式中被处理成了空对象,所以输出'[object Object]'
      {} + []; // 0 原因:{}被看作了代码块,而[]被处理成了'',故+[]为0
      
  • 对象结构
    var {a, b} = {a: 123, b: 456};
    var {a: a, b: b} = {a: 123, b: 456};
    var {a: c, b: d} = {a: 123, b: 456};
    var [a, b] = [123, 456];
    

函数参数

  • 参数默认值
    function foo(a = 1, b = a + 1) {
        return a + b;
    }
    foo();              // 3
    foo(2);             // 5
    foo(2, 2);          // 4
    foo(undefined, 1);  // 2
    foo(void 123, 1);   // 2
    foo(null, 1);       // 1
    

try..finally

try {
    foo();
} catch (error) {
    console.log(error); // ReferenceError: foo is not defined
} finally {
    console.log('Hello Lee!'); // Hello Lee!
}
  • finally 中存在 return 会覆盖掉 trycatch 中的 return 返回值
    function foo(){
        try {
            // return bar();
            return 'abc';
        } catch (error) {
            console.log(error);
            return 'error';
        } finally {
            return 123;
        }
    }
    console.log(foo()); // 123
    
  • finally 和带标签break 混合使用
    function foo(){
        bar: {
            try {
                return 'abc';
            } catch (error) {
                console.log(error);
                return 'error';
            } finally {
                break bar;
            }
        }
    
        console.log('Hello');
    
        return 'World';
        
    }
    console.log(foo()); // Hello World
    

switch

  • switch (a) 执行的是 === 判断
  • switch (true) 可以自定义执行对变量的比较操作
var a = '10';

// a 为 10 or '10'
switch (true) {
    case a == 20:
        console.log(`a 为 20 or '20'`);
        break;
    case a == 10:
        console.log(`a 为 10 or '10'`);
        break;
    default:
        console.log('没有分析出a的值');
        break;
}

// 没有分析出a的值
switch (a) {
    case 20:
        console.log(`a 为 20 or '20'`);
        break;
    case 10:
        console.log(`a 为 10 or '10'`);
        break;
    default:
        console.log('没有分析出a的值');
        break;
}

混合环境JavaScript

全局DOM变量

由于浏览器演进的历史遗留问题,在创建带有id属性的DOM元素时也会创建同名的全局变量

<div id="foo"></div>
<script>
    console.log(foo); // HTML元素
</script>

<script>

多个 <script> 标签下的代码可以互相调用,但前提是需要保证前后顺序

<script>foo(); // ReferenceError: foo is not defined</script>
<script>function foo(){};</script>
<script>console.log(foo()); // undefined</script>
console.log(foo()); // undefined
function foo(){};

保留字

保留字不能用作变量名(包括四类:关键字 预留关键字 null常量 true/false布尔常量

显示全文