1. 代码执行分析
var x = 0;
x = x ++;
console.log( x === window.x ); // true
console.log( x ); // 0
let foo = function () {
var i = 0;
return function () {
console.log(i++);
}
}
let f1 = foo(); // 每次调用foo,都被 return 一个新的函数
let f2 = foo();
f1(); // 0
f2(); // 0
f1(); // 1
let foo = function () {
var i = 0;
return function (i) {
console.log(i++); // console.log(undefined++); -> NaN
}
}
let f1 = foo();
let f2 = foo();
f1(); // NaN
f2(); // NaN
f1(); // NaN
let foo = function () {
let i = 0;
return function () {
console.log(i++);
}
}
let f1 = foo();
let f2 = foo();
f1(); // 0
f2(); // 0
f1(); // 1
let foo = function () {
let i = 0;
return function (i) {
console.log(i++);
}
}
let f1 = foo();
let f2 = foo();
f1(); // NaN
f2(); // NaN
f1(); // NaN
console.log('1: ', i)
for (var i = 0; i < 3; i++) {
console.log('2: ', i);
setTimeout(() => console.log('3: ', i), 0);
}
console.log('4: ', i);
for (let i = 0; i < 3; i++) {
console.log('5: ', i);
setTimeout(() => console.log('6: ', i), 0);
}
console.log('7: ', i);
/*
1: undefined
2: 0
2: 1
2: 2
4: 3
5: 0
5: 1
5: 2
7: 3
3: 3 var 声明的变量没有块级作用域,i 被提升到了全局,第一个 setTimeout 异步回调执行的时候只能从全局获取 i 变量
3: 3
3: 3
6: 0 let 声明的变量有块级作用域,i 被保存在了对应的块级作用域中,第二个 setTimeout 异步回调执行的时候,直接从当前作用域内获取 i 变量
6: 1
6: 2
*/
2. 不同类型的数值进行四则运算:
var c = "10", d = 10, f = false;
var y;
console.log( "c+d: "+(c+d)+", c+f: "+(c+f)+", d+f"+(d+f) ); // c+d: 1010, c+f: 10false, d+f: 10
console.log( d+y ) // NaN
+ (加法运算) | Number(10) | String("light") | boolean(true/flase) | undefined | null | NaN | 总结 |
---|---|---|---|---|---|---|---|
Number(10) | 20 | 10light | 10 + false = 10; 10 + true = 11 | NaN | 10 | NaN | |
String("light") | 10light | lightlight | lighttrue | lightundefined | lightnull | lightNaN | 对字符串做+相当于对字符串进行拼接, 总会将另一个变量转换为字符串 |
boolean(true/flase) | 10 + false = 10; 10 + true = 11 | light + true = lighttrue; light + false = lightfalse | true + true = 2; true + false = 1; false + false = 0 | NaN | true + null = 1; false + null = 0 | NaN | 做四则运算时,true被看做1,false被看做0 |
undefined | NaN | lightundefined | NaN | NaN | NaN | NaN | 只能做字符串拼接 |
null | 10 | lightnull | true + null = 1; false + null = 0 | NaN | 0 | NaN | null 和能转化为数值类型的数据做运算,null 会转化为 0 |
NaN | NaN | lightNaN | NaN | NaN | NaN | NaN | 只能做字符串拼接 |
typeof | number | string | boolean | undefined | object | number |
3. JS中声明一个数组:
var r1 = ["arr1", "arr2", "arr3"];
var r2= new Array("arr1", "arr2", "arr3");
var r3= Array("arr1", "arr2", "arr3");
4. 一个前缀是 "$" 的 js 变量是:合法的 JS 语法,和其他任何字符一样。
5. 几个四舍五入的方法、属性:
- a.toFixed(m) :保留 m 个小数。规律:4舍6入5单独考虑。
- 第 m+1 个小数不是5:4舍6入;
- 第 m+1 个小数是5,看5后面是否还有数字:
- 有:整体表现为4舍5入;
- 没有,就看5前面是奇数还是偶数:
- 奇数:4舍5入;
- 偶数:5舍6入;
(总结:a.toFix(n) 保留 n 位小数,a 恰巧有 n+1 个小数,第 n+1 位是 5,第 n 位是偶数,表现为5舍6入,其它都是4舍5入。注意!chrome 并不是这个规律,暂时没有确定的规律)
- Math.round(a) 对a进行4舍5入,只保留整数部分。
- a.toPrecision(m) 对a进行4舍6入,当最后一位的下一位为5时,就很奇怪了,舍入规则稀奇古怪,避免使用。
var a = 3.1515
var b = 3.1525
var c = 3.1535
var d = 3.1545
var e = 3.1555
var f = 3.1565
var g = 3.1575
var h = 3.1585
var i = 3.1595
var j = 3.1505
console.log( a.toPrecision(4) ); // 3.151
console.log( b.toPrecision(4) ); // 3.152
console.log( c.toPrecision(4) ); // 3.154
console.log( d.toPrecision(4) ); // 3.155
console.log( e.toPrecision(4) ); // 3.155
console.log( f.toPrecision(4) ); // 3.156
console.log( g.toPrecision(4) ); // 3.158
console.log( h.toPrecision(4) ); // 3.159
console.log( i.toPrecision(4) ); // 3.159
console.log( j.toPrecision(4) ); // 3.151
6. typeof的两种用法:
var x = typeof 123;
var x = typeof(123);
console.log( x );
7. null == undefined, null !== undefined
var one;
var two = null;
console.log( one == two, one === two ) // true, false 也就是说null == undefined,null !== undefined
8. +new Date() 等价于调用 Date.prototype.valueOf() 方法,返回“ 从1970年1月1日0时0分0秒(UTC,即协调世界时)到该时间的毫秒数 ”。
console.log( +new Date() ) //1619089280052
9. 经典IIFE定时器问题:
for(var i = 0; i < 5; i++){
(function(i){
setTimeout(function(){
console.log( i );
}, 5000);
})(i)
}
// 0,1,2,3,4
// for循环的每一圈,在IIFE中都传入了参数i,这样对于这个定时器来说,外层的函数作用域内就包含了一个带值的i,调用时就不需要向上查找i
10. eval()的作用:
eval(string)的作用是将string作为js代码执行,然后返回其中的值,如果没有值返回undefined。作用域为调用该函数的执行上下文。
有很多的缺点,例如并不会像普通的js代码那样对其中的代码进行预加载,不过这个缺点可以通过一些手段将其带来的损失降到最低。
11. JSON对象的方法:
JSON对象的方法有两个,一个是将一个JSON字符串转换为一个JSON对象,另一个则是将JSON对象转换为JSON字符串。
将JSON字符串转化为JSON对象:JSON.parse()
var info = '{"name":"guangtailang","age":22}';
var heroGuang = JSON.parse(info);
console.log(heroGuang.name); // guangtailang
将JSON对象转化为JSON字符串:JSON.stringify() 【ify:动词后缀,...化的;stringify:字符串化的,在本环境中,就是将一个JSON对象字符串化为一个字符串】
var heroGuang = {name: "guangtailang", age: 22};
var info = JSON.stringify(heroGuang);
console.log(info); // {name: "guangtailang", age: 22}
console.log( typeof info ); // string
12. 原生JS获取Dom对象的几种方法:
- 通过id获取:getElementById("div1")。 参数是id,所有获取方法的参数均是一个字符串,不能加“ # ”返回一个Dom对象。
- 通过class获取:getElementsByClassName("box")。 参数是类名,不能加“ . ”。返回一个集合。不能直接给集合绑定事件,需要获取到集合中的某一个元素,然后再为元素绑定事件。
- 通过标签名获取:getElementsByTagName("p")。 参数是标签名。返回的也是一个集合。
- 通过name属性获取:getElementsByName("user")。 参数是name属性的值。返回的也是一个集合。只有含有name属性的元素才能通过name属性获取。
- 通过选择器获取:querySelector("#div1")。 参数是一个选择器。只能获取到选择器对应的第一个元素。
- 通过选择器获取:querySelectorAll(".div1")。 参数也是一个选择器。获取到了所有匹配选择器的元素组成的集合。
13. html标签是否可以用过JS的方法来获取?
可以,上面的所有方法均可以获取到html标签。举个栗子:
<!DOCTYPE html>
<html lang="en" id="no1">
<head>
...
</head>
<body>
<script>
var res = document.getElementById("no1");
console.log( res ); // 会将整个html结构打印出来
</script>
</body>
</html>
看一下打印结果
展开当然就是完整的html结构,我就不展开了。
14. 调用一个对象的的属性的两种方法:“[ ]”、“ . ”
var heroGuang = {name: "guangtailang", age: 22};
// 方法一:
console.log( heroGuang['name'] ); // [ ]内的引号可以省略
// 方法二:
console.log( heroGuang.name );
- "[ ]"
- "[ ]"的方法常常用于动态地为一个对象添加属性。
- 其次,正常情况下,我们并不能为对象定义一个以数字开头的属性,但heroGuang['111'] = 222; 的方式可以实现。
heroGuang['111'] = 222;
console.log( heroGuang['111'] ); // 222
15. 下面哪个语句表示调用了一个函数:
function.invoke(...) // invoke并不是js中的方法
function.Execute(...) // Execute并不是js中的方法
function.Apply(...) // 没有大写的用法
function.exec(...) // 匹配字符串
function.apply(...) // 可以用于调用函数
exec:
exec() 方法用于对字符串字符串进行正则匹配。
var str = 'The Quick Brown Fox Jumps Over The Lazy Dog';
var re = /quick\s(brown).+?(jumps)/ig;
// 借助exec方法
var result1 = re.exec(str);
// 借助match方法
var result2 = str.match(re);
console.log( result1 );
console.log( result2 );
对于exec函数来说
- 如果 exec() 找到了匹配的文本,则返回一个结果数组。
- 0:表示与正则表达式相匹配的文本,
- 1:表示与 regExpObject 的第 1 个子表达式相匹配的文本(如果有的话),
- 2:表示与 regExpObject 的第 2 个子表达式相匹配的文本(如果有的话),以此类推。
- index:表示与正则表达式匹配的文本的第一个字符的位置。
- input:则存放的是被检索的字符串 string。
- groups:reg = /(\d+)/ 被 "()" 括住的部分叫做捕获,对应的的英文就叫做 group ,而这个 groups 中列举的就是“有名有姓”的捕获。我们将reg进行修改,reg2 = /(?<name>\d+)/,其中 ?<name> 就表示被捕获的名字为 name,这个时候 groups 中就会多一个 name 的属性。
- 没有找到则返回 null。
在调用非全局的 RegExp 对象的 exec() 方法时,返回的数组与调用方法 String.match() 返回的数组是相同的。
但是,当 regExpObject 是一个全局正则表达式时,exec() 的行为就稍微复杂一些。它会在 RegExpObject 的 lastIndex 属性指定的字符处开始检索字符串 string。当 exec() 找到了与表达式相匹配的文本时,在匹配后,它将把 RegExpObject 的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置。这就是说,您可以通过反复调用 exec() 方法来遍历字符串中的所有匹配文本。当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。
Apply call bind的区别:
先说区别:
- apply 和 call类似,仅在“除第一个参数外的剩余参数”的格式上存在一定差异。call是将剩余所有参数直接罗列进去,而 apply 则是传递了一个,保存了除第一个参数之外的“其他参数”的数组。这个“其他参数”是要传递给原函数,放在原函数的实参之前,作为原函数的实参,供原函数使用的。
- bind 只接收一个参数,这个参数与 apply 第一个参数相同,均表示:调用者的 this 重新指向的对象。
- 但是 bind 不像前两者,执行完关键字所在行就立即调用了原函数,而是会返回一个新的函数。只需要在调用新函数时,将剩余参数放入新函数中即可,具体用法下面会介绍
再说一下适用场合:
- 当我们需要传递的参数不多时,可以选择 call:
fun.call(thisObj, arg1, arg2);
- 当我们需要传递的参数比较多时,可以先将参数放至一个数组,然后选择apply:
args = [arg1,arg2,arg3...];
fun.apply(thisObj, args);
- 当我们想要生成一个新的函数长期绑定某个函数给某个对象使用,就可以选择bind:
var newFun = Fun.bind(thisObj);
newFun(arg1, arg2, ...);
16. 声明提前 & 函数定义的方法
// 函数定义方式一:函数表达式
(
function (){
var x = foo();
var foo = function(){
console.log("foobar");
};
return x; // Uncaught TypeError: foo is not a function
}
)()
// 函数定义方式二:函数声明
(
function (){
var x = foo();
function foo(){
console.log("foobar");
};
return x; // foobar
}
)()
方式一之所以会提示foo is not a function,就是和声明提前有关。
在表达式式的函数声明中,声明一个函数就像声明一个变量一样,仅仅只是将var foo这个声明部分进行提前而已,后面的赋值函数的部分并没有执行。
因此,从上到下依次执行赋值的部分,先调用了foo函数,准备将其赋给了变量x,这个时候foo还仅仅只是一个普通的变量而已,尚未执行赋值函数的操作。下一步将一个函数赋给变量foo,foo才成为一个函数,接下来才可以执行这个函数。
将整个问题可以进行简化,将IIFE去掉,将问题抽象出来,就变成了这个样子:
console.log( y ); // Uncaught ReferenceError: y is not defined
console.log( x ); // undefined
var x = 1;
// 函数声明
foo1(); // foobar1
function foo1(){
console.log("foobar1");
};
// 函数表达式
console.log( typeof(foo2) ); // undefined
foo2(); // Uncaught TypeError: foo2 is not a function
var foo2 = function (){
console.log("foobar2");
};
其实就是在考察我们,声明提前的是哪个部分?
- 打印一个从来没有声明过的变量会报错;
- 打印一个后面会借助 var 声明的变量,仅仅只会提示 undefined;
- 所有 var 声明的变量,会将声明过程提升到最前面;
- 对于一个函数来说,
- 表达式的声明方法,就像声明一个变量一样,赋值之前都是 undefined,赋值之前像调用函数一样执行这个变量,就会报错;
- 但是如果是函数声明的方式,就会将整个函数体进行提前声明,这样不论在何处,我们都可以随时调用这个函数。
17. Object.keys()
var a = {1: "one", 2: "two", 3: "three"};
var b = Object.keys(a);
console.log( b ) // (3) ["1", "2", "3"]
也就是将 a 的所有的 key 组成了一个数组打印了出来。
18. typeof undefined === "undefined" & typeof typeof undefined === "string"
console.log([typeof a, typeof y]); // ['undefined', 'undefined']
var a = [typeof a, typeof y][0];
console.log( typeof a ); // 'string'
var b = typeof typeof a;
console.log( `a: ${a}` ); // 'a: undefined'
console.log( `b: ${b}` ); // 'b: string'
console.log( typeof undefined) // 'undefined'
console.log( typeof typeof undefined) // 'string'
console.log( typeof undefined == undefined ) // false
console.log( typeof undefined === undefined ) // false
console.log( typeof undefined === "undefined" ) // true
console.log( typeof typeof undefined === "string" ) // true
简单总结,typeof 返回的是一个字符串。
19. JavaScript 捕获异常的方法
- try / catch / finally 语句
- window 的 onerror 事件
20. JS获取对象属性的各种方式和区别(自身/原型属性、可枚举/不可枚举)
- Object.keys():返回自身的可枚举属性组成的数组 (不包含symbol)
- Object.getOwnPropertyNames():返回所有的自身属性组成的数组 (不包含symbol)
- Object.getOwnPropertySymbols():返回自身所有的Symbol属性组成的数组
- for...in:以任意顺序迭代一个对象的除Symbol以外的
可枚举属性,包括继承的可枚举属性
21. for...of 和 for...in 的区别
- for let i in obj 是以任意顺序遍历数组、对象的可枚举属性,i 是 key
let arr = ['a', 'b', 'c'];
let obj = {
name: 'Jack',
age: 18,
sex: 'man'
}
console.log('---- for...in... 遍历数组 ----');
for (let i in arr) {
console.log('i: ', i);
console.log('arr[i]: ', arr[i]);
}
console.log('---- for...in... 遍历对象 ----');
for (let j in obj) {
console.log('j: ', j);
console.log('obj[j]: ', obj[j]);
}
---- for...in... 遍历数组 ----
i: 0
arr[i]: a
i: 1
arr[i]: b
i: 2
arr[i]: c
---- for...in... 遍历对象 ----
j: name
obj[j]: Jack
j: age
obj[j]: 18
j: sex
obj[j]: man
- for let value of iterable 是按顺序迭代一个可迭代对象,i 是值
iterable可迭代对象包括:Array、Map、Set、String、TypedArray、arguments
遍历器(Iterator)它是一种接口,为各种不同的数据结构提供统一的访问机制。 任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
它的作用有三个:
一是为各种数据结构,提供一个统一的、简便的访问接口;
二是使得数据接口的成员能够按照某种次序排列;
三是ES6创造了一种新的遍历命令 for...of
循环,Iterator 接口主要提供 for...of
消费
let arr = ['a', 'b', 'c'];
let obj = {
name: 'Jack',
age: 18,
sex: 'man'
}
console.log('---- for...of... 遍历数组 ----');
for (let i of arr) {
console.log('i: ', i);
}
console.log('---- for...of... 遍历对象 ----');
for (let j of obj) {
console.log('j: ', j);
console.log('obj[j]: ', obj[j]);
}
---- for...of... 遍历数组 ----
i: a
i: b
i: c
---- for...of... 遍历对象 ----
/Users/xxx/Desktop/myTest/MyReact/test.js:24
for (let j of obj) {
^
TypeError: obj is not iterable
22. 结构赋值默认值,如果对象中没有该属性,就赋予默认值
var { foo: F, bar: B = "123", car: C = "123" } = { f00: "aaa", bar: "bbb" }
console.log(F); // undefined
console.log(B); // bbb
console.log(C); // 123
console.log(foo);
/*
console.log(foo);
^
ReferenceError: foo is not defined
*/
23. 所有函数严格意义上讲本身都是闭包吗?
是的,所有函数都有 [[Environment]]
隐藏属性,该属性保存了对创建该函数的词法环境的引用