weixin_39621060
weixin_39621060
2020-12-02 05:28

You-Dont-Know-JS笔记之类型和语法

You Don't Know JS: Types & Grammar

JavaScript的类型和语法

第一章:类型(Types)

很多开发者认为动态语言没有类型。但ES5规范定义:

此规范内的算法在处理每个值时都有一个关联的类型。可能的值类型都定义在这个条款中。类型可以进一步分为 ECMAScript 语言类型和规范类型。 ECMAScript 语言类型和使用ECMAScript语言的程序员处理的值相符。ECMAScript语言类型有:Undefined, Null, Boolean, String, Number, 和 Object。

内置类型

内置类型有:nullundefinedbooleannumberstringobjectsymbol(ES6新加)。

除了object都是基础类型(primitives)。

typeof操作符检查给定操作数的类型。类型是undefinedbooleannumberstringobjectsymbolfunction七种中的一个。 1. 为什么没有null

typeof null; // 'object',这是个浏览器的bug,null不是对象。 2. 为什么有function

typeof function a(){ /* .. */ } === "function"; // truefunction是JS内置的顶级类型之一,也是对象(的子类型),可以调用的对象。

值类型(Values as Types)

在JS中,变量(variables)没有类型——值有类型。变量可以在任何时候有任何值。

换一种方法理解JS类型:JS没有强制类型,引擎不要求变量总是存储与初始化时相同类型的值。

undefined vs "undeclared"

当前没有值的变量,其实是当前值为undefined。两者区别是: - undefined的变量在当前可访问作用域里已经声明了,只是当前没有值; - "undeclared"的变量在当前可访问作用域没有正式声明。

typeof undeclared

对未声明的变量执行typeof得到"undefined",这可能会造成一点混淆。但这是安全的检测未声明变量的方法。

 js
var declared;
typeof declared; // "undefined"
typeof undeclared; // "undefined"
if(undeclared){} // Uncaught ReferenceError: undeclared is not defined

第二章:值(Values)

数组(Arrays)

数组就是数值索引的任何类型值的集合。

数组不需要你提前定义长度。delete会删除对应位置的值,但即使你delete了所有值,数组的长度不会变化。这样的数组是稀疏数组("sparse" array),即留下或创建了空槽。

注意,稀疏数组看起来是索引对应的值为undefined,但这和显示设置arr[index] = undefined不同

数组是数值索引的,但同时它是对象,所以可以有字符串键值对。一般,你设置字符串属性时,不会影响length,但如果这个key可以转换成十进制数字时,会假设你想使用数值索引:

 js
var a = [ ];

a['key'] = 'value';
a.length; // 0

a['13'] = 42;
a.length; // 14, a: [undefined × 13, 42]

类数组

类数组可以通过Array.prototype.slice.callArray.from(ES6)来转换成数组。

 js
Array.prototype.slice.call({length: 2}) // [undefined × 2]

字符串(Strings)

认为字符串就是字符数组的想法很常见。但不管字符串的底层实现是否使用数组,字符串与数组有很多不同,相似只是表面的。

尽管字符串和数组有indexOflength等等相似属性,但注意:JS字符串是不可变的(immutable),而数组是可变的。

更进一步,字符串的不可变性:没有一个字符串方法可以就地改变字符串的内容,相反,这些方法都创建并返回一个新字符串。而数组的许多方法可以改变数组本身的内容。

数字(Numbers)

JS只有一个数值类型:number。这个类型包括"整数"和小数。"整数"之所以有引号是因为JS并不像其它语言有真的整数。

所以,在JS中,"整数"就是没有小数部分的数字:42.042一样是"整数"。

像大多数现代语言,包括实际上所有脚本语言,JS的number基于IEEE 754标准,常称为"浮点数"。JS尤其使用了标准的双精度(double precision)格式(64位二进制)。

数字语法(Numeric Syntax)

