# JS面试题
# 1、['1', '2', '3'].map(parseInt)
第一眼看到这个题目的时候,脑海跳出的答案是 [1, 2, 3],但是真正的答案是[1, NaN, NaN]。
首先让我们回顾一下,map函数的第一个参数callback:
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}
[, thisArg])
2
3
4
这个callback一共可以接收三个参数,其中第一个参数代表当前被处理的元素,而第二个参数代表该元素的索引。
而parseInt则是用来解析字符串的,使字符串成为指定基数的整数。 parseInt(string, radix) 接收两个参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数。
了解这两个函数后,我们可以模拟一下运行情况
parseInt('1', 0) //radix为0时,且string参数不以“0x”和“0”开头时,按照10为基数处理。这个时候返回1
parseInt('2', 1) //基数为1(1进制)表示的数中,最大值小于2,所以无法解析,返回NaN
parseInt('3', 2) //基数为2(2进制)表示的数中,最大值小于3,所以无法解析,返回NaN
2
3
map函数返回的是一个数组,所以最后结果为[1, NaN, NaN]
# 2、null和0的比较
console.log(null > 0); // false // null 尝试转型为number , 则为0 . 所以结果为 false
console.log(null < 0); // false // null 尝试转型为number , 则为0 . 所以结果为 false,
console.log(null >= 0); // true // null 尝试转型为number , 则为0 . 所以结果为 true,
console.log(null <= 0); // true // null 尝试转型为number , 则为0 . 所以结果为 true,
console.log(null == 0); // false // null在设计上,在此处不尝试转型. 所以 结果为false.
console.log(null === 0); // false // null在设计上,在此处不尝试转型. 所以 结果为false.
2
3
4
5
6
关系运算符 和 相等运算符 并不是一个类别的.
关系运算符,在设计上,总是需要运算元尝试转为一个number . 而相等运算符在设计上,则没有这方面的考虑.
最重要的一点, 不要把 拿 a > b , a == b 的结果 想当然的去和 a >= b 建立联系. 正确的符合最初设计思想的关系是 a > b 与 a >= b是一组 . a == b 和其他相等运算符才是一组. 比如 a === b , a != b, a !== b .
# 3、求输出结果
数组和对象的toString()方法
var a = {};
var b = {key: 'b'};
var c = {key: 'c'};
var d = [3,5,6];
a[b] = 123;
a[c] = 345;
a[d] = 333;
console.log(a[b]);
console.log(a[c]);
console.log(a[d]);
2
3
4
5
6
7
8
9
10
答案:345,345,333
解答:对象的key值是一个字符串,b与c都是一个对象,将他们作为a的key值会先转化为字符串,对象转化为字符串后是[object Object],所以a[b]=123就是a['[object Object]']=123,执行a[c] = 345;的时候会把a['[object Object]']重新赋值为345,所以打印a[c]与a[d]就是对a['[object Object]']的取值,结果都是345。数组d转化为字符串的结果是3,5,6,所以a[d] = 333;就是a['3,5,6'] = 333;,取值同理就是取a['3,5,6']的值。
# 4、try..catch程序的输出结果
(function () {
try {
throw new Error();
} catch (x) {
var x = 1, y = 2;
console.log(x);
}
console.log(x);
console.log(y);
})();
2
3
4
5
6
7
8
9
10
复制代码输出结果:
1
undefined
2
2
3
用变量提升的方法,把程序重写并分析如下:
(function () {
var x,y; // 外部变量提升
try {
throw new Error();
} catch (x/* 内部的x */) {
x = 1; //内部的x,和上面声明的x不是一回事!!
y = 2; //内部没有声明,作用域链向上找,外面的y
console.log(x); //当然是1
}
console.log(x); //只声明,未赋值,undefined
console.log(y); //就是2了
})();
2
3
4
5
6
7
8
9
10
11
12
# 5、使用递归的方法,将obj变为obj2的格式
obj = [
{id:1,parent:null},
{id:2,parent:1},
{id:3,parent:2}
]
obj2 = {
obj:{
id: 1,
parent: null,
child: {
id: 2,
parent: 1,
child: {
id: 3,
parent: 2
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
解析:
const obj2 = obj.reverse().reduce((a, b) => {
b.child = a
return b
})
2
3
4
# 6、求输出
function changeObjProperty(o) {
o.siteUrl = "http://www.baidu.com"
o = new Object()
o.siteUrl = "http://www.google.com"
}
let webSite = new Object();
changeObjProperty(webSite);
console.log(webSite.siteUrl);
2
3
4
5
6
7
8
答案: "http://www.baidu.com"
原因: o = new Object() 这句代码切断了原本对 o 的引用
# 7、考Symble
// example 1
var a={}, b='123', c=123;
a[b]='b';
a[c]='c';
console.log(a[b]);
---------------------
// example 2
var a={}, b=Symbol('123'), c=Symbol('123');
a[b]='b';
a[c]='c';
console.log(a[b]);
---------------------
// example 3
var a={}, b={key:'123'}, c={key:'456'};
a[b]='b';
a[c]='c';
console.log(a[b]);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 8、如何遍历对象中非原型链上的属性
方法1:使用Object.keys()
方法2:使用for...in结合hasOwnProperty过滤
//遍历对象和子对象
var obj = {
name: "zhang",
age: 18,
son: {
name: "wang",
age: 2
}
}
for (var prop in obj) {
//排除原型链上的属性
if (obj.hasOwnProperty(prop)) {
console.log(obj[prop]);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 9、实现数字转千分制
每千位加个","
function change(num) {
var arr = num.toString().split(''),
i = arr.length - 1,
count = 0,
result = []
while (i--) {
(result.unshift(arr[i])) && count++
count % 3 == 0 && i != 0 && (result.unshift(','))
}
return result.join('')
}
2
3
4
5
6
7
8
9
10
11
# 10、js实现任意进制转换
// 将m进制的num转成n进制
function change(num, m, n) {
var str = num + ''
return parseInt(str, m).toString(n)
}
2
3
4
5
parseInt(str,radix)
会将字符串str按照radix进制编码方式转换为10进制返回,没有radix,默认为10; 此方法把任意进制字符串转为10进展返回。
toString(n)
会把十进制的数转成n进制。
# 11、怎么清除闭包
如下代码,清除闭包直接让closure=null
即可。
var closure = (function(){
var i = 0;
return function(){
console.log(i++)
}
})();
closure();
2
3
4
5
6
7
8
# 12、原生js操作dom元素的class
<div id="test" class="one two">测试class</div>
# classList
html5增加了classList,classList属性返回元素的类名,作为 DOMTokenList 对象。该属性用于在元素中添加,移除及切换CSS类,classList属性是只读的。
var ele = document.getElementById('test')
// 获取
console.log(ele.classList) // ['one','two']
// 添加
ele.classList.add('three','fourth','five')
// 删除
ele.classList.remove('five','fourth')
// 替换
ele.classList.replace('one','five')
2
3
4
5
6
7
8
9
# className
利用元素的className属性实现。
var ele = document.getElementById('test')
// 获取
console.log(ele.className) // 'one two'
// 添加
ele.className = ele.className.concat(' three')
// 删除
ele.className = ele.className.replace(' three', '')
// 替换
ele.className = ele.className.replace(' two', ' five')
2
3
4
5
6
7
8
9
# class属性
var ele = document.getElementById('test')
var classList = ele.getAttribute('class')
// 获取
console.log(classList) // 'one two'
// 添加
ele.setAttribute('class', classList.concat(' three'))
// 删除
ele.setAttribute('class', classList.replace(' three', ''))
// 替换
ele.setAttribute('class', classList.replace(' two', ' five'))
2
3
4
5
6
7
8
9
10
# 13、for of可以遍历对象吗?for..of和for..in区别
for...of
不能遍历Object对象,因为能够被for...of
正常遍历的,都需要实现一个遍历器Iterator。而数组、字符串、Set、Map结构,早就内置好了Iterator(迭代器),它们的原型中都有一个Symbol.iterator
方法,而Object对象并没有实现这个接口,使得它无法被for...of
遍历。
如何让对象可以被for of 遍历,当然是给它添加遍历器
Object.prototype[Symbol.iterator] = function () {
let _this = this
let keyArr = Object.keys(_this)
let index = 0, len = keyArr.length
return {
next: () => {
let value = keyArr[index]
let done = index >= len
index++
return { value, done }
}
}
}
var person = {
name: 'zls',
age: 20
}
for (let item of person) {
console.log(item)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# for..of和for..in区别
# for..in
一般用于遍历对象的可枚举属性,以及对象从构造函数原型中继承的属性。对于每个不同的属性,语句都会被执行。
如果迭代的对象的变量值是null或者undefined, for...in
不执行循环体,建议在使用for...in
循环之前,先检查该对象的值是不是null或者undefined。
# for..of
for...of
循环用来获取一对键值对中的值,而for...in
获取的是键名。
一个数据结构只要实现了Symbol.iterator
属性, 就被视为具有iterator接口, 就可以使用for...of
循环。
for of 语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
# 14、map和foreach怎么跳出循环,map里面修改了item会改变原数组吗。
forEach和map可以用抛出异常(try/catch)的方式来跳出循环
var arr = [1, 2, 3, 4, 5, 6]
try {
arr.forEach(item => { // 换成map也是
if (item == 3) {
throw Error();
} else {
console.log(item)
}
})
} catch (e) {}
2
3
4
5
6
7
8
9
10
map里面修改了item会改变原数组吗?当数组中元素是值类型,map不会改变原数组;当是引用类型,则可以改变原数组。有没有return item只是会影响返回的结果。
const arr = [
{ name: 'Tom', age: 16 },
{ name: 'Aaron', age: 18 },
{ name: 'Denny', age: 20 }
]
const result = arr.map(item => {
item.age = item.age + 2; // 将他们的年龄都加2
});
console.log('arr', arr); // 数组每项的age都会加2
console.log('result', result); // 返回[undefined,undefined,undefined],
// 如果map循环体内有return item,则result和arr输出结果一样
2
3
4
5
6
7
8
9
10
11
# 15、addEventListener和onclick有啥区别
addEventListener可以给一个事件注册多个监听器,而onclick后面的会覆盖前面的。
<ul id="color-list">
<li id="addEvent">测试addEvent</li>
<li id="on_click">测试on_click</li>
</ul>
<script type="text/javascript">
(function(){
var addEvent = document.getElementById("addEvent");
addEvent.addEventListener("click",function(){
alert("我是addEvent1");
},false);
addEvent.addEventListener("click",function(){
alert("我是addEvent2");
},false);
// 会弹出"我是addEvent1"和"我是addEvent2"
var on_click = document.getElementById("on_click");
on_click.onclick = function() {
alert("我是click1");
}
on_click.onclick = function() {
alert("我是click2");
}
// 只弹出我是click2,前面的会被后面的覆盖
})();
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 16、JS 数组sort方法用的是哪种排序算法
sort使用的是插入排序和快速排序结合的排序算法。数组长度不超过10时,使用插入排序。长度超过10使用快速排序。在数组较短时插入排序更有效率。