creeperyang / blog

前端博客,关注基础知识和性能优化。
MIT License
2.63k stars 211 forks source link

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

Open creeperyang opened 9 years ago

creeperyang commented 9 years ago

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。两者区别是:

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

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可以转换成十进制数字时,会假设你想使用数值索引:

var a = [ ];

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

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

类数组

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

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中数字通常用十进制表示:

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()方法的输出:

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)。

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"

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

// 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的所有语言)的最著名副作用是:

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通常被用来当作可互换的空值或非值。可以这么区分:

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

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,但:

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

Infinities

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

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

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,根据规范。

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"

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

+"-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(..)获取。

而对基础类型的值来说:

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)

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

拆箱(Unboxing)

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

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

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

Natives as Constructors

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

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

Array(..)

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不一样。

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 commented 9 years ago

第四章: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,两者的差别是:

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

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这种形式,要注意它的一些令人困惑的地方:

var c = "3.14";
var d = 5+ +c;

d; // 8.14

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

Date To number

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

+new Date() // 1439391610180

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

The Curious Case of the ~

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

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

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

那么~有什么用(除了转换32位数字)?

~的另一个用处是~~可以截断数字小数部分。但需注意2点:

显式:Parsing Numeric Strings

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

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进制。

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

注意:

显式:* --> 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

var a = [1,2];
var b = [3,4];

a + b; // "1,23,4"

隐式:Booleans --> Numbers

0 + true // 1

隐式:* --> Boolean

有哪些隐式的boolean转换?

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

Operators || and &&

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

Symbol Coercion

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

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

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

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

mino01x commented 7 years ago

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

creeperyang commented 7 years ago

@Huahua-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

mino01x commented 7 years ago

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


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

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

哦哦哦,我知道了。

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

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

creeperyang commented 7 years ago

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

mino01x commented 7 years ago

@creeperyang 谢谢。所以有些不懂preferredType有什么作用。那请问if(express)if(!!express)两者的区别在那里?是后者的性能好,还是前者可能会造成异常?express不是会强制类型转换吗,为什么使用!!呢?

tiansh commented 6 years ago

在用 ES6 的话就不要再写

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

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

Array(3).fill(void 0);
Array.from({ length: 3 });
eachmawzw commented 6 years ago

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

creeperyang commented 6 years ago

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