1. NaN这个特殊的Number与所有其他值都不相等,包括它自己
如果一个参数值不能转换为一个数字将返回 NaN (非数字值)。
NaN === NaN; // false
唯一能判断NaN的方法是通过isNaN()函数,注意:使用isNaN会先对里面的值使用Number()之后才会判断isNaN()
isNaN(NaN); // true
2. 浮点数在运算过程中会产生误差,因为计算机无法精确表示无限循环小数。要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值:
Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true
3. null和undefined
属性 | 解释 |
---|---|
null | 表示一个“空”的值。它和0以及空字符串’‘不同,0是一个数值,’'表示长度为0的字符串,而null表示“空”。 |
undefined | 它表示“未定义”。 |
4. 字符串是不可变的,如果对字符串的某个索引赋值,不会有任何错误,但是,也没有任何效果
var s = 'Test';
s[0] = 'X';
alert(s); // s仍然为'Test'
5. 直接给Array的length赋一个新的值会导致Array大小的变化
var arr = [1, 2, 3];
arr.length; // 3
arr.length = 6;
arr; // arr变为[1, 2, 3, undefined, undefined, undefined]
arr.length = 2;
arr; // arr变为[1, 2]
6. 如果通过索引赋值时,索引超过了范围,同样会引起Array大小的变化
var arr = [1, 2, 3];
arr[5] = 'x';
arr; // arr变为[1, 2, 3, undefined, undefined, 'x']
7. 如果in判断对象的一个属性存在,这个属性不一定是属于这个对象的,它可能是继承得到的
var xiaoming = {
name: '小明'
};
'toString' in xiaoming; // true
//因为toString定义在object对象中,而所有对象最终都会在原型链上指向object,所以xiaoming也拥有toString属性。
8. 要判断一个属性是否是xiaoming自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法
var xiaoming = {
name: '小明'
};
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false
9. 在多个if…else…语句中,如果某个条件成立,则后续就不再继续判断了。
var age = 20;
if (age >= 6) {
console.log('teenager');
} else if (age >= 18) {
console.log('adult');
} else {
console.log('kid');
}
//输出:teenager
10. JavaScript把null、undefined、0、NaN和空字符串’'视为false,其他值一概视为true
11. for … in循环
把一个对象的所有属性依次循环出来
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
console.log(key); // 'name', 'age', 'city'
}
循环出Array的索引(注意:for … in对Array的循环得到的是String而不是Number)
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}
12. JavaScript引擎自动提升了变量y的声明,但不会提升变量y的赋值。
'use strict';
function foo() {
var x = 'Hello, ' + y;
console.log(x);
var y = 'Bob';
}
foo();
对于上述foo()函数,JavaScript引擎看到的代码相当于:
function foo() {
var y; // 提升变量y的申明,此时y为undefined
var x = 'Hello, ' + y;
console.log(x);
y = 'Bob';
}
13. 名字空间
全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。
例如:
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能。
许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等。
14. this的指向问题
如果以对象的方法形式调用,比如xiaoming.age(),该函数的this指向被调用的对象,也就是xiaoming,这是符合我们预期的。
如果单独调用函数,比如getAge(),此时,该函数的this指向全局对象,也就是window。
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25, 正常结果
getAge(); // NaN
ECMA决定,在strict模式下让函数的this指向undefined
有些时候,喜欢重构的你把方法重构了一下:
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - this.birth;
}
return getAgeFromBirth();
}
};
xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined
结果又报错了!原因是this指针只在age方法的函数内指向xiaoming,在函数内部定义的函数,this又指向undefined了!(在非strict模式下,它重新指向全局对象window!)
修复的办法:我们用一个that变量首先捕获this:
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法内部一开始就捕获this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};
xiaoming.age(); // 25
用var that = this;,你就可以放心地在方法内部定义其他函数,而不是把所有语句都堆到一个方法中。
15. apply:控制 this 的指向
要指定函数的this指向哪个对象,可以用函数本身的apply方法,它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数。
用apply修复getAge()调用:
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
16. call: 控制 this 的指向
call() 与 apply()类似,唯一区别是:
apply()把参数打包成Array再传入;
call()把参数按顺序传入。
比如调用Math.max(3, 5, 4),分别用apply()和call()实现如下:
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
对普通函数调用,我们通常把this绑定为null。
17. 装饰器
利用apply(),我们还可以动态改变函数的行为。
JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
现在假定我们想统计一下代码一共调用了多少次parseInt(),可以把所有的调用都找出来,然后手动加上count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的parseInt():
var count = 0;
var oldParseInt = parseInt; // 保存原函数
window.parseInt = function () {
count += 1;
return oldParseInt.apply(null, arguments); // 调用原函数
};
/*
// 测试:
parseInt('10');
parseInt('20');
parseInt('30');
console.log('count = ' + count); // 3
*/
18. 高阶函数
JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
function add(x, y, f) {
return f(x) + f(y);
}
// add(-5, 6, Math.abs)
// 结果是 11
19. map方法
map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果:
function pow(x) {
return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
console.log(results);
// 1,4,9,16,25,36,49,64,81
注意:map()传入的参数是pow,即函数对象本身。
map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把Array的所有数字转为字符串:
// 只需要一行代码
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
20. reduce 方法
// 语法
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
callback: 执行数组中每个值 (如果没有提供 initialValue则第一个值除外)的函数,包含四个参数:
accumulator:累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue。
currentValue:数组中正在处理的元素。
index:可选,数组中正在处理的当前元素的索引。 如果提供了initialValue,则起始索引号为0,否则从索引1起始。
array:可选,调用reduce()的数组
initialValue: 可选,作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。
Array的reduce()把一个函数作用在这个Array的[x1, x2, x3…]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是:
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
比方说对一个Array求和,就可以用reduce实现:
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x + y;
}); // 25
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x * 10 + y;
}); // 13579
注意,如果数组长度为一的话,要加上默认值
function string2int(s) {
var arr = s.split('');
var b = arr.reduce(function (x, y) {
return x * 10 + y * 1;
}, 0);
return b;
}
// 如果数组长度为一的话,要写默认值,数组的值才会进入函数计算,否则不会进入函数计算,直接返回了
21. filter: 用于把Array的某些元素过滤掉,然后返回剩下的元素。
filter()接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身:
var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
console.log(element); // 依次打印'A', 'B', 'C'
console.log(index); // 依次打印0, 1, 2
console.log(self); // self就是变量arr
return true; // 布尔值判断是否要返回这个值到数组
});
22. sort: 数组排序
Array的sort()方法默认把所有元素先转换为String再排序,结果’10’排在了’2’的前面,因为字符’1’比字符’2’的ASCII码小。
[10, 20, 1, 2].sort(); // [1, 10, 2, 20]
sort()方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。
// 数字大小排序
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
});
console.log(arr); // [1, 2, 10, 20]
23. every() : 方法可以判断数组的所有元素是否满足测试条件。
例如,给定一个包含若干字符串的数组,判断所有字符串是否满足指定的测试条件:
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.every(function (s) {
return s.length > 0;
})); // true, 因为每个元素都满足s.length>0
console.log(arr.every(function (s) {
return s.toLowerCase() === s;
})); // false, 因为不是每个元素都全部是小写
24. find(): 方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.find(function (s) {
return s.toLowerCase() === s;
})); // 'pear', 因为pear全部是小写
console.log(arr.find(function (s) {
return s.toUpperCase() === s;
})); // undefined, 因为没有全部是大写的元素
25. findIndex: findIndex()和find()类似,也是查找符合条件的第一个元素,不同之处在于findIndex()会返回这个元素的索引,如果没有找到,返回-1
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.findIndex(function (s) {
return s.toLowerCase() === s;
})); // 1, 因为'pear'的索引是1
console.log(arr.findIndex(function (s) {
return s.toUpperCase() === s;
})); // -1
26. forEach: 和map()类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach()常用于遍历数组,因此,传入的函数不需要返回值
var arr = ['Apple', 'pear', 'orange'];
arr.forEach(console.log); // 依次打印每个元素
27. 闭包(函数作为返回值)
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。例如,如果不需要立刻求和,而是在后面的代码中,根据需要再计算,则可以不返回求和的结果,而是返回求和的函数!
function lazy_sum(arr) {
var sum = function () {
return arr.reduce(function (x, y) {
return x + y;
});
}
return sum;
}
当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
调用函数f时,才真正计算求和的结果:
f(); // 15
在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为 “闭包(Closure)” 的程序结构拥有极大的威力。
请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false
// f1()和f2()的调用结果互不影响。
28. JS 和 TS(TypeScript) 都没有整数类型,只有number 。
29. null的类型是object,Array的类型也是object,如果我们用typeof
将无法区分出null、Array和通常意义上的object{}。
30. 匿名函数自调用(立即执行)
匿名函数自调用在函数后使用 () 即可,注意要在函数外卖增加() 之后再跟() ,否则会报错
(function () {
var x = "Hello!!";
console.log(x)
})()
31. console.log(typeof(typeof(123))) 输出的是 String,因为第一次typeof返回的是字符串
32. ECS 推荐函数命名要以小驼峰命名法(第一个单词小写后面单词首字母大写)
33. 注意声明函数的时候,不要 var a = b = 1;
这样声明变量,因为 b
会不可控,例如下面的代码
function test(){
var a = b = 1;
console.log(a,b);
}
test();
console.log(b,'test');
可以看到,上面的代码输出以后,可以取到 b
的值,这是因为 b
现在是挂在 windows
上面的,是全局的,所以可以取到。所以不要用 var a = b = 1;
的方式声明变量。
34. 函数表达式(字面量)声明 / 命名
字面量:
var a = 1
,1
就是字面量。
var a = []
,[]
就是字面量。
var a = function(){}
,function(){}
就是字面量。
var test = function test1(){
var a = 1, b = 1;
console.log(a, b);
}
test();
test1();
test1()
报错,是因为声明一个变量把函数赋给他的时候,你就把他赋予了一个函数的功能,所以 test()
可以正常执行。但是当你赋值是一个function
的时候,会自动忽略 function
的名字,所以写与不写 test1
都是可以正常调用,但是test1
,是可以在函数内部调用,相当于递归。所以函数外部 test1 是不可见的,在函数内部是可见的。
35. 函数如果没有写return
,JS默认会增加return
的,所以return
可以没有值,但是函数肯定会有return
。
36. 函数的实参和形参数量可以不相等
function test(a, b, c){
console.log(a, b, c);
}
test(1, 2);
function test1(a, b){
console.log(a, b);
}
test1(1, 2, 3);
结果
37. arguments 可以获得函数的实参,获得一个对象
function test(a, b){
console.log(arguments);
}
test(1, 2, 3);
结果
38. 获取函数形参长度
function test(a, b){
console.log(test.length); // 形参长度
console.log(arguments.length); // 实参长度
}
test(1, 2, 3);
结果
例子:点击查看例子
39. 函数的实参和形参不是一个东西,是不同的量,存储在不同的地方,但是他们之前有映射关系,所以当形参中有对应的实参值的情况下,无论形参怎么赋值,实参都会跟着变;无论实参怎么赋值,形参也会跟着变。
情况一:修改形参,对应实参也改变
function test(a, b){
a = 3;
console.log(arguments[0]);
}
sum(1, 2);
结果
情况二:修改实参,对应的形参也会变
function test(a, b){
a = 3;
arguments[0] = 6;
console.log(a);
}
test(1, 2);
结果
情况三:没有对应实参,修改形参
function test(a, b){
b = 3;
console.log(arguments[1]);
}
test(1);
结果
40. 函数参数默认值
- 实参未传值,形参也没有设置默认值,则该形参的值为undefined
function test(a, b){
console.log(a);
console.log(b);
}
test(2);
- 第一个形参使用默认值,给第二个形参传值
function test(a = 1, b){
console.log(a);
console.log(b);
}
test(undefined, 2);
原理:形参和实参(arguments)是不同的(存储位置都不同),但是他们是有映射关系的,但是对应的形参和实参,无论哪个上面是非undefined
的话,直接取值非undefined
,例如上面,实参上面是undefined
,就选择形参的值,如果形参上是undefined
,就选择实参的值,两个都为undefined
值就是undefined
- 函数直接在形参里面设置默认值是ES6支持的,低版本的浏览器不支持
解决方案:
function test(a, b){
var a = arguments[0] || 1;
var b = arguments[1] || 2;
console.log(a, b);
}
test();
41. 立即执行函数(IIFE)
立即执行函数,立即执行(自动执行),执行完成后就销毁释放。
立即执行函数,一定是表达式才能被执行符号(括号)执行,是可以传递参数和return的。
立即执行函数传递参数
(function(a, b){
console.log(a + b);
})(2, 3);
立即执行函数返回值(return)
var t = (function(a, b){
return a + b;
})(1, 2);
console.log(t);
立即执行函数必须是用于表达式
报错
function test(){
console.log(456);
}();
可以执行
var t = function test(){
console.log(456);
}(6);
console.log(t);
因为现在立即执行函数,相当于是一个表达式,要用分号结束的;
上面代码也证明了,立即执行函数是立即执行,执行完就销毁,所以t
是undefined
,并且输出了456
能被立即执行函数执行的,都是表达式,因为是表达式,所以函数名会被自动忽略,所以会自动变成匿名函数,所以写与不写是一样的
(function test(){
console.log(123);
})();
(function (){
console.log(123);
})();
注:在函数声明之前,增加 + - 都可以,或者 0 || function(){}(); 或者 1 && function(){}(); 等等都会变成表达式,只要是表达式,执行符号就能执行。
42. 逗号(,)是个运算符,只返回逗号最后的一个值
var num = (2 - 1, 6 + 5);
console.log(num);