JS中数字通常用十进制表示:

 js
var b = 42.3;
b = 0.42;
b = .42; // 十进制开始部分如果是0可以省略
b = 42.0;
b = 42. ;// 十进制结束部分如果是0可以省略
b = 42.300; b; // 42.3 尾部多余的0通常被移除
b = 42.0; b; // 42

很大或很小的数字一般以指数形式输出,等同于toExponential()方法的输出:

 js
var a = 5E10;
a;                  // 50000000000
a.toExponential();  // "5e+10"

var b = a * a;
b;                  // 2.5e+21

var c = 1 / a;
c;                  // 2e-11

toFixed(..)可以指定小数部分的输出位数(0-20)。toPrecision(..)指定显示数字时有效数字的个数(1-21)。

 js
var a = 42.59;

a.toFixed( 0 ); // "43"
a.toFixed( 1 ); // "42.6"
a.toFixed( 2 ); // "42.59"
a.toFixed( 3 ); // "42.590"

a.toPrecision( 1 ); // "4e+1"
a.toPrecision( 2 ); // "43"
a.toPrecision( 3 ); // "42.6"
a.toPrecision( 4 ); // "42.59"
a.toPrecision( 5 ); // "42.590"

注意数字的.点操作符。因为点是有效的数字字符,所以它首先被解释为数字的一部分,而不是属性访问。

 js
// invalid syntax:
42.toFixed( 3 );    // SyntaxError

// these are all valid:
(42).toFixed( 3 );  // "42.000"
0.42.toFixed( 3 );  // "0.420"
42..toFixed( 3 );   // "42.000"
42 .toFixed(3); // "42.000"

数字可以以指数形式定义,如1e3。可以16进制定义,0xf3。可以8进制定义,0363

注意,ES6+ strict模式下,8进制的0363不在允许。但ES6允许两种新形式:0o363-8进制,0b11110011-2进制。

小的数字(Small Decimal Values)

使用二进制浮点数(使用IEEE 754的所有语言)的最著名副作用是:

 js
0.1 + 0.2 === 0.3; // false

简单说,0.10.2的二进制浮点表示都不是精确的,所以相加后不是0.3,接近(不等于)0.30000000000000004

所以,比较数字时,应该有个宽容值。ES6中这个宽容值被预定义了:Number.EPSILON

安全的整数范围(Safe Integer Ranges)

由于数字的表示方法,整数肯定有个安全范围,并且肯定小于Number.MAX_VALUE。整数的最大安全值是2^53 - 1,即9007199254740991,最小安全值是-9007199254740991,分别被定义在Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER

我们通常会遇到数据库的64位ID值,由于64位数字无法被JS数字表示,所以必须用字符串表示。

测试整数

Number.isInteger(..)测试是否是整数。Number.isSafeInteger(..)测试是否安全的整数。

32位(有符号)整数

安全的整数可以到53位(二进制),但很多数字操作(如二进制操作符)只支持32位,所以整数的安全范围可能更小。

a | 0可以把数字强制转换为32位有符号整数,因为|二进制操作符只对32位整数有效。

注意:NaNInfinity当然不是安全的整数,但二进制操作符要工作的话首先会把它们转换成+0Infinity | 0 // => 0

特殊值(Special Values)

不是值的值(The Non-value Values)

undefined类型的值有且只有undefined一个。null类型的值有且只有null一个。

undefinednull通常被用来当作可互换的空值或非值。可以这么区分: - null是空值(empty value);undefined是无值(missing value)。 - null有值但不做任何事;undefined还没有值。

undefined可以做标识符

非严格模式下,可以向全局的undefined赋值。严格与非严格模式下,都可以定义叫undefined的变量。但这么做是会被打的。

 js
function foo() {
    undefined = 2; // really bad idea!
}
foo();

function bar() {
    "use strict";
    var undefined = 2;
    console.log( undefined ); // 2
}
bar();

