JavaScript 笔记02
接笔记01
5.for / forEach / for-in / for-of
不多 bb,直接看代码。
// 循环数组
let array = ['a', 'b', 'c'];
// 普通 for 循环
for (let index = 0; index < array.length; index++) {
const element = array[index];
console.log(element);
}
// forEach 循环
// 不能使用 break 语句中断循环,也不能使用 return 语句返回到外层函数
array.forEach(element => {
console.log(element);
});
// for-in 循环
// 不推荐,因为这里的 index 不是数字,而是字符串,计算时用到很容易出错
// 遍历出来后的顺序也可能随机
for(const index in array) {
console.log(typeof index);
console.log(array[index]);
}
// for-of 循环
// 这是最简洁、最直接的遍历数组元素的语法
// 这个方法避开了for-in循环的所有缺陷
// 与forEach()不同的是,它可以正确响应break、continue和return语句
for (const value of array) {
console.log(value);
}
// 循环对象
let me = {
name: 'ethan',
sex: 'male',
age: 24,
hobby: 'skate'
};
// for-in 循环
for (const key in me) {
// 先判断是否含有该属性
if (me.hasOwnProperty(key)) {
console.log(key+ ':' + me[key]);
}
}
// for-of 循环,太麻烦,还不如 for-in
for (const key of Object.keys(me)) {
console.log(key+ ':' + me[key]);
}
但是 for-of 除了可以遍历数组和对象,它还可以遍历 string 字符串,以及 ES6 新增的 Map、Set 集合。更详细的可以参考这篇文章。总结下,循环数组不需要 break 或者 return 推荐使用 forEach,需要的话使用 for-of,循环普通对象使用 for-in,剩下的都用 for-of,当然最重要的还是了解他们的异同,在使用的时候灵活选择。
6.关于函数参数
- js 中的函数不在乎参数的个数,也不在乎参数的类型
- js 中的参数其实是一个数组
关于上述两点,见如下代码
function abc (a, b) {
console.log(arguments[0]);
console.log(arguments.length);
}
abc(1); // 1 1
abc(1,2); // 1 2
abc(); // undefined 0
再看下我们把函数的参数去掉
function abc () {
console.log(arguments[0]);
console.log(arguments.length);
}
abc(1); // 1 1
abc(1,2); // 1 2
abc(); // undefined 0
结果是相同的。
可以利用 arguments 这个特性来写个不规定加数个数的加法
function add() {
if(arguments.length === 0){
return 0;
} else {
let total = 0;
for(let i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === 'number') {
total += arguments[i];
}
}
return total;
}
}
console.log(add(1, 3, 5.5, 7, 8, 'a', 10));
有一点需要注意,arguments 实际上不是 array 的实例,而是一个 array 对象。
function abc() {
console.log(arguments);
console.log(typeof arguments);
}
abc(); // {} object
abc(1); // { '0': 1 } object
abc(1,2); // { '0': 1, '1': 2 } object
7.基本类型和引用类型复制值时的区别
这点主要是用来理解引用类型的指针
- 复制基本类型的值时会在变量对象上创建一个新值,然后把这个新值复制到新变量分配的位置上。
- 复制引用类型的值时,复制的其实是一个指针,虽然指针不同,但指针指向的是同一个对象,所以当其中一个引用类型改变了对象,另一个引用类型上的值也会变化。
let num1 = 1;
let num2 = num1;
console.log(num2); // 1
num1 = 2;
console.log(num2); // 1 ,由此可见,改变 num1 并不会影响 num2 的值
let person1 = {
name: 'ethan'
};
let person2 = person1;
console.log(person2.name); // ethan
person1.name = 'ethan1';
console.log(person2.name); // ethan1,由此可见,改变 person1 会影响到 person2 的值,因为他们指向的是同一个对象。
8.检测类型
关于 typeof 在笔记1的第4点中已经写明,但 typeof 主要是用来检测基本数据类型,如果检测的值是一个对象或者 null,都会返回object
,所以当变量是引用类型的时候,最好使用 instanceof,person instanceof RegExp
,返回true/false
。对于数组,还可以使用Array.isArray()
。
9.数组常用 API
方法 | 作用 | 返回值 |
---|---|---|
push() | 将任意数量的参数添加到数组末尾 | 修改后数组的长度 |
pop() | 从数组末尾移除最后一项 | 移除的项 |
shift() | 移除数组的第一项 | 移除的项 |
unshift() | 将任意数量的参数添加到数字前端 | 修改后数组的长度 |
reverse() | 反转数组项的顺序 | 反转后的数组 |
sort() | 排序,默认的基本不用,需要重写 | 排序后的数组 |
concat() | 基于当前数组创建一个新数组 | 新数组 |
splice() | 删除:array.splice(0,2); 2个参数分别是要删除的第一项和要删除的项数 |
从原数组中删除的项组成的数组 |
插入:array.splice(2,0,"red","green"); 从第二项起删除0项,并插入 red 和 green。 |
从原数组中删除的项组成的数组 | |
替换:array.splice(2,1,"red","green"); 参考上一条,很容易看懂 |
从原数组中删除的项组成的数组 | |
indexOf() / lastIndexOf() | 从数组开头/末尾开始查找位于第 n 位的项 | 查找的项 |
every() | 对数组中的每一项运行给定函数 | 如果每一项都返回 true,则返回 true |
filter() | 对数组中的每一项运行给定函数 | 返回该数组中所有返回 true 的项组成的新数组 |
forEach() | 对数组中的每一项运行给定函数 | 无返回值,该方法主要用来遍历数组 |
map() | 对数组中的每一项运行给定函数 | 每次函数调用的结果组成的数组 |
some() | 对数组中的每一项运行给定函数 | 只要有一项返回 true,则返回 true |
reduce() /reduceRight() | 归并方法,看下面的例子吧 | 看例子 |
重写 sort 方法
function compare (value1, value2) {
if(value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
let values = [14,2,53,3,6,1];
values.sort(compare);
console.log(values); // [ 1, 2, 3, 6, 14, 53 ]
迭代方法
let num = [1,2,3,4,5,4,3,2,1];
// every()
let everyResult = num.every((item, index, array) => {
return (item > 2);
});
console.log(everyResult); // false
// filter()
let filterResult = num.filter((item, index, array) => {
return (item > 2)
})
console.log(filterResult); // [ 3, 4, 5, 4, 3 ]
// forEach()
num.forEach(element => {
console.log(element); // 1 2 3 4 5 4 3 2 1
});
// map()
let mapResult = num.map((item, index, array) => {
return (item*2);
})
console.log(mapResult); // [ 2, 4, 6, 8, 10, 8, 6, 4, 2 ]
// some()
let someResult = num.some((item, index, array) => {
return (item > 2);
})
console.log(someResult); // ture
reduce()
let num = [1,2,3,4,5];
let sum = num.reduce((prev, cur, index, array) => {
return prev+cur;
})
console.log(sum); // 15
10.关于 callee(严格模式下会报错)
函数内部有两个特殊的对象:arguments 和 this。callee 就是 arguments 的一个属性。
首先看下非常经典的阶乘函数。
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num-1);
}
}
console.log(factorial(5)); // 120
但这样有个问题,函数的执行和函数名factorial
紧紧耦合在一起了,为了解决这个问题,我们就可以使用arguments.callee
。
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num-1);
}
}
console.log(factorial(5)); // 120
这样,即使改了函数名也不会有任何影响。注意与 caller 区分。
11.函数的 apply()、call() 和 bind()方法
-
apply()
:接受两个参数:一个是在其中运行函数的作用域,另一个是参数数组。
function sum (num1, num2) {
return num1 + num2;
}
function applySum1 (num1,num2,num3) {
return sum.apply(this, [num1, num3]);
}
function applySum2 (num1,num2,num3) {
return sum.apply(this, arguments);
}
console.log(applySum1(10,20,30)); // 40
console.log(applySum2(10,20,30)); // 30
-
call()
:作用和apply()
相同,不同的是第二个参数,不能以数组的形式,必须一个个列出来。
function sum (num1, num2) {
return num1 + num2;
}
function callSum1 (num1,num2,num3) {
return sum.call(this, num1, num3);
}
console.log(callSum1(10,20,30)); // 40
作用:apply()
和 call()
最大的作用是能扩充函数赖以运行的作用域,如下:
window.color = 'red';
let o = {
color: 'green'
};
function printColor () {
return this.color;
}
console.log(printColor.call(window)); // red
console.log(printColor.call(this)); // red
console.log(printColor.call(o)); // green
- 这个方法会创建一个函数的实例,其 this 值会被绑定到传给
bind()
函数的值,如下:
let color = 'red';
let o = {
color: 'green'
};
function printColor () {
console.log(this.color);
}
let objectPrintColor = printColor.bind(o);
objectPrintColor(); // green
12.具体位数的小数处理
自己工作中刚好遇到,要求是四舍五入取小数点后一位。我是这样写的:
s = Math.round(s * 10) / 10 + 'km';
今天一看书中有现成的方法toFix()
:
let s = 3.1415;
s1 = Math.round(s * 10) / 10;
console.log(s1); // 3.1
console.log(typeof s1); // number
s2 = s.toFixed(1);
console.log(s2); // 3.1
console.log(typeof s2); // string
似乎更简单,但数据类型已经改变了。所以书中也建议不要去实例化 Number 类型。当然,你看我工作中使用的场景,其实使用toFix()
也不影响。