更详细全面的 JavaScript 讲解
先立一个flag,如果不留校集训的话过年之前把那个三百多行的代码读透了。
var name = zrd;
console.log(`Hello, ${
name}!
这是
多行
字符串`)
//数组操作
a.unshift(); a.shift(); //在开头添加、删除元素
a.splice(x, k, args); //从索引x开始删除k个元素,再在该位置添加args这些元素,返回删除的元素
a.concat(b); //返回a, b合并的数组
[1, 2, 3, 'a'].join('-'); //'1-2-3-a'
var s = [1, 2, 3], t = [1, 2, 3];
s === t //false
//对象
var student = {
name: 'zhangruida',
gender: 'female' //最后一个键值对不在末尾加逗号
};
delete student.gender;
'gender' in student; //false
'toString' in student; //true(该属性是student继承得到的)
student.hasOwnProperty('toString'); //false
Map.has(key); Map.set(key, value); Map.delete(key); Map.get(key); Set.add(key);
var a = ['a', 'b', 'c', 'd'];
for (var i in a) {
console.log(i); //'0', '1', '2', '3' (数组是对象,因此返回属性(不包括length,但是包括你添加的属性))
}
for (var i of a]) {
console.log(i); //'a', 'b', 'c', 'd' (只返回集合本身元素)
}
a.forEach(function(element, index, array) {
//每次迭代自动回调该函数
console.log(element + ', index = ' + index); //A, index = 0...
}
//函数
//以下两种声明方式等价
var abs = function (x) {
...};
function abs(x) {
...}
var test = abs(-1, undefined, 'whatever'); //传入参数可以比定义的参数多或少
function foo(a, b, ...rest) {
console.log(rest); //rest拿到其余所有参数
console.log(arguments); //arguments永远拿到所有参数
}
注意: reverse(), sort(), splice()
都是直接修改原数组。
js中全局变量、函数默认被绑定到windows上。如果不同的js文件用了相同的全局变量或者顶层函数,会导致错误。
for (var i = 0; i < 100; i++) {
...}
i++; //仍可以使用i,因为js局部作用域仅限于函数内部
for (let i = 0; i < 100; i++) {
...}
i++; //SyntaxError (let声明作用域块级作用域的变量)
const PI = 3.1415927; //const常量块级作用域
var [x, [y, z]] = [1, [2, 3]];
[, , z] = [1, 2, 3];
var zrd = {
name: 'ksj', gender: 'male', school: 'No.2 middle school', hobbies: {
first: 'eating', second: 'sleeping'}};
var {
name, school} = zrd; //name = 'ksj', school = 'No.2 middle school'
var {
gender, hobbies: {
second}} = zrd; //gender = 'male', second = 'sleeping'
({
gender, hobbies: {
second: sec}} = zrd;) //second存到sec里;大括号位于开头会被认为是代码块,因此要用小括号括起来
({
name, single=true} = zrd)
var zrd = {
name: 'super little da da',
birth: 2020,
age: function () {
var y = new Date().getFullYear();
return y - this.birth; //这里的this就是zrd这个变量
}
};
但如果这样就会有问题:
function getAge() {
var y = new Date().getFullYear();
return y - this.birth; //这里的this指向window,因此this.birth是undefined,在strict模式下这里的this会指向undefined,会报错
}
var zrd = {
name: 'super little da da',
birth: 2020,
age: getAge()
};
getAge();
函数自带的apply方法,有两个参数,第一个参数是需要绑定的this变量,第二个参数是一个数组表示传入函数的参数。
getAge.apply(zrd, []);
Math.max.call(null, 12, 14, 19); //类似的call,参数分别传入
比如我们想要知道程序调用了多少次Math.max(),我们可以这样做:
var oldMax = window.max, count = 0;
window.max = function () {
count++;
return oldMax.apply(null, arguments);
}
好处是arguments可以看做一个数组,可以直接放在apply里面而不能直接传入max函数。
var a = [1, 2, 3];
var b = a.map(function (x) {
return x * x;
}); // b = [1, 4, 9]
var c = a.map(String); // c = ['1', '2', '3']
注意:map()
的回调函数传入三个参数,分别是 currentValue, index, array
(老三样)。一般来说我们传入的函数只需要一个参数,比如 abs
,但是比如 parseInt(string, radix)
没有忽略第二个参数,因此下面的例子:
var a = ['1', '2', '3'];
var b = a.map(parseInt); // b = [1, NaN, NaN]
因为上述代码相当于分别调用了 parseInt('1', 0), parseInt('2', 1), parseInt('3', 2)
。
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
,其中 f 必须要接受两个参数。
var a = [1, 2, 3];
var b = a.reduce(function (x, y) {
return x + y;
}); // b = 6
var c = a.reduce(function (x, y) {
return x * 10 + y;
}); // c = 123
var a = ['A', '', 'B', null, undefined, 'C', ' '];
var b = a.filter(function (s, index, array) {
return s && s.trim();
}); // b = ['A', 'B', 'C']
去除重复元素:
var a = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];
var b = a.filter(function (s, index, self) {
return self.indexOf(s) === index; //注意 ===
}); //b = ['apple', 'strawberry', 'banana', 'pear', 'orange']
Array.sort()
默认把数字转化为字符串比较。所幸,它也是个高阶函数。自定义函数中,x < y, x == y, x > y
分别返回 -1, 0, 1
。
注意: Array.sort()
是原地排序。
every()
方法可以判断数组的所有元素是否满足测试条件。
find()
方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回 undefined
.
findIndex()
和 find()
类似,也是查找符合条件的第一个元素,不同之处在于 findIndex()
会返回这个元素的索引,如果没有找到,返回 -1
.
forEach()
和 map()
类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。比如依次打印每个元素:arr.forEach(console.log);
一句话来说,闭包就是存储状态(变量)的函数,并且它的状态(变量)可以完全对外隐藏起来。
function lazy_counter(a) {
var temp = function () {
return a.reduce(function (x, y) {
return x + y;
});
};
return temp;
}
ksj = lazy_counter([1, 2, 3]);
这样,ksj
得到的是一个并没有被执行的函数,调用 ksj()
的时候才会执行并返回 6
。可以发现,lazy_sum
中的 temp
可以调用其 a
参数,但在函数外面无法对其进行修改。
如果像下面这样写:
function foo() {
temp = [];
for (var i = 1; i <= 3; i++) {
temp.push(function () {
return i * i;
});
}
return temp;
}
var funcList = foo();
var [f1, f2, f3] = funcList;
f1(), f2(), f3(); // 16, 16, 16
因为调用 f1
的时候才会执行 function () {return i * i;}
,而此时 i
已经变成 4
了。所以返回函数不要引用后续会发生变化的变量。
但是如果 for
中用 let
声明循环变量而不是 var
,就不会有问题,因为此时不同函数中的就不是一个 i
了。
如果需要引用循环变量,还有方法是在创建一层函数,把循环变量作为参数传进去。
function foo() {
temp = [];
for (var i = 1; i <= 3; i++) {
temp.push((function (n) {
return function () {
return n * n;
};
})(i));
}
return temp;
}
上面“创建一个匿名函数并立刻执行”的效果就是让每次循环传入的参数独立。
开一下脑洞,不用数字和运算符实现加法:
// 定义数字0:
var zero = function (f) {
return function (x) {
return x;
}
};
// 定义数字1:
var one = function (f) {
return function (x) {
return f(x);
}
};
// 定义加法:
function add(n, m) {
return function (f) {
return function (x) {
return m(f)(n(f)(x));
}
}
}
var three = add(add(one, one), one);
(three(function () {
console.log('zrd');}))(); // 会打印三次
...
这个脑洞实际上做了这么一回事:
zero(f)(x) = x;
one(f)(x) = f(x);
two(f)(x) = one(f)(one(f)(x)) = one(f)(f(x)) = f(f(x));
three(f)(x) = two(f)(one(f)(x)) = two(f)(f(x)) = f(f(f(x)));
...
five(f)(x) = f(f(f(f(f(x)))));
(x, y) => x * x + y;
(x, ...rest) => {
var sum = x;
for (let i = 0; i < rest.length; i++) {
sum += rest[i];
}
return sum;
}
x => ({
foo: x}) // 返回对象
function* fib(n) {
var a = 0, b = 1;
for (let i = 1; i <= n; i++) {
yield a;
[a, b] = [b, a + b];
}
return 'hahaha';
}
直接调用返回一个 generator 对象。想要获得里面的元素,一种方法是 next
:
var f = fib(3);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 'hahaha', done: true} // done为true时value为return的返回值
还有一种方法是用 for ... of ...
:
for (let x of fib(5)) {
console.log(x);
}