1. 类型
-
1.1 内置类型
Javascript中有七种内置类型:null、undefined、number、string、boolean、object、symbol(ES6)。
-
1.2 typeof
typeof:返回参数的类型的字符串
typeof null; // "object" typeof undefined; // "undefined" typeof 1; // "number" typeof "abc"; // "string" typeof true; // "boolean" typeof {}; // "object" typeof Symbol(); // "symbol" typeof typeof 1; // "string"
-
1.3 undefined 和 undeclared
JavaScript中通过typeof判断未声明和未持有值的时候,都会返回"undefined"。
var a; typeof a; // "undefined" typeof b; // "undefined" console.log(a); // undefined; console.log(b); // ReferenceError: b is not defined typeof b === "undefined"; // 判断变量是否存在
2. 值
-
2.1 数组
数组通过数字进行索引,但也可以包含字符串键值和属性(这些不计算在数组长度内)。
PS:如果字符串键值能被强制转换为十进制数字时,会被当做数字索引进行处理。var arr = [1,2,3]; arr['foo'] = 'foo'; console.log(arr.length); // 3 console.log(arr.foo); // "foo" // 类数组转换 var divs = document.getElementsByTagName("div"); divs = Array.from(divs); // [].slice.call(divs);
-
2.2 字符串
字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。
-
2.3 数字
- 2.3.1 . 运算符
对于数字常量使用 . 运算符时,会被优先识别为数字常量的一部分,然后才是对象属性访问运算符。
var num = 10; 10.toFixed(2); // SyntaxError:Invalid or unexpected token num.toFixed(2); // 10.00 (10).toFixed(2); // 10.00 10..toFixed(2); // 10.00 10 .toFixed(2); // 10.00
- 2.3.2 较小数值的精度问题
二进制浮点数最大的问题(所有遵循IEEE 754规范的语言都是如此)0.1 + 0.2 === 0.3 // false // 0.1 + 0.2 实际等于0.30000000000000004 // 解决:使用ES6的Number.ESPILON,判断误差范围 Number.prototype.isEqual = function(num1, num2) { if (!Number.ESPILON) { Number.ESPILON = Math.pow(2, -52) }; return Math.abs(num1 - num2) < Number.ESPILON }
- 2.3.1 . 运算符
-
2.4 特殊值
-
2.4.1 undefined / null(不是值的值)
undefined:没有值,是一个标识符,可以当做变量来使用和赋值
null:空值,特殊关键字,不是标识符,不能当做变量来使用和赋值。
void:表达式,没有返回值,所以返回结果为undefined(例:void 0; void 1;…)undefined = 1; // 非严格模式下,可以对undefined赋值; // PS:永远不要重新定义undefined "use strict" undefined = 1; // TypeError void 0 === undefined; // true
-
2.4.2 NaN(not a number / 无效数值)
NaN仍是数字类型,且不等于自身。var a = 2 / "aaa"; // NaN typeof a === "number"; // true NaN === NaN; // false Number.isNaN(NaN); // true
-
2.4.3 零值
-0:例如在动画中,数字的符号位用来表示方向,当一个变量为0时,便会失去了它的符号位,导致方向信息丢失。此时应保留零值符号位。var a = 0 / -3, // -0 b = 0 * -2; // -0 a === b; // true -0 === 0; // true // 判断是否是 -0 Number.prototype.isNegZero(num) { return (num === 0) && (1 / num === -Infinity); }
-
-
2.5 值和引用
JavaScript中没有指针,引用指向的是值!
简单值(基本类型值):通过值赋值的方式来赋值/传递,包括null、undefined、number、string、boolean、Symbol。
复合值(对象和函数):通过引用复制的方式来赋值/传递。// 简单值 var n1 = 1; n2 = n1; n2++; console.log(n1, n2); // 1 , 2 // 复合值 var arr1 = [1,2,3]; var arr2 = arr1; arr2.push(4); console.log(arr1); // [1, 2, 3, 4] console.log(arr2); // [1, 2, 3, 4] arr2 = [4,5,6]; console.log(arr1); // [1, 2, 3, 4] console.log(arr2); // [4,5,6]
函数参数问题:当参数是复合值(包括基本类型值对象,例:Number(1))时,如下,实际是将引用arr的一个副本赋值给了n,arr仍然指向原值,函数中可以通过引用n来更改arr的值,但是当n更改了指向时,并不影响arr的指向,所以arr最终的值为[1,2,3,4,5]。
function foo(n) { n.push(5); console.log(n); // [1, 2 ,3, 4, 5] n = [6, 7, 8]; n.push(9); console.log(n); // [6, 7, 8, 9]; } var arr = [1, 2, 3, 4]; foo(arr); console.log(arr); // [1, 2, 3, 4, 5];
3. 原生函数
-
3.1 JavaScript中的原生函数
JavaScript中的原生(内建)函数有:String()、Number()、Boolean()、Array()、Object()、Function()、RegExp()、Date()、Error()、Symbol() -
3.2 封装对象包装与拆封
JavaScript中的基本类型值没有.length和.toString()的属性和方法,需要通过封装对象才能访问,此时JavaScript会自动为基本类型值包装成一个封装对象。
可以使用.valueOf()拆封一个封装对象PS:需要注意对封装对象的是否存在或空的判断 var a = new Boolean(false); console.log(!a); // false // 拆封 console.log(a.valueOf()); // false // 自行封装基本类型值,使用不带new的 Object var str = "This is a string.", str2 = new String("string too."), str3 = Object(str); console.log(typeof str, typeof str2, typeof str3)
-
3.3 对于原生函数的使用
尽量不要使用Object(…)/Function(…)/RegExp(…);
创建日期对象必须使用Date(…);创建错误对象(带不带new都可以)主要是为了获得当前运行栈的上下文;
Symbol(…)是具有唯一性的特殊值(基本类型值),使用时不能带new关键字。 -
3.4 原生原型(Prototype)
原生构造函数都有自己的.prototype对象,包含其对应子类型所特有的行为特征(如:String.prototype.trim(…)、Array.prototype.concat(…))
4. 强制类型转换
var a = 1;
console.log((a + ""), typeof (a + "")); // "1", string
-
4.2 抽象值操作
-
4.2.1 toString
负责处理非字符串到字符串的强制类型转换。
基本类型值转化规则:null -> “null”、 undefined -> “undefined”、true -> “true”;数字的字符串化遵循通用规则。
普通对象的字符串化规则:除非自行定义,否则返回内部属性[[Class]]的值。PS:JSON.stringify()在讲对象序列化为字符串时也用到了toString。
如果对象中定义了toJSON()方法, JSON字符串化时会首先调用该方法,然后用定义方法的返回值进行序列化。
字符串、数字、布尔值和 null 的 JSON.stringify(…) 规则与 toString 基本相同var obj = { a: 1 }; obj.toJSON = function() { return { b: 2 } }; console.log(JSON.stringify(obj)); // "{"b":2}" // JSON.stringify(value[, replacer])的可选参数一replacer // 如果replacer是个函数,会对每个属性调用,传递键和值,要忽略某属性时需要返回undefined,否则返回指定值 // 如果replacer是个字符串数组,只会返回该数组中包含的对象属性。 var obj2 = { a:1, b: 2, c: [3,4,5] }, str1 = JSON.stringify(obj2, ["a", "c"]), str2 = JSON.stringify(obj2, function(key, value) { if (key === "c") return; return value; }); console.log(str1); // "{"a":1,"c":[3,4,5]}" console.log(str2); // "{"a":1,"b":2}" // JSON.stringify(value[, replacer [, space]])的可选参数一space // space,指定缩进用的空白字符串,用于美化输出,上限为10,小于1 则没有空格;如果该参数是字符串,则该参数(前十个字母)作为空格 var str3 = JSON.stringify(obj2, null, 4), str4 = JSON.stringify(obj2, null, "eeeeee");
-
4.2.1 toNumber
对字符串的处理基本遵循数字常量的处理规则,处理失败时返回NaN。
如:true -> 1、false -> 0、undefined -> NaN、null -> 0
为了将值转换为相应的基本类型值,抽象操作toPrimitive会首先检查该值是否有valueOf()方法,如果有并返回基本类型值,就使用该值进行强制类型转换,如果没有就使用toString()方法的返回值(如果存在)来进行强制类型转换。
如果valueOf()和toString()方法均不返回类型值,会产生TypeError错误。console.log(Number(undefined)); // NaN console.log(Number(null)); // 0 console.log(Number(true)); // 1 console.log(Number(false)); // 0
假值与假值对象:
(1)假值:undefined、null、-0、+0、NaN、false、"";(转换结果为false);
(2)假值对象:new Boolean(false)、new Number(0)、new String("");(转换结果为true);
(3)真值:假值之外的值console.log(Boolean(0)); // false console.log(Boolean(new Number(0))); // true
-
-
4.3 显示强制类型转换
-
4.3.1(字符串和数字)
PS:+str1是+运算符的一元形式,即只有一个操作符。
PS:~(字位操作“非”,执行toInt32);~x = - (x + 1),只有当x为-1时,会返回假值。var str1 = "3", num1 = Number(str1); // 3 num2 = +str1; // 3 var num3 = 4, str2 = String(num3), // "4" str3 = num2.toString(); // "4" console.log(~-1); // 0 console.log(~-2); // 1 console.log(~2); // 3 var str = "This is a string." if (~str.indexOf('str')) { // 匹配成功 } else { // 匹配失败 }
-
4.3.2 显示强制类型转换(日期和数字)
var d = new Date().getTime(), 1547536767391 d1 = Date.now(), // 1547536767391 d2 = +new Date(); // 1547536767391
-
4.3.3 显示解析数字字符串
解析:允许字符串中含有非数字字符,解析顺序从左到右,遇到非数字字符时停止;
转换:不允许出现非数字字符,否则会失败并返回NaN。var width = "40px"; console.log(parseInt(width)); // 40 console.log(Number(width)); // NaN
-
4.3.4 显示转换布尔值
一元运算符!显示的将值强制转换为布尔值,并将值翻转。
最为常用的方法是 !!var a = "", b = 1; console.log(!!a, !!b); // false true !!a ? true : false; // 三元运算符中
-
-
4.4 隐式类型转换
-
4.4.1 字符串和数字
如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+将进行拼接操作。如果一个操作数是对象(包括数组),则首先对其调用ToPrimitive抽象操作,该抽象操作再调用[[DefaultValue]],以数字作为上下文。console.log( 1 + "2" ); // 12 console.log( 2 + "1" ); // 21 console.log( 1 + "" ); // "1" console.log([1,2,3] + 4); // "1,2,34" var obj = { valueOf: function() { return "This is returned by valueOf" }, toString: function() { return "This is returned by toString" } } console.log(obj + ""); "This is returned by valueOf" console.log(obj + 1); "This is returned by valueOf1"
-
4.4.2 布尔值和数字
function onlyOneTrue() { var sum = 0; for (var i = 0; i < arguments.length; i ++) { if (arguments[i]) { sum += arguments[i]; // sum += Number(!!arguments[i]); // 显示强制类型转换 } } return sum === 1 } onlyOneTrue(false, false, false, true, false); // true
-
4.4.3 隐式强制转换为布尔值
(1)if(…)语句中的条件判断表达式;
(2)for(…; …; …)语句中的条件判断表达式;
(3)while(…)和do…while(…)循环中的条件判断表达式;
(4)… ? … : …中的条件判断表达式;
(5)逻辑运算符 || 和逻辑与 && 左边的操作数(作为条件判断表达式) -
4.4.4 Symbol的强制类型转换
ES6允许从Symbol到字符串的显示强制类型转换,隐式强制类型转换会报错。var s1 = Symbol('s1'); String(s1); // "Symbol(s1)" s1 + ""; // TypeError: Cannot convert a Symbol value to a string
-
-
4.5 宽松相等和严格相等
宽松相等(==)和严格相等(===)的区别: ==允许在相等比较中进行强制类型转换,而===不允许! 如果两个比较值的类型不同,需要考虑是否有强制类型转换的必要,有就用==,没有就===。
-
4.5.1 相等比较操作的性能
如果进行比较的俩值类型不同,==进行强制类型转换;如果俩值类相同,则使用相同的算法。所以除了JavaScript引擎实现上的细微差别,其他都相同。
-
4.5.2 抽象相等
(1)字符串和数字
ES5规范:
1.如果Type(x)是数字,Type(y)是字符串,则返回 x == ToNumber(y)的结果;
2.如果Type(x)是字符串,Type(y)是数字,则返回 ToNumber(x) == y的结果;(2) 其他类型和布尔值间的相等比较
规范定义:
1.如果Type(x)是布尔类型,则返回 ToNumber(x) == y的结果;
2.如果Type(y)是布尔类型,则返回 x ==ToNumber(y)的结果。// 比较过程:true转换成数字1,和"12"进行比较, // 进行隐式强制类型转换成12,和1进行比较,结果是false console.log("12" == true); //false // 尽量使用下列判断方法 if (a) {} if (!!a) {} if (Boolean(a)) {}
(3)null和undefined之间的比较
在 == 中 null 和 undefined 是一回事,可以相互进行隐式强制类型转换。
1.如果x是null,y是undefined,则返回true;
2.如果y是undefined,则返回true。var a = null, b; a == b; // true a == null; //true b == null; // true a == false; // false b == false; // false a == ""; // false b == ""; // false a ==0; // false b == 0; // false
(4)对象和非对象之间的相等比较
1.如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果;
2.如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPromitive(x) == y 的结果。var a = 12, arr = [12]; // 通过ToPromitive抽象操作后,会转换成"12" console.log(a == arr); // true var c = { a: "this is a" }, // 转换成 "[object Object]" d = "[object Object]"; console.log(c == d); // true var a1 = "this is a1", b1 = Object(a1); // ToPromitive进行强制类型转(拆封),并返回 "this is a1" console.log(a1 == b1); // true var a2 = null, b2 = Object(a2); // null 没有封装对象,所以返回一个常规对象 console.log(a2 == b2); // false var a3 = undefined; b3 = Object(a3); // undefined 没有封装对象,所以返回一个常规对象 console.log(a3 == b3); // false var a4 = NaN, b4 = Object(a4); // NaN能被封装为数字封装对象,但拆封后NaN == NaN 返回false console.log(a4 == b4); // false
-
4.5.3 假值的相等比较(特殊情况)
"0" == false; // true(false会转换成数字0) false == 0; // true(同上) false == ""; // true(同上) false == []; // true([]会转换成"") "" == []; // true(同上) "" == 0; // true(""会转换成数字0) 0 == []; // true([]会转换成"")
-
-
4.6 抽象关系比较
a<b中的隐式强制类型转换:
(为了保证安全,应对关系比较中的值进行显示强制类型转换)
1.比较双方首先调用ToPromitive,如果结果出现非字符串,就根据ToNumber规则将双方强制类型转换成数字进行比较;
2.如果比较双方都是字符串,就按字母顺序进行比较;a<=b会被处理为b<a;
3.a<=b会被处理为b<a,并将结果取反,JavaScript中<=是不大于的意思,即!(a>b)处理为!(b<a)console.log(["12"] < [013]); // true(第一条规则) console.log(["12"] < ["013"]); // false(第二条规则, "1" > "0") console.log(["ba"] < ["aa"]); // false(第二条规则) console.log(["12"] >= [013]); // true(处理为!([013] < ["12"])) var a = {foo: 12}, b = {foo: 13}; console.log(a < b); // false console.log(b<a); // false console.log(a == b);// false console.log(a<=b); // true(处理为!(b<a))
5 语法
-
5.1.1 语句的结果值
JavaScript中,语句都有一个结果值,undefined也算。
获得结果值最直接的方法,是在浏览器控制台中输入语句,默认情况下手控制台会显示所执行的最后一条语句的结果值。 -
5.1.2 表达式的副作用
// 最常见的副作用,函数调用 function foo() { a = a +1 }; var a = 1; foo(); // 结果返回值是undefined,a 的值会被改变 // -- 和 ++,在操作数前面时,副作用产生在表达式返回结果之前;在后面时,副作用在产生之后 var a = 1, b = a++; // 首先返回 a,再对 a进行++操作 console.log(a, b); // 2, 1 var c = ++a; // 先对 a进行++操作,再返回a console.log(a, c); // 3,3
-
5.1.3 上下文规则
1.大括号
// 定义对象常量 var obj = { foo: bar() } // 标签 { foo: bar() // 标签语句 } // 标签语句的用处 foo: for(var i = 0; i < 3; i ++) { for (var j = 0; j < 3; j ++) { if (i * j > 6) { break foo; // 跳出 foo循环 } console.log(i, j) } }
2.代码块
// 此时的 {} 是一个常规对象,[]转换成"", {}转换成"[object Object]" [] + {} = "[object Object]"; // 此时的 {} 被当做一个独立的代码块(不执行任何操作,代码块结尾不需要分号),+ [] 被显示强制转换类型转换成0 {} + [] = 0;
3.对象解构
function foo({ a, b, c }) { // console.log(obj.a, obj.b, obj.c) console.log(a, b, c) return { result1: a, result2: b + c } } var { result1, result2 } = foo();
4.else…if和可选代码块
JavaScript中并没有else if 语句,但是if和else只包含单条语句的时候可以省略代码块的{}。// 因此,常用的else if 语句实际上是下面的样子 if (..) { } else { if (..) { } else {} }
-
5.2 运算符优先级
对于 && 和 || 来说,如果左边的操作数能够得出结果,就可以忽略右边的操作数。
// 如果不判断ots是否 if (opts && opts.foo) { //.. }
优先级:&& > || > ? :
a && b || c ? c || b ? a : c && b : a 实际执行顺序是: ( a && b || c ) ? ( c || b ) ? ( a : c && b : a )
-
5.3 自动分号(Automatic Semicolon Insertion)
ASI只在换行符处起作用,而不会在代码行的中间插入分号。
JavaScript解析器发现代码行可能会因为缺失分号而导致错误,那么它就会自动不上分号,并且只有在代码行末尾与换行符之间除了空格和注释之外没有别的内容,才会加上分号。 -
5.4 try…finally
finally中的代码总是会在try之后执行,如果有catch的话,则再catch之后执行。
function foo() { try { console.log("This is try block") } catch(err) { console.log("err", err) } finally { console.log("This is finally block") } }; foo(); // "This is try block" "This is finally block"
-
5.5 switch
switch中判断的变量与case表达式的匹配算法与===(严格相等)相同。
当需要强制类型转换时,要做一些特殊处理。var a = "12"; switch(true) { case a == 10: console.log("10 or '10'"); break; case a == 12: console.log("12 or '12'"); break; default: // 永远执行不到这里 } // "12 or '12'"
除简单值外,case中还可以出现各种表达式,并将表达式的结果和true进行比较。尽管可以使用==,但switch中true和true的比较仍然是严格相等比较。
var a = 1, b = 2; switch(true) { case(a || b == 2): console.log("It has been executed"); break; case true: console.log("This is true"); } // "This is true"