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

发布于 2022-05-10 12:40:11 字数 14181 浏览 964 评论 9

第一章:类型(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",这可能会造成一点混淆。但这是安全的检测未声明变量的方法。

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

  • null是空值(empty value);undefined是无值(missing value)。
  • null有值但不做任何事;undefined还没有值。

undefined可以做标识符

非严格模式下,可以向全局的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(..)

  • Array可以不加newArray(1,2,3)返回[1, 2, 3]
  • Array的参数是一个数字时,当成数组长度。此时会创建稀疏数组。
  • 稀疏数组的map陷阱。
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.create 和 Symbol.iterator

Native Prototypes

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

Prototypes As Defaults

  • Function.prototype是空函数。
  • RegExp.prototype是空正则(不匹配任何字符串)。
  • Array.prototype是空数组。

这些都是很好的默认值。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

倒数 2022-05-04 13:58:57

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

稚气少女 2022-05-04 13:58:53

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

我乃一代侩神 2022-05-04 13:58:44

在用 ES6 的话就不要再写

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

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

Array(3).fill(void 0);
Array.from({ length: 3 });
断桥再见 2022-05-04 13:58:22

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

因为看清所以看轻 2022-05-04 13:56:15

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

迷爱· 2022-05-04 13:45:40

@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()

够运- 2022-05-04 13:44:56

@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

喵星人汪星人 2022-05-04 12:56:58

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

︶葆Ⅱㄣ 2022-05-04 11:22:55

第四章: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 TypeResult
Undefined

~没有更多了~

关于作者

薯片软お妹

暂无简介

0 文章
0 评论
24 人气
更多

推荐作者

1CH1MKgiKxn9p

文章 0 评论 0

ゞ记忆︶ㄣ

文章 0 评论 0

JackDx

文章 0 评论 0

信远

文章 0 评论 0

yaoduoduo1995

文章 0 评论 0

霞映澄塘

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文