void操作符

void操作符可以生成undefined值,void 42;//undefined

特殊数字(Special Numbers)

NaN

NaN--Not a number。NaN是一个哨兵值,表示数字范围内的一种错误情况。

NaN不等于任何值,包括自己。一般用isNaN来测试是否是NaN,但:

 js
window.isNaN(2 / "foo"); // true
window.isNaN("foo"); // true -- ouch!

Infinities

 js
1 /0; // Infinity
-1 / 0; // -Infinity (1 / -0)
Infinity / Infinity; // NaN (Infinity / -Infinity)

如果一个操作如加法产生太大而难以表示的数字,IEEE 754舍入到最近值("round-to-nearest")的模式指定值。

 js
var a = Number.MAX_VALUE;   // 1.7976931348623157e+308
a + a;                      // Infinity
a + Math.pow( 2, 970 );     // Infinity Number.MAX_VALUE + Math.pow( 2, 970 )与Infinity更近
a + Math.pow( 2, 969 );     // 1.7976931348623157e+308  Number.MAX_VALUE + Math.pow( 2, 969 )与Number.MAX_VALUE更近

Zeros

JS中有0-0。除了-0的显示写法,-0一般从特殊算数运算中得来,如0 / -30 * -3。加减运算不会产生-0

最近浏览器控制台才输出(揭示)-0,但字符串化-0只会得到0,根据规范。

 js
var a = 0 / -3;

// (some browser) consoles at least get it right
a;                          // -0

// but the spec insists on lying to you!
a.toString();               // "0"
a + "";                     // "0"
String( a );                // "0"

// strangely, even JSON gets in on the deception
JSON.stringify( a );        // "0"

有趣的是,相反操作(从字符串到数字)不会说谎:

 js
+"-0";              // -0
Number( "-0" );     // -0
JSON.parse( "-0" ); // -0

比较操作也说谎,即0等于-0

第三章:Natives

常用的原生对象有:String()Number()Boolean()Array()Object()Function()RegExp()Date()Error()Symbol()

可以看出,这些原生对象其实是内置函数。

Internal [[Class]]

typeof结果为object的值额外有个[[Class]]属性来标记(可看做内部分类)。这个属性无法直接访问,可通过Object.prototype.toString(..)获取。

而对基础类型的值来说:

 js
Object.prototype.toString.call( null );         // "[object Null]"
Object.prototype.toString.call( undefined );    // "[object Undefined]"

Object.prototype.toString.call( "abc" );    // "[object String]"
Object.prototype.toString.call( 42 );       // "[object Number]"
Object.prototype.toString.call( true );     // "[object Boolean]"

nullundefined来说,尽管没有Null()Undefined(),但内部[[Class]]的值暴露了"Null""Undefined"

对其它基础类型来说,输出的是它对应包装对象的[[Class]]

Boxing Wrappers

基础类型没有属性或方法,但JS自动包装基础类型的值,但你尝试访问属性或方法时。

特意手动创建包装对象来访问属性方法是不必要的,看起来JS不用去包装了,但浏览器很久以前就对这些常见情况优化了,手动创建反而会拖慢程序。

包装对象的陷阱(Object Wrapper Gotchas)

 js
!new Boolean( false ); // false
typeof new String('a'); // object 注意,String前需要new
Object('a') instanceof String; // true 注意,Object前的new可以省略

拆箱(Unboxing)

使用valueOf()来获取包装对象对应的基础类型值。

 js
new String( "abc" ).valueOf() // "abc"

另外拆箱可以隐式发生,如new String( "abc" ) + ''。这个(类型转换)会在第四章讲。

Natives as Constructors

对于arrayobjectfunction,和正则来说,更常用的是它们的字面值形式。

就像上面看到的其它原生对象,这些构造函数形式一般要避免,因为构造函数可能带来陷阱。

