# 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])
1
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
1
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. 
1
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]);
1
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);
})();
1
2
3
4
5
6
7
8
9
10

复制代码输出结果:

1
undefined
2
1
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了
})();
1
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
            }
        }
    }
}
1
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
})
1
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);
1
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]);
1
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]);
	}
}
1
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('')
}
1
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)
}
1
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();
1
2
3
4
5
6
7
8

# 12、原生js操作dom元素的class

<div id="test" class="one two">测试class</div>
1

# 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')
1
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')
1
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'))
1
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)
}
1
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) {}
1
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输出结果一样
1
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>
1
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使用快速排序。在数组较短时插入排序更有效率。

# 17、fastClick原理

Last Updated: 11/15/2020, 12:44:37 PM