一、变量和函数同名
1、例题如下
// 下面代码的几种情况的输出是什么?
// 1、情况一:
var a = 1;
function a() {}
console.log(a); // 1
// 2、情况二:
var a = 1;
var a = function(){}
console.log(a); // Function
// 3、情况三:
var a = 1;
(function a() {
// 'use strict'
a = 2;// 无效赋值
console.log(a); // Function
})();
2、知识点:
-
因为函数名称和变量名称相同,如果函数是字面量声明那么a将会是函数,如果是定义声明a是变量
-
函数a自调用将a变量赋值成该函数,且只读,不可改变,所以打印前a存储的是函数
二、变量的隐式转换
1、例题如下
// 下面打印的内容是什么?
var arr = [0];
if (arr) {// 1
console.log('1=>', arr == true);// 2
} else {
console.log('2=>', a);
};
// 结果: 1=> false
// 执行解析
// 1、在【1】的时候实际上进行了Boolean([0])的操作
// 2、在【2】的时候进行了变量的隐式转换
// [0] => [0].valueof() => [0].toString() 即 [0].join() => '0' = 0
2、知识点:
-
一边是布尔值转换成数值进行比较
-
一边是字符串转换成数值进行比较
-
一边是对象,将对象转成原始值
-
valueof()
-
toString()
-
TypeError异常
-
-
null == undefined 是 true
-
NaN和任何值比较都是false
三、对象的属性是string或者Symbol
1、例题如下
// 分别查看下面的输出
// 知识点: 对象的属性是string或者Symbol
var a ={}, b = '123', c = 123;
a[b] = 'b'; // a[b] => a['123'] = 'b'
a[c] = 'c'; // a[c] => a['123'] = 'c'
console.log('片段一:', a[b]); // c
var a ={}, b = Symbol('123'), c = Symbol('123');
a[b] = 'b';
a[c] = 'c';
// Symbol('123') !== Symbol('123')
console.log('片段一:', a[b]); // b
var a ={}, b = {key: '123'}, c = {key: 123};
a[b] = 'b';// a[b] => a[b.toString()]=>a['[object Object]'] = 'b
a[c] = 'c'; // a[c] => a[c.toString()]=>a['[object Object]'] = 'c'
console.log('片段一:', a[b]); // c
四、setTimeout第三个参数
1、例题如下
// 观察输出
for(var i = 0; i < 3; i++) {
setTimeout(_ => {
console.log(i) // 2 2 2
}, 0)
}
for(var i = 0; i < 3; i++) {
setTimeout(i => {
console.log(i) // 0 1 2
}, 0, i)
}
for(let i = 0; i < 3; i++) {
setTimeout(_ => {
console.log(i) // 0 1 2
}, 0)
}
// 使用第二个参数进行前置处理
setTimeout(_ => {
console.log('after...')
}, 1000, setTimeout(() => {
console.log('before...')
}, 0))
五、循环并行与串行
1、例题如下
// 观察下面代码的输出
let getOut = function(n) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(n)
}, 1000)
})
};
let testArr1 = [1, 2, 3];
// testArr1.forEach(async (num, i) => {
// let res = await getOut(num);
// console.log(res);// 将会隔一秒同时输出 1 2 3
// });
// 上面代码的本质是这样:
// (async function(num) {
// let res = await getOut(num);
// console.log(res);
// })(num) // num = 1
// (async function(num) {
// let res = await getOut(num);
// console.log(res);
// })(num) // num = 2
// (async function(num) {
// let res = await getOut(num);
// console.log(res);
// })(num) // num = 3
2、知识点:
-
foreach进行的循环,每一个循环都是独立的作用域,是并行的,是互不影响的
-
将循环变成串行,要么变成一个作用域的同步形式(for循环),要么使用promise的then进行串行
3、串行改写
改成每隔一秒依次输出 1 2 3
-
for循环变成一个作用域下的同步代码
// 1.for循环变成一个作用域下(不能let)的同步代码 async function outputFn1() { for(var i = 0; i < testArr1.length; i++) { let res = await getOut(testArr1[i]); console.log(res) } } // outputFn1(); async function outputFn2() { for(item of testArr1) { let res = await getOut(item); console.log(res) } } // outputFn2();
-
通过promise.then来调用实现串行
// 写法一 function outputFn3(arr) { let count = 0; function inFn() { if (count >= testArr1.length) return; let pro = Promise.resolve(); pro.then(async res => { let result = await getOut(arr[count]); console.log(result); count++; inFn(); }) } inFn() } outputFn3(testArr1) // 写法二 async function test4(x) { var promise= Promise.resolve(); console.log('x=>', x); if (x >= list.length) return; promise.then(async _ => { const res = await square(x); console.log(res); x++; test4(x); }); } test4(0);
六、箭头函数和普通函数this指向
1、例题如下
var name = 'window';
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name);
},
show2: () => console.log(this.name),
show3: function() {
return function() {
console.log(this);
}
},
show4: function() {
return () => console.log(this.name);
}
}
var person2 = { name: 'person2' };
// 知识点
// 1、箭头函数指向是在编译期间创建,在定义那一刻就已经确定,是上下文的this,不可以改变
// 2、普通定义的函数中this是在执行时候确定,可以改变this的指向
// this => person1
person1.show1();// 'person1'
// this => person1 => person2
person1.show1.call(person2);// 'person2'
// this => window
person1.show2();// 'window'
// 箭头函数this指向无法改变
person1.show2.call(person2);// ‘window’
// var fn = person1.show3() => this => window
person1.show3()();// window对象
// var fn = person1.show3() => this => person2
person1.show3().call(person2);// person2对象
// this => person1
person1.show4()(); // 'person1'
// var fn = person.show4() => person1
person1.show4().call(person2);// 'person1'
// this => person2
person1.show4.call(person2)();// 'person2'
2、知识点
-
箭头函数指向是在编译期间创建,在定义那一刻就已经确定,是上下文的this,不可以改变
-
普通定义的函数中this是在执行时候确定,可以改变this的指向
七、函数科里化
1、例题如下
// 1、请实现下面功能的函数
// curring(sumFn)(1)(2)(3)(); // 6
// curring(sumFn)(1, 2)(3, 4)(5)(); // 15
// curring(sumFn)(1,)(2, 3, 4, 5)(6)(); // 21
const sumFn = function(ags) {
// console.log(ags)
return ags.reduce((pre, cur) => {
// console.log(pre + cur);
return pre + cur;
}, 0)
}
function curring(fn) {
let argArr = [];
return function inFn(...args) {
if (args.length > 0) {
argArr = argArr.concat(args);
return inFn;
} else {
return fn(argArr);
};
}
};
console.log(curring(sumFn)(1)(2)(3)());
console.log(curring(sumFn)(1, 2)(3, 4)(5)());
/* 实现以下需求的函数
* const foo = function(...ags) {}
*
* foo(1, 2, 3) === 6 // true
* foo(1)(2, 3) === 6 // true
* foo(1)(2)(3)(4) === 10 // true
*/
function foo(...ags) {
let sum = ags.reduce((pre, nxt) => pre + nxt, 0);
function fn(...args) {
sum += args.reduce((pre, nxt) => pre + nxt, 0);
return fn;
}
fn.toString = function () {
return Number(sum);
}
return fn;
};
let res2 = foo(1)(2, 3);
let res1 = foo(1, 2, 3);
let res3 = foo(1)(2)(3)(4)
console.log('res2:', res2 == 6);
console.log('res1:', res1 == 6);
console.log('res3:', res3 == 10);
2、知识点
-
柯里化通常也被称为部分求值,其含义是给函数分布传递参数
-
每次传递参数进行处理,并返回一个更具体 的函数接受剩余的参数
-
这中间可以嵌套多层这样的接受部分参数函数,直至返回最后的结果
八、数组、字符串的includes方法和indexof
1、例题如下
// 1、NaN includes能匹配数组中的NaN,indexof不能匹配到
let ar1 = [NaN, 1, '3'];
console.log('ar1:', ar1.includes(NaN))// true
console.log('ar1:', ar1.indexOf(NaN))// -1
// undefined includes能匹配到稀疏数组中的undefined,indexof不行
let ar2 = [];
ar2[2] = 1;
console.log('ar2:', ar2.includes(undefined));// true
console.log('ar2:', ar2.indexOf(undefined));// -1
// 2字符串和数组中的indexof方法比较
// 不同点1:字符串会进行类型转换
let arr = ['1', '2', '3', '4'];
console.log('数组中:', arr.includes(1)) // false
console.log('数组中:', arr.includes('1')) // true
let str = '12345';
console.log('str:', str.includes(1)); // true
console.log('str:', str.indexOf(1)); // 0
console.log('str:', str.indexOf('1')); // 0
2、知识点
-
NaN includes能匹配数组中的NaN,indexof不能匹配到
-
undefined includes能匹配到稀疏数组中的undefined,indexof不行
-
字符串和数组中的indexof方法比较
-
字符串会进行类型转换,数组不会
-
八、Object.keys() 方法
1、例题如下
// 1 查看下面代码的输出是
const obj1 = {
'a': 'aa',
'b': 'bb',
'c': 'cc'
}
Object.keys(obj1);
// ['a', 'b', 'c']
// 2 查看下面代码输出
const obj2 = Object.create({}, {
getFoo: {
value: () => this.foo,
enumerable: false
}
});
obj2.foo = 2;
console.log('第二:', Object.keys(obj2))// ['foo']
// 3、继承的属性也可以枚举出来
class Parent {
constructor() {
this.name = 'parent';
}
}
class Child extends Parent {
constructor() {
super();
this.age = 20;
}
}
let instance = new Child();
console.log('继承的属性:', Object.keys(instance)); // ['name', 'age']
// 4、请问下面打印的顺序是什么
const sa = Symbol('a');
const obj3 = {
5: '5',
a: 'a',
1: '1',
c: 'c',
3: '3',
b: 'b',
}
obj3[sa] = 'sa';
console.log('顺序:', Object.keys(obj3))// ['1', '3', '5', 'a', 'c', 'b']
console.log(Object.keys(123))// []
console.log(Object.keys('123'))// ['0', '1', '2']
2、知识点
-
继承的属性也可以枚举出来
-
Number:大于0的整数,进行排序返回,String: 按照定义的顺序返回,Symbol: 过滤掉,不反回
-
Object.keys(null) && Object.keys(undefined) 报错
-
Object.keys(123) => Object.keys(Number(123)) Object.prototype.toString.call(Number(123)) => '[object Number]'
-
Object.keys('123') => Object.keys(String('123')) Object.prototype.toString.call(String('123')) => '[object String]'