Array(..)

  • Array可以不加newArray(1,2,3)返回[1, 2, 3]
  • Array的参数是一个数字时,当成数组长度。此时会创建稀疏数组。
  • 稀疏数组的map陷阱。
 js
var a = new Array( 3 ); // [undefined × 3]
var b = [ undefined, undefined, undefined ]; // [undefined, undefined, undefined]
var c = [];
c.length = 3; // [undefined × 3]

ac是稀疏数组,它们一些情况下和b表现一致,然后其它情况和b不一样。

 js
a.join( "-" ); // "--"
b.join( "-" ); // "--"

a.map(function(v,i){ return i; }); // [ undefined x 3 ]
b.map(function(v,i){ return i; }); // [ 0, 1, 2 ]

怎么显式创建填充undefined的数组(非手动)?Array.apply( null, { length: 3 } )apply会把第二个参数当作(类)数组,这就是魔法所在。

Object(..), Function(..), and RegExp(..)

Object(..)/Function(..)/RegExp(..)构造函数都是可选的,也最好不用。

Function(..)有时很有用,比如你想动态定义采数和函数体。但不要把Function(..)当做eval的替代。

Date(..) and Error(..)

Date(..)Error(..)很有用,因为没有对应的字面值形式。

Symbol(..)

Symbol可以用作属性名。但一般你无法访问或看到symbol的真实值。

ES6预定义了一些symbol,如Symbol.createSymbol.iterator

Native Prototypes

内置原生对象构造函数都有自己的.prototype对象。这些.prototype对象包含原生对象独特的行为。

Prototypes As Defaults

Function.prototype是空函数。

RegExp.prototype是空正则(不匹配任何字符串)。

Array.prototype是空数组。

这些都是很好的默认值。

该提问来源于开源项目:creeperyang/blog

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

