类型
-
内置类型:null、undefined、boolean、number、string、object、symbol(es6);
-
null的判断
var a = null; !a && typeof a == "object" // true
-
typeof 类型判断
typeof null === 'object' // true typeof undefined === 'undefined' // true typeof true === 'boolean' // true typeof 123 === 'number' //true typeof '123' === 'string' // true typoef Symbol() === 'symbol' // true typeof {} === 'object' // true typeof [] === 'object' // true typeof Array === 'function' // true typeof Function === 'function' // true typeof Number === 'function' //true
-
函数具有length属性,值为形参个数
function foo (m, n) { /* 函数体 */ } foo.length // 2
-
JavaScript变量无类型,仅值有类型(非强制类型语言),即同一变量可以修改持有不同类型的值;
-
undefined与undeclared: undefined表示声明但未分配值,而undeclared是在作用域中未声明该变量;使用typeof无法区分两者;
var a; typeof a; // "undefined" typeof b; // "undefined"
在js中,访问一个undeclared的变量通常程序会报错Uncatched ReferenceError,而这通常被用来作为typeof的安全防御机制。可以通过typeof undeclared检测变量变量是否已声明或赋值,从而防止重复声明、异常报错等;另外,也可以通过window.全局变量的方式作为安全防御机制,但这样会产生污染;
if (typeof test === 'undefined') { // 排除重复声明、覆盖或者未声明报错 test = function () {/**/} }
依赖注入(dependency injection)设计模式:将需要的依赖以参数方式传入并做判断
function shopping (buyMilk) { var getMilk = buyMilk || function () {}; }
值
-
使用delete可以删除数组元素,但是其长度不变;
var arr = [1, 2, 3, 4]; delete arr[1]; console.log(arr); // [1, undefined, 3, 4] console.log(arr.length); // 4 ```
-
数组也是对象,因此也可以包含键值对属性,但是不会影响数组的length属性;当然,这种做法并不提倡。
var arr = [1, 2, 3, 4]; arr['key'] = 5; console.log(arr); // [1, 2, 3, 4, key: 5] console.log(arr.length); // 4 ```
-
for…in与for…of
- for…in是ES5标准,for…of是ES6标准;
- for…in是索引的遍历,for…of是值的遍历;
- for…of不支持遍历对象;
- for…of支持遍历数组、字符串、类数组、NodeList、Map、Set等;
-
类数组转为数组:
- ES6标准的Array.from()方法;
- Array.prototype.slice.call();
-
JavaScript中字符串是不可变的,而数组是可变的;
-
字符串也具有length、indexOf()和concat()属性;通过索引可以访问字符串,如str[1],但正确的方法应该是str.charAt(1);
-
JavaScript中的数字是双精度浮点数格式(64位二进制),基于IEEE 754标准;
-
Number.EPSILON:ES6标准,表示1与大于1的最小浮点数的差值,值为2E-52;通常可以用于判断两个数是否相等;
var a = 0.1, b = 0.2, c = 0.3; var equal = Math.abs(a + b - c) < Number.EPSILON // true;
-
Number的方法toFixed()与toPrecision()的区别,两者均可以用于数字常量和变量;
10.toFixed(2); // Uncaught SyntaxError: Invalid or unexpected token,“.”会优先被识别为数字常量的一部分——小数点 10..toFixed(2); // 10.00 (10).toFixed(2); // 10.00
-
MAX_VALUE、MIN_VALUE、MAX_SAFE_INTEGER、MIN_SAFE_INTEGER
MAX_VALUE MIN_VALUE MAX_SAFE_INTEGER MIN_SAFE_INTEGER 1.798E+308 5E-324 2E53-1 -2E53+1 -
null与undefined区别:
- 两者既是类型也是值;
- null指空值,undefined指没有值;
- null是特殊关键字,不是标识符;
- undefined是标识符,因此在非严格模式下可以为其赋值(不推荐);
undefined = 2; console.log(undefined); // undefined function test () { var undefined = 3; console.log(undefined); } test(); // 3;
-
void不改变表达式的值,只是让其不返回值;void 0,void 1, void true和undefined没有本质区别;
var a = 1; console.log(void a, a); // undefined, 1
-
NaN (Not a Number),概念易误解,更准确的是无效数值;ES6标准给出了判断NaN的方法Number.isNaN();ES6之前存在一个方法window.isNaN(),但该方法检测的如误解的那样不是一个数字或者是NaN都会返回true;
NaN === NaN; // false, 非自反性 var a = 1/'a'; // NaN typeof a === 'number'; //true // NaN判断 var b = 'fn'; Number.isNaN(a); // true Number.isNaN(b); // false window.isNaN(a); // true window.isNaN(b); // true,这就有问题了~ // ES6之前的polyfill Number.isNaN = Number.isNaN ? Number.isNaN : function (n) { return typeof n === 'number' && window.isNaN(n); } // 或者利用NaN的非自反性 Number.isNaN = Number.isNaN ? Number.isNaN : function (n) { return n !== n; }
-
Infinity,表示JavaScript的计算结果溢出
var a = 1 / 0; // Infinity, Number.POSITIVE_INFINITY var b = -1 / 0; //-Infinity, Number.NEGATIVE_INFINITY Infinity / Infinity; // NaN
-
JavaScript中的零值,有+0和-0之分,且二者相等;-0出现的意义是某些情况下需要其表示某些矢量,如动画帧的移动方向及速度;
var a = -1 / Infinity; // -0 Number(a); // -0 a.toString(); // "0" JSON.stringify(a); // "0" JSON.parse("-0"); // -0 // -0的判断 function isNegZero (n) { n = Number(n); return (n === 0) && (1 / n === -Infinity); }
-
ES6引入了一个新函数Object.is(),可以用于判断两个值是否完全相等,包括判断0与-0及NaN;You Don’t Know JavaScript中建议,该方法效率没有 ===或 == 效率高,尽量只用于特殊值的判断;
var a = 1 / Infinity; // 0 var b = -1 / Infinity; // -0 var c = 1 / 'foo'; // NaN Object.is(a, b); // false; Object.is(c, NaN); //true;
-
简单值(基本类型)进行值复制,复合类型进行引用复制;
-
JavaScript中的引用指向的是值,多个引用之间没有引用/指向关系;
function foo (x) { x.push(4); console.log('x: ', x); // x: [1, 2, 3, 4] // then x = [4, 5, 6]; // x指向了新数组 x.push(7); console.log('then x: ', x); // then x: [4, 5, 6, 7] } var a = [1, 2, 3]; foo(a); console.log('a: ', a); // a: [1, 2, 3, 4] // 想要通过x更改a的值,如下 function foo (x) { x.push(4); console.log('x: ', x); // x: [1, 2, 3, 4] // then x.length = 0; // 未创建新数组,只是对共同引用的值进行了修改 x.push(4, 5, 6, 7); console.log('then x: ', x); // then x: [4, 5, 6, 7] } var a = [1, 2, 3]; foo(a); console.log('a: ', a); // a: [4, 5, 6, 7]
我们不能通过引用x来更改引用a的指向,只能更改a和x共同指向的值;
原生函数
- JS中常见的原生函数(也称内建函数):Number()、String()、Boolean()、Array()、Function()、Object()、RegExp()、Date()、Error()、Symbol();原生函数通常可以用作构造函数;
- 基本类型的封装与拆封——valueOf()
- demo
分析:从ES5开始,Array.apply()方法可以使用任何的具有length属性的类数组对象作为第二个参数;Array.apply(null, [1,2,3]); // [1, 2, 3] Array.apply(null, {a: 1}); // [] Array.apply(null, {length: 2}); // [undefined, undefined] Array.apply(null, {'0': 'eat', '1': 'bananas'}); // [] Array.apply(null, {'length': 3, '0': 'eat', '1': 'bananas'}); // ["eat", "bananas", undefined]
- 使用常量形式定义正则表达式具有更高的执行效率。因为JS引擎会对其进行预编译和缓存;
- ES5之前,获取当前时间戳使用
new Date.getTime()
,ES5引入了静态方法Date.now();new Date.getTime() === Date.now(); // true
- Symbol()构造函数使用时不能带new关键字,且生成值具有唯一性,常用于创建私有或特殊属性;
- String.substr(start, length)和String.substring(start, end):
- str.substr()第二个参数为截取长度,str.substring()第二个参数为结束位置索引;
- str.substring():任一参数小于0或为NaN,则取0;任一参数大于str.length,则取str.length;start大于end则两者互换;
- str.substr():start为正且不小于str.length,则返回为空字符串;start为负,从末尾的第一个字符开始取;start为负且绝对值大于length,则start取0;length为0或负,则返回空串;
var str = 'hello, world'; str.substr(1); // "234567" str.substring(1); // "234567" str.substr(1,3); // "23" str.substring(1,3); // "234" // substring() str.substring(-1, 3); // "123" str.substring(NaN, 3); // "123" str.substring(6, 10); // "7" str.substring(10, 6); // "7" // substr() str.substr(10, 6); // "" str.substr(-2, 8); // "67" str.substr(1, -1); // ""
强制类型转换
- 强制类型转换指的是隐式地将值从一种类型转为另一种类型,得到的结果总是基本类型值;
- toString(): 字符串化是一个递归过程
String(null); // "null" String(undefined); // "undefined" String(true); // "true" var a = 3.14 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000; a.toString(); // "3.14e+24"
- 数组的toString()方法进行了重新定义,字符串化时元素之间使用","链接;
- 所有安全的JSON值都可以用JSON.stringify()方法字符串化,它实际上也用到了toString()方法,但是不是严格意义的强制类型转换;JSON.stringify()方法在遇到undefined、symbol、function以及循环引用的对象时的处理并不符合强制类型转换规则;对象中的undefined、symbol、function会自动被忽略,数组中的则以null做占位符;
如果JSON序列中定义了toJSON()方法,则优先调用;toJSON返回一个能够被字符串化的安全的JSON值;JSON.stringify(undefined); // undefined JSON.stringify(function () {}); // undefined JSON.stringify(Symbol()); // undefined JSON.stringify([1, undefined, function () {}, Symbol(), 2]); // "[1,null,null,null,2]" JSON.stringify({a: 1, b: function () {}, c: Symbol(), d: undefined, e: 2}); // "{"a":1,"e":2}"
JSON.stringify()可以接受一个数组或函数类型的可选参数,用于指定字符串化的过程中那些属性被处理或者忽略;该方法也支持第三个参数,用于为文本添加缩进、空格或换行;var json = { a: 1, b: undefined, c: "12", d: [1, 3, [5, 7]] } JSON.stringify(json, ["b", "c", "d"]); // "{"c":"12","d":[1,3,[5,7]]}" JSON.stringify(json, function (k, v) { if (k != "1") return v; }); // "{"a":1,"c":"12","d":[1,null,[5,null]]}"
- toNumber – Number(),处理失败返回NaN
Number(undefined); // NaN Number(null); // 0 Number('123'); // 123 Number('123a'); // NaN Number(Symbol); // NaN, typeof Symbol === 'function' Number(Symbol()); // TypeError, can't convert a symbol value to a number Number({a: 1, b: 2}); // NaN
- 进行强制类型转换时,一般会检查该值是否拥有valueOf()方法;
- 如果有valueOf()并且返回基本类型值,则使用该值进行强制类型转换;
- 如果没有或者返回非基本类型值则使用toString()的返回值进行强制类型转换;
var a = { valueOf: function () { return '12'; } } var b = { valueOf: function () { return {a: 1}; }, toString: function () { return '34'; } } var c = { toString: function () { return '22'; } } var d = [1, 2]; d.toString(); // "1,2" d.toString = function () { return this.join(''); } d.toString(); // "12" Number(a); // 12 Number(b); // 34 Number(c); // 22 Number(d); // 12 Number([]); // 0
- 强制类型转换——ToBoolean:
null、undefined、false、+0、-0、NaN、’'这些值强制类型转为boolean类型时值为false,其他的均为true; - JavaScript语法中构造函数没有参数时可以不用带括号,如new Date、new fn等;
- str.indexOf()方法不仅可以得到子字符串的位置,而且可以用于检测字符串中是否包含指定的子字符串;
- 抽象渗漏:指在代码中暴露了底层实现细节;“~”非运算符可以用于防止抽象渗漏;
var str = "Hello World"; if (a.indexOf("ol") == -1) { // 暴露了条件判断临界点 Toast('not found'); } // 使用'~'运算符 if (!~a.indexOf("ol")) { Toast('not found'); }
- 解析字符串与转换字符串的差异——parseInt()与Number():解析parseInt()允许出现非数字字符,从左至右解析,直到遇到非数字字符串停止;转换若出现非数字字符则返回NaN;parseInt()可以接受两个参数,第一个是需要解析的字符串,第二个参数是转换结果的进制数;ES5之前,若不指定第二个参数,则以字符串的第一个字符决定:第一个字符为x或X,则转为十六进制;第一个字符为0,则转为八进制数字;ES5开始,第二参数不指定则默认为十进制;
parseInt(1/0, 19)
等同于parseInt(Infinity, 19) = 18
,十九进制范围是0-9、a-i,解析时从左至右I对应18,n无对应,解析终止;- 字符串与数字间的隐式强制类型转换,+通常可以用于数值加法或者字符串拼接:
false + NaN // NaN {} + [] // 0,{}被当做一个空代码块,+运算符强制将[]转为0,故结果为0; [] + {} // "[Object Object]",调用了数组和对象的toString()方法,结果分别为0 和"[Object Object]",最后进行字符串拼接操作;
- a + ‘’(隐式) 与 String(a)(显式)之间有一个细微差别:根据
Toprimitive
规则,a + ‘’ 会首先调用valueOf()方法,然后通过toString()将返回值转为字符串;String()则直接调用toString()方法; - JavaScript中的&&和||运算符的返回值是两个操作数中的一个,而不像其他一些语言中只是返回布尔值;&&判断为true总是返回第二个操作数;||可以用于设置函数默认值:
a = a || 'hello'
;&&常被用作守护运算符,即确保第一个操作数为真的条件下再执行第二个操作数的操作:如a && foo()
; - 隐式强制转换为布尔值:
- if(…)语句的条件判断表达式;
- for(…; …; …)语句的条件判断表达式;
- do…while(…)及while(…)的条件判断表达式;
- 三目运算符
? :
中的条件判断表达式; - &&与||左边的操作数(作为条件判断表达式);
- 符号的强制转换——ES6允许从符号到字符串的显式强制转换,而隐式强制转换会报错;符号不能强制转换为数字(隐式与显式),可以被强制转换为boolean;
var s = Symbol('test'); String(s); // "Symbol(test)" s + ''; // TypeError, can't convert a symbol value to string Number(s); // TypeError, can't convert a symbol value to number s - 0; // TypeError, can't convert a symbol value to number Boolean(s); // true s ? 1 : 0; // 1
==
与===
的正确释义:前者允许在相等比较中进行强制类型转换,后者不允许;两者都会检查操作数的类型,只不过在类型不同时的操作不同;宽松相等中的强制转换规则:- 字符串和数字间的相等:若一个操作数为字符串,另一个为数字,总是把字符串转为数字再进行比较;
- 其他类型与布尔值的相等:若Type(x)是布尔值,则返回
ToNumber(x) == y
的结果;若Type(y)是布尔值,则返回x == ToNumber(y)
的结果;var x = true; var y = '12' var z = '1'; x == y // false x == z // true
- null与undefined间的相等比较:在
==
中null与undefined相等,除此之外其他值都不存在这种状况。var a = null; var b; a == b; // true a == null; // true b == null; // true a == false; // false b == false; // fase a == ""; // false b == ""; // false a == 0; // false b == 0; // false ```
==
中的类型转换
其中toPrimitive
为对象转基本类型- 对象(对象、函数、数组)与非对象(字符串、数字、布尔值)间的相等比较:Type(x)为字符串或数字,Type(y)为对象,返回
x == ToPrimitive(y)
的结果; Type(x)为对象,Type(y)为字符串或数字,返回xToPrimitive(x) == y
的结果;布尔值会先被转为数字再依照上边规则进行比较;封装对象会被解封装,但null、undefined无对应封装对象,不能被封装;var a = 'hello world'; var b = Object(a); // 同new String(a)一样 a == b; // true var a = null; var b = Object(a); // 同Object() a == b; // false
- 假值的相等比较:
"0" == false; // true "0" == null; // false "0" == undefined; // false "0" == 0; //true "0" == ""; //false false == null; // false, => 0 == null false == undefined; // false, => 0 == undefined false == 0; // true false == []; // true; false->0,[]调用ToPrimitive返回'0', 0 == '0',数组的valueOf()返回数组本身,并不是一个基本类型值,故会继续调用toString()方法返回字符串; false == {}; // false; false->0, {}调用Toprimitive返回'[Object Object]' "" == []; // true "" == {}; // false "" == [null]; // true; String(null)为'null',String([null])为''; 0 == []; // true 0 == {}; // false 0 == ' \n'; // true; ''、'\n'、' '或其他空格组合等空字符串被ToNumber强制类型转换为0; [] == ![]; // true, 非运算符优先级高,优先对[]进行强制类型转换,得到false; 比较false == [];
- 隐式强制类型转换安全规则:
- 若两边的值中含有false或true,不要使用
==
; - 若两边的值中含有[]、’'或0,不要使用
==
;
- 若两边的值中含有false或true,不要使用
- 抽象关系比较:比较双方先调用ToPrimitive规则,结果出现非字符串则根据ToNumber规则将双方转为数字再比较;
var a ={c: 42}; var b = {c: 43}; a < b; // false a == b; // false a.toString() == b.toString(); // true a > b; // false a <= b; // true; js中a<=b会被处理为b<a然后结果取反,故为true; a >=b; // true
语法
-
语句都有一个结果值,称为statement completion value,这就是为什么我们在控制台调试的时候总是看到输出undefined或其他值;默认情况下控制台会显示最后一条语句的结果值。根据规范,var的结果值为undefined;
var a = 21; // undefined b = a; // 21
-
语句和表达式的区别;
-
代码块的结果值就是一个隐式的返回,返回的是最后一个语句的结果值;想一想,有时候我们需要通通过一系列运算之后得到一个结果并赋值给一个变量,之前的做法通常是将这一段代码封装在一个函数中,然后将最后一行的结果值返回,这是不是和代码块隐式返回的结果值有些相似?
我们无法直接将代码块的结果值赋值给一个变量,之前的做法是可以使用eval(),但明显不被推荐;ES7中提出了一个提案:
var a, b; a = if (true) { // Uncaught SyntaxError: Unexpected token if b = 1 + 2; } // ES7提案,不需要将语句封装为函数并调用return返回结果 var a, b; a = do { if (true) { b = 1 + 2; } }
-
标签语句
- continue foo: 执行foo循环的下一轮循环
- break foo:跳出foo所在的循环/代码块
foo: for (var i=0; i<4; i++) { for (var j=0; j<4; j++) { if ((i*j) >=3) { console.log('stopping: ', i, j); break foo; // 结束外层循环 } console.log(i, j); } } // 返回值 0 0 0 1 0 2 0 3 1 0 1 1 1 2 stopping: 1 3 foo: for (var i=0; i<4; i++) { for (var j=0; j<4; j++) { if ((i*j) >=3) { console.log('stopping: ', i, j); break; // 结束内层循环 } console.log(i, j); } } // 返回值 0 0 0 1 0 2 0 3 1 0 1 1 1 2 stopping: 1 3 2 0 2 1 stopping: 2 2 3 0 stopping: 3 1
-
JSON是JavaScript语法的一个子集,但JSON本身不是合法的JavaScript语法;如
{"a": 1}
在js中会报错,会被当做带有非法标签的代码块执行;JSONP可以将JSON转为合法的js语法;调用foo({"a": 1})
在js语法中不会报错; -
实际上,JavaScript没有else if,但当if和else只包含单条语句的时候可以省略代码块的
{}
,之所以else if不会报错是因为else后跟的if语句是因为其为单条语句,可以省略{}
,原型为if() {...} else {if () {...}}
-
运算符优先级:
- 逗号运算符优先级最低;
var a = 1, b; b = (a++, a); a; // 2 b; // 2 var a = 1, b; b = a++, a; // 相当于(b = a++), a a; // 2 b; // 1
- 逗号运算符优先级最低;
-
js自动为某些代码行补上分号的行为称为自动分号(ASI, auto semicolon insertion),这主要是为了提高解析器的容错率,否则确认的分号会导致代码解析失败;js不仅有运行时错误(TypeError, ReferenceError, SyntaxError等,可以通过try…catch捕获),还存在“早期错误”(编译阶段发现的代码错误),所有的语法错误都是早期错误;
-
ES6引入暂时性时区(Temporal Dead Zone)概念,即不能在变量被初始化之前被引用
{ a = 1; // ReferenceError let a; } // typeof 用于未声明变量并不会报错 { typeof a; // undefined typeof b; // ReferenceError(TDZ) let b; } var b = 3; function foo(a=1, b = a + b) { // b会出现TDZ // ... }
-
函数参数与参数默认值
function foo(a=1, b = a + 1) { // b会出现TDZ console.log(a, b, arguments.length); } foo(); // 1, 2, 0 foo(undefined); // 1, 2, 1 传入undefined会取参数默认值,但是会影响argumengts foo(null); // null, 1, 1 foo(void 0, 1); // 1, 1, 2
-
try…finally,finally总是在try之后执行,有catch则在catch之后执行,并且finally总是会执行;
- 如下例,try中包含return,try中的return语句执行后,将函数foo的返回值设置为12;然后继续执行finally中的代码;try中的throw同理;
function foo() { try { return 12; // throw 12; 结果与return类似,最后返回Uncaught Exception: 12 } finally { console.log('hello'); } console.log('foo end...'); // 函数foo的return之后的语句均会被忽略,无return则函数默认返回undefined; } console.log(foo()); // 'hello' // 12
- finally中抛出异常的话,函数会就此终止;如果之前try中return了返回值,会被抛弃;
function foo() { try { throw 12; // return 12;同样会被忽略 } finally { throw 'Oops!'; } console.log('foo end...'); } console.log(foo()); // Uncaught Oops!
- continue与break
for (var i=0; i<10; i++) { try { continue; } finally { console.log(i); // 结果为0...9而非1...10,因为finally无论如何都会执行,所以continue进行下次循环前都会执行finally. } }
- 函数中的返回值:省略
return
、return;
以及return undefined
的效果其实是一样的;但是finally如果省略return,则使用try或catch中的return;若finally设置了return,则会覆盖try和catch中的return;function foo() { try { return 10; } finally { return; } } function bar() { try { return 10; } finally { return 11; } } function baz () { try { return 10; } catch (err) { return 11; } } function fun () { try { return 10; } catch (err) { return 11; } finally { return 12; } } console.log(foo()); //undefined console.log(bar()); // 11 console.log(baz()); // 10 console.log(fun()); // 12
- 如下例,try中包含return,try中的return语句执行后,将函数foo的返回值设置为12;然后继续执行finally中的代码;try中的throw同理;
-
switch语句:
- switch中的条件语句与case比较时不会进行强制类型转换,即需要
===
; - case执行顺序与排列顺序相关;
var a = '1'; switch (a) { case 1: console.log('number'); case '1': console.log('string'); default: console.log('default'); case a > 0: console.log('positive'); break; case a == 1: console.log('equal'); }
- switch中的条件语句与case比较时不会进行强制类型转换,即需要
参考文献
- 《You Don’t Know JavaScript》(中册);
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/substring
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/substr