8条回答

  • weixin_39809175 weixin_39809175 4月前

    typeof null === 'object' 并不是浏览器bug image

    点赞 评论 复制链接分享
  • weixin_39621060 weixin_39621060 4月前

    设置成typeof null === 'object'肯定是有原因的,不过如果说是一个bug,也不过分。

    点赞 评论 复制链接分享
  • weixin_39621060 weixin_39621060 4月前

    -Chen 这是规范里面定义的一个抽象操作。

    ToPrimitive ( input [, PreferredType] ),对 object 执行该操作时,有两种情况:

    1. PreferredTypestring,则按"toString", "valueOf"的顺序去拿到第一个非 object 的值作为 primitive。
    2. PreferredTypenumber|default,则按"valueOf", "toString"的顺序去拿到第一个非 object 的值作为 primitive。

    比如对 object 的 ToNumber 操作就是执行:

    
    1. Let primValue be ToPrimitive(argument, hint Number).
    2. Return ToNumber(primValue).
    

    下面是一个详细例子,比如我们知道 == 比较时,如果一个是数字而另一个是对象,那么会对对象执行ToPrimitive操作:

    2017-05-14 6 05 40

    我想这个例子足够讲清楚了,更多信息直接看ES6 Spec: ToPrimitive

    点赞 评论 复制链接分享
  • weixin_39770226 weixin_39770226 4月前

    有点不懂,这个操作是内部运行的?怎么可以调用它,也就是设置preferredType。

    谢谢博主的耐心解答,上楼我是懂得。是我没有表达清楚,我只是在纠结PreferredType这个可选参数在内部是怎么设置的,就比如上面博主举的例子,就是默认的情况,也就是先执行valueOf再执行toString,我不知道什么情况下才是先执行toString,再执行valueOf(不知道我的问题是不是比较蠢O(∩_∩)O哈哈~)。 像下面

    js
    var a = {
      toString: function () {
        console.log('toString');
        return '1'
      },
      valueOf: function () {
        console.log('valueOf');
        return {x: 1}
      }
    //   valueOf: function () {
    //     console.log('valueOf');
    //     return 1
    //   }
    }
    '1' == a//都是先执行valueOf,再执行toString
    

    哦哦哦,我知道了。

    js
    var a = {
      toString: function () {
        console.log('toString');
        return {}
      },
      valueOf: function () {
        console.log('valueOf');
        return '1'
      }
    }
    parseInt(a) // toString  valueOf  1
    

    在这里时就是先执行toString()在执行valueOf()

    点赞 评论 复制链接分享
  • weixin_39621060 weixin_39621060 4月前

    -Chen 这是规范层面的描述,实际实现(JS引擎)可能并没有类似的方法。即使有,也是内部运行的,JavaScript层也不会有类似的接口。

    点赞 评论 复制链接分享
  • weixin_39796868 weixin_39796868 4月前

    在用 ES6 的话就不要再写

    javascript
    Array.apply(null, { length: 3 });
    

    这样的代码了,因为有更优雅的实现方式,比如

    javascript
    Array(3).fill(void 0);
    Array.from({ length: 3 });
    
    点赞 评论 复制链接分享
  • weixin_39621060 weixin_39621060 4月前

    第四章:Coercion

    值的类型转换(Converting Values)

    把值从一个类型转为另一个类型,通常称为类型转换("type casting"),可以是显式的,也可以是隐式的(由值怎么使用的规则强制)。

    注意:虽然不明显,但类型转换的结果总是生成基础类型的值。包装不是严格意义的类型转换。

    抽象值操作(Abstract Value Operations)

    在分辨显式隐式转换前,首先了解控制转换的基本规则。ES5规范的第九章定义了一些抽象操作(也叫 "internal-only operation"),关于转换规则。我们关注ToString,ToNumber,ToBoolean, ToPrimitive4个。

    ToString

    非字符串转换成字符串,就由ToString处理。

    内置基础类型有规定的转换规则:null-->"null"undefined-->"undefined"true-->"true"。数字就是像我们期待那样, 但很小或很大的数字是以指数形式。

    对一般对象来说,除非你指定了你自己的,默认的toString()(位于Object.prototype.toString())会返回[[Class]](第三章),例如"[object Object]"

    注意:对象转换为字符串需要经过ToPrimitive,这会在ToNumber段细讲,这里跳过。

    JSON Stringification

    JSON.stringify(..)看起来和ToString相关,但注意,这和类型转换不是一回事。

    对大多数基础值来说,JSON.stringify(..)表现与ToString一致。

    JSON-safe的值可以被JSON.stringify(..)。但什么是JSON-safe的?即可以被JSON有效表示的。不是JSON-safe的很容易列出:undefinedfunctionsymbol,有循环引用的object等。

    JSON.stringify(..)会自动忽略这些不合法值,如果这些值在数组中,会被替换为null

    如果你JSON.stringify(..)一个对象,这个对象有toJSON方法,toJSON会自动先调用(可以在此返回JSON-safe的值)。

    ToNumber

    | Input Type | Result | | --- | --- | | Undefined | NaN | | Null | +0 | | Boolean | true-->1, false-->+0 | | String | 看下面的阐述 | | Object | 两个步骤:1. 首先调用ToPrimitive得到primValue; 2. 返回 ToNumber(primValue) |

    下面几个小点根据ES5规范添加了内容。

    字符串怎么ToNumber

    ToNumber应用到字符串上时,大部分跟数字字面值差不多。如果转换失败,返回NaN。假设数字字面值为_NumericLiteral_,要赚换到数字的字符串为_StringNumericLiteral_,两者的差别是: - StringNumericLiteral前后可以有空白符(包括换行符); - StringNumericLiteral前面可以有任意位0,且不会被解释成8进制; - StringNumericLiteral前面可以有'+/-'来表示符号; - StringNumericLiteral如果为空字符串或者只有空白符(包括换行符)会被转为0。

    ToPrimitive

    参数是要转换的值和可选的_PreferredType_。如果一个对象可以转换成多个基础值,用_PreferredType_来选一个。

    | Input Type | Result | | --- | --- | | Undefined | 值不变,不转换 | | Null | 值不变,不转换 | | Boolean | 值不变,不转换 | | String | 值不变,不转换 | | Number | 值不变,不转换 | | Object | 返回对象的默认值。通过调用对象的[[DefaultValue]]内部方法获取该默认值,传入可选的hint PreferredType |

    [[DefaultValue]] (hint)

    假设对象O。

    如果hint是String,步骤如下: 1. O.toString可调用(是函数)吗?是就str=O.toString()。如果str是基础类型,返回它。 2. O.valueOf可调用(是函数)吗?是就val=O.valueOf()。如果val是基础类型,返回它。 3. 抛出TypeError错误。

    如果hint是Number,步骤如下: 1. O.valueOf可调用(是函数)吗?是就val=O.valueOf()。如果val是基础类型,返回它。 2. O.toString可调用(是函数)吗?是就str=O.toString()。如果str是基础类型,返回它。 3. 抛出TypeError错误。

    没有hint,那么就当作hint是Number。除非O是Date对象(当作hint是String)。

    ToBoolean

    | Input Type | Result | | --- | --- | | Undefined | false | | Null | false | | Boolean | 值不变,不转换 | | String | 长度为0-->false;其它,true | | Number | +0,-0,NaN-->false;其它,true | | Object | true |

    注意:document.all虽然是对象,但浏览器(尤其IE,因为旧代码用它hack IE )出于想尽快废弃它的原因,有了!document.all // truetypeof document.all // undefined

    显式转换(Explicit Coercion)

    显式转换Strings <--> Numbers

    String(..)Number(..)函数可以显式转换数字和字符串。注意,没有new

     js
    var a = 42;
    var b = String( a );
    
    var c = "3.14";
    var d = Number( c );
    
    b; // "42"
    d; // 3.14
    

    String(..)把任何值转换成字符串,遵从上面的ToString()规则。Number(..)把任何值转换成数字,遵从上面的ToNumber()规则。

    .toString()也是显式转换为字符串。

    +value+是一元操作符,可以把操作数转换成数字。那么它足够显式吗?这依赖你的经验和观点。如果你很喜欢+value这种形式,要注意它的一些令人困惑的地方:

     js
    var c = "3.14";
    var d = 5+ +c;
    
    d; // 8.14
    

    +一元操作符类似的-一元操作符也把操作数转换成数字,它改变了符号。

    Date To number

    +一元操作符可以把Date对象转换成数字。

     js
    +new Date() // 1439391610180
    

    当然,用Date.now()(以及new Date( .. ).getTime())来获取时间戳可能更语义化一点。

    The Curious Case of the ~

    二进制非~可能常被忽视。

    ~及其它位操作符只支持操作32位操作数,这意味着它们会把操作数转换成32位数字,转换运用的规则是ToInt32ToInt32首先会执行ToNumber转换。

    比如常见的0 | x就可以把x转换成32位整数。

    那么~有什么用(除了转换32位数字)? - ~x的值与-(x+1)一致; - -1比较特殊,常被看作哨兵值(sentinel value)。比如数组/字符串的搜索(indexOf),-1代表不存在,>=0则是下标。 - 那么替代if(str.indexOf('x') >= 0){},可以用if(~str.indexOf('x')){//找到}

    ~的另一个用处是~~可以截断数字小数部分。但需注意2点: - 处理负数时与Math.floor不一致。Math.floor(-49.6) === -50~~-49.6 === -49。 - 为什么不用x | 0来截断小数?操作符优先级问题。~优先级更高,所以可以~~1E20 / 10;而不用(1E20 | 0) / 10

    显式:Parsing Numeric Strings

    从字符串解析数字(parseIntparseFloat)和把字符串类型转换为数字可以得到类似结果,但它们有明显的区别。

     js
    var a = "42";
    var b = "42px";
    
    Number( a );    // 42
    parseInt( a );  // 42
    
    Number( b );    // NaN
    parseInt( b );  // 42
    

    从字符串解析数字可以容忍非数字字符,遇到时它只是停止从左向右解析。而类型转换时直接返NaN。解析不应该看作类型转换的替代,因为两者目的不同,解析不关心右侧的非数字字符;而类型转换只接受数字字符。

    提示:不要忘记parseInt操作字符串,传入非字符串没有意义。如果传入非字符串,首先按照ToString转化成字符串。另外,parseInt接受第二个参数,指定进制。(parseFloat(string)没有第二个参数!)如果没有指定,如果字符串开头是0x就会按照16进制解析,而0开头就8进制。

     js
    parseInt( 0.0000008 );      // 8   ("8" from "8e-7")
    parseInt( false, 16 );      // 250 ("fa" from "false")
    parseInt( "0x10" );         // 16
    parseInt( "103", 2 );       // 2
    
    parseInt( "\n 3" );         // 3
    parseInt( "" );             // NaN
    parseInt( " x" );           // NaN
    parseFloat( "   .9");       // 0.9
    

    注意: - parseIntparseFloat对前导的空白字符(包括换行符)也容忍,但如果第一个非空字符不是数字直接返NaN。 - parseIntparseFloat对空字符串返NaN

    显式:* --> Boolean

    Boolean(..)(没有new)把非布尔值转换成布尔值。但我们更习惯的可能是!以及!!

    隐式转换(Implicit Coercion)

    隐式转换是不是邪恶?很难说。本文的目标是更好的理解隐式转换,减少代码冗余和不必要的实现细节。

    在开始具体的罗列分析隐式转换各种情况前,首先列出ES5相关的一些规范:

    二元操作符+ 1. 分别计算左右操作数,得到lval,rval。(有步骤合并省略) 2. lprim = ToPrimitive(lval)。 3. rprim = ToPrimitive(rval)。 4. 如果lprimrprim是字符串,都转换为字符串然后相加返回。 5. 都转换为数字相加后返回。

    二元操作符- 1. 分别计算左右操作数,得到lval,rval。(有步骤合并省略) 2. lnum = ToNumber(lval)。 3. rnum = ToNumber(rval)。 4. 相减后返回。

    加减很不同!

    隐式:Strings <--> Numbers

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

    隐式:Booleans --> Numbers

     js
    0 + true // 1
    

    隐式:* --> Boolean

    有哪些隐式的boolean转换? - if (..) - for ( .. ; .. ; .. )第二处值 - while (..)do..while(..) - ? :第一处值 - ||&&左边的值

    以上所使用的值如果不是布尔值,会被隐式转换成布尔值,遵循ToBoolean的规则。

    Operators || and &&

    逻辑或,逻辑与都是短路的,并且它们都不会把值转换成布尔值。更精确地说,两个操作符是从两个操作数中选一个。

    Symbol Coercion

    到现在为止,显式和隐式转换间没有可见的结果不同,除了代码的可读性。

    但ES6的symbol不同:显式转换symbolstring是允许的,但隐式则报错。

     js
    var s1 = Symbol( "cool" );
    String( s1 );                   // "Symbol(cool)"
    
    var s2 = Symbol( "not cool" );
    s2 + "";                        // TypeError
    

    symbol完全无法转换到number,但奇怪的是symbol可以显式与隐式转换到boolean(总是true)。

    Loose Equals vs. Strict Equals

    JS中有(宽松)相等(==)和严格相等(===),两者的区别是==允许类型转换,===不允许。

    性能(Equality Performance)

    尽管=====可能要慢一点(微秒级),但不要纠结这个。 - 如果你比较的两个值类型相同,那么两种相等采用同一算法,做的工作相同。 - 如果比较的类型不同,那么重点是你希望有类型转换吗?

    Abstract Equality

    x == y遵从The Abstract Equality Comparison Algorithm: 1. x与y类型相同: 1. x的类型是 Undefined,返回true。 2. x的类型是 Null,返回true。 3. x的类型是 Number: 1. x是NaN,返回false。 2. y是NaN,返回false。 3. x与y是同一数字,返回true。 4. x是+0,y是-0,返回true。 5. x是-0,y是+0,返回true。 6. 返回false。 4. x的类型是 String,如果x和y是完全相同的字符序列,返回true,否则返回false。 5. x的类型是 Boolean,如果x和y同是true或者false,返回true,否则返回false。 6. 如果x和y指向同一个对象,返回true,否则返回false。 2. 如果x是null,y是undefined,返回true。 3. 如果x是undefined,y是null,返回true。 4. 如果x的类型是 Number,y的类型是 String,返回 x == ToNumber(y)。 5. 如果x的类型是 String,y的类型是 Number,返回 ToNumber(x) == y。 6. 如果x的类型是 Boolean,返回 ToNumber(x) == y。 7. 如果y的类型是 Boolean,返回 x == ToNumber(y)。 8. 如果x的类型是 String 或 Number,y的类型是 Object,返回 x == ToPrimitive(y)。 9. 如果y的类型是 String 或 Number,x的类型是 Object,返回 ToPrimitive(x) == y。 10. 返回false

    Strict Equality Comparison Algorithm

    x === y遵从The Strict Equality Comparison Algorithm 1. x与y类型不同,返回false。 2. 如果x的类型是 Undefined,返回true。 3. 如果x的类型是 Null,返回true。 4. 如果x的类型是 Number: 1. 如果x是 NaN, 返回false。 2. 如果y是 NaN, 返回false。 3. x和y是相同的数字,返回true。 4. x是+0, y是-0,返回true。 5. x是-0, y是+0,返回true。 6. 返回false。 5. 如果x的类型是 String,如果x和y是完全相同的字符序列,返回true,否则返回false。 6. 如果x的类型是 Boolean,如果x和y同是truefalse,返回true,否则返回false。 7. 如果x和y指向同一个对象,返回true,否则返回false

    Abstract Relational Comparison

    The Abstract Relational Comparison Algorithm

    x < y,规则比较复杂,简单概括下: 1. 首先会对两个操作数执行ToPrimitive,也就是说,最终比较的都是基本类型。这里假设转换后左右分别是pxpypx < py)。 2. 如果pxpy都是String, 1. 如果pypx的前缀,返回false。 2. 如果pxpy的前缀,返回true。 3. 让 k 是最小的非负整数,在位置 k,pxpy的字符不同。 4. 让 m 是 px[k] 字符的编码。 5. 让 n 是 py[k] 字符的编码。 6. 如果 m < n, 返true。否则返回false。 3. 如果不都是String: 1. 用ToNumber转化为数字nxny。 2. 如果nxNaN,返回undefined。 3. 如果nyNaN,返回undefined。 4. 如果nxny是同一数字,返回false。 5. 如果nx-0ny+0,返回false。 6. 如果nx+0ny-0,返回false。 7. 如果nxInfinity,返回false。 8. 如果nyInfinity,返回true。 9. 如果ny-Infinity,返回false。 10. 如果nx-Infinity,返回true。 11. 如果算数上nx小于ny,且nxny都有限,不都为0,返回true。否则,返回false

    步骤很多,但总结下:首先都转换为基础类型,如果都是字符串,按字符串比较;否则都转数字比较。

    点赞 评论 复制链接分享
  • weixin_39770226 weixin_39770226 4月前

    请问toPrimitive是将object类型先通过valueOf(),如果结果不是基本类型在通过toString转换成基本类型的意思么?hint有些不懂

    点赞 评论 复制链接分享