🔒 js相关题目

与javascript有关的面试题汇总

参考资料

https://jirengu.github.io/javascript-tutorial/
http://book.jirengu.com/fe/

引用类型的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = {
a:1,
b:[1,2,3]
}
var x = obj.a;
var y = obj.b;
x = 2;
y.push(4);
obj //{a:1,b:[1,2,3,4]}
x //2
y //[1,2,3,4]

!!画内存图即可解题。值得注意的是,数组是引用类型,所以y会修改obj里的b

this的指向问题

this只有在函数被执行时才能确认
箭头函数中的this是在定义函数的时候就确定了,而不是在执行函数时确定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var s = {
s:'student',
getS:function(){
console.log(this.s);
}
};
var t = {
s:'teacher'
};
var getS = s.getS;
var getS1 = getS.bind(s);
// 写出以下输出结果
s.getS(); //student
s.getS.apply(t); //teacher
getS(); //undefined
getS1.call(t); //getS1被bind过,不能再修改this的指向;student

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
window.number = 2;
var obj = {
number:3,
dbl:(function(){
this.number += 4 //这个this指向window,因为立即执行函数的this始终指向window
return function(){
this.number += 7; //这个this,谁调用,this就指向谁
}
})()
}
var dbl = obj.dbl;
dbl(); //2+4+7=13,此时window.number == 13
obj.dbl();//3+7=10,此时obj.number == 10
console.log(window.number + obj.number) //23

1
2
3
4
5
6
7
8
9
10
11
var name = 'hunger'
var obj = {
name: 'valley',
fn: function() {
function fn2() {
console.log(this.name)
}
fn2() //等同于window.fn2()
}
}
obj.fn() //执行fn2(),等同于window.fn2(),因此this指向window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a = 123;
var obj = {
a:1,
getA:function(){
var self = this;
console.log(this.a); //1
console.log(self.a); //1
(function(){
console.log(this.a); //123,因为立即执行函数中的this指向window
console.log(self.a); //1
})();
}
}
obj.getA();

数组中的this

1
2
3
4
5
6
7
8
var arr = []
for(var i=0; i<3; i++){
arr[i] = function(){ console.log(this) }
}
//数组相当于特殊的对象,此时arr = {'0': function(){}, '1': function(){}, '2': function(){}, length:3}
arr[0]() // this指向这个arr,即[ƒ, ƒ, ƒ]

arr0 等同于 arr.0() ,因此 this 指向arr

箭头函数中的this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var app = {
init() {
var menu = {
init: ()=>{
console.log(this) //this -> menu -> app【箭头函数的this指向父级的】
},
bind() {
console.log(this) //this -> menu
}
}
menu.init()
menu.bind()
}
}
app.init()

异步中的this

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
26
var app = {
fn1() {
setTimeout(function(){
console.log(this) //指向window
}, 10)
},
fn2() {
setTimeout(()=>{
console.log(this) //指向app
},20)
},
fn3() {
setTimeout((function(){
console.log(this)
}).bind(this), 30)
},
fn4: ()=> {
setTimeout(()=>{
console.log(this)
},40)
}
}
app.fn1() //先把fn放入异步队列,等过10ms后执行fn,此时变成window.fn(),所以this指向window
app.fn2() //this指向app,因为箭头函数的this在定义时就确认好了
app.fn3() //this指向app
app.fn4() //this -> app -> window

js类型转换

1
2
3
4
'10'+1 // 101
1+'10' //110
'10'-1 // 9
10-'1' //9

6 个falsy值

0、NaN、’’、false、null、undefined

1
2
3
4
null == false // false
undefined == false //false
null == undefined //true
null === undefined //false

正则表达式

号码段为131到139的11位手机号的验证

1
/^13[1-9][0-9]{8}$/

用正则实现string.trim()去除首尾空格

1
2
3
function trim(str){
return str.replace(/^\s+|\s+$/g, '');
}

正则判断url

匹配邮箱

除了用正则,还可以用什么来匹配邮箱?
前端做了表单验证后,后端还需要再验证一遍吗?

把yyyy-mm-dd格式,替换成mm/dd/yyyy怎么做?

正则表达式中,括号的用法

1
2
3
4
5
6
7
8
9
10
var str = '今天是2017-06-12';
var reg = /(\d{4})-(\d{2})-(\d{2})/; //!!!注意,千万不要全局匹配g
//$0:2017-06-12
//$1:2017
//$2:06
//$3:12
var result = str.replace(reg, "$2/$3/$1");
console.log(result); // "今天是06/12/2017"

变量提升

在进入一个执行环境后,先把 var 和 function 声明的变量前置, 再去顺序执行代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a = 1; //变量声明
function fn(){}; //函数声明
var fn2 = function(){}; //函数表达式
//函数声明会将整个函数提升
//函数表达式只会将var fn2 = undefined 提升
function fn(){
var a=1; //在函数中,用var声明的变量就是局部变量
b=2; //没有用var声明的变量就是全局变量
}
//变量先提升,函数后提升,然后按照执行顺序,当变量与函数同名时,函数名覆盖变量名
var xxx;
function xxx(){}
console.log( xxx ) //函数


1
2
3
4
5
6
7
8
9
10
11
console.log( fn )
var fn = 1
function fn(){}
console.log( fn )
/* 将变量和函数提升,然后再做赋值运算 */
var fn;
function fn(){}
console.log( fn ) //函数fn
fn = 1
console.log( fn ) //1

1
2
3
4
5
6
console.log(i)
for(var i=0; i< 3; i++){
console.log(i)
}
//undefined
//0,1,2 此处不是点击按钮,因此输出0,1,2;如果是点击按钮,那么输出3,因为点击事件是个异步事件,点击时i已全部变成3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var n = 3;
function getN(){
return n;
}
var getN2 = (function(){
n = 4;
var n;
n += 3;
return getN;
function getN(){
return n;
}
})();
getN() //3
getN2() //7

1
2
3
4
5
6
if(!('a' in window)){
var a=1;
}
console.log(a); //undefined
console.log('b' in window) //true
var b;

1
2
3
4
5
6
7
8
9
var a = 1;
var b = 1;
function x(){
var a=b=2; //此处相当于var a=2;b=2;由于b没有var,因此b是全局的
}
x();
console.log(a,b);
//1,2

1
2
3
4
5
6
(function(){
var a=b=1;
})();
console.log(typeof a); //undefined 【a在函数作用域内,因此全局的a is not defined;未定义和undefined的typeof都是undefined】
console.log(typeof b); //number 【b=1,它是全局的】

setTimeout异步题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for(var i=0;i<5;i++){
let j = i;
setTimeout(function(){
console.log(j);
},j*1000);
}
//结果,每隔1s输出0、1、2、3、4
//分析
//i是全局变量,j是块级变量,所以打印出0、1、2、3、4【如果是i,则每隔1s打印出5】
//每隔1s是因为j*1000这个赋值是同步的,function里面的才是异步代码
//i=0;setTimeout(fn,0s)
//i=1;setTimeout(fn,1s)
//i=2;setTimeout(fn,2s)
//i=3;setTimeout(fn,3s)
//i=4;setTimeout(fn,4s)

1
2
3
4
5
6
7
8
9
for(var i=1;i<=3;i++){
setTimeout(function(){
console.log(i)
},0)
}
//4
//4
//4

数组api

forEach,map,filter,some,every

slice和splice

伪数组转数组

1
2
3
4
5
ES5:
var arr = Array.prototype.slice.call(aLi)
ES6:
var arr = Array.from(aLi)

对象api

Object.assign是干嘛的

ES6

ES6新增的数据类型有哪些?

  • Set:类似于数组,但Set中不允许出现重复的元素
  • Map:类似于对象,但Map的key可以是任何数据类型

解构赋值

用ES6解构的方式,将下面代码中的obj.name赋值给n,obj.age赋值给a

1
2
3
4
5
let obj = {name:’韩梅梅’, age:’20’};
let n, a;
//答案:
let {name:n, age:a} = obj;

var,let,const

  • var声明的变量,无块级作用域,且存在变量提升现象,可以重复声明;
  • let声明的变量,有块级作用域,不存在变量提升,不能重复声明;
  • const声明的是常量,如果是基本类型则不能修改,如果是引用类型可以修改。
    1
    2
    3
    4
    5
    6
    7
    //let不能重复声明
    let a=1;
    let a=2; //报错 'a' has already been declared
    //const声明的变量,基本类型的不能更改,引用类型的可以更改
    const obj={x:1}
    obj.x=2;

算法题

快速排序https://segmentfault.com/a/1190000009426421
时间复杂度:O(NlogN)
空间复杂度:O(logN)
稳定性:不稳定

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
26
27
28
function quickSort(arr) {
//如果数组<=1,则直接返回,这句很重要!!!否则数组中只剩一个数的时候会无限递归!!
if (arr.length <= 1) {
return arr;
}
let baseIndex = Math.floor(arr.length / 2);
//找基准,并把基准从原数组删除
let base = arr.splice(baseIndex, 1)[0];
//定义左右数组
let left = [];
let right = [];
//比基准小的放在left,比基准大的放在right
arr.forEach(item=>{
if(item<=base){
left.push(item);
} else{
right.push(item);
}
})
//递归
return [...quickSort(left),base, ...quickSort(right)];
}
var arr = [55,21,86,32,16,96,4]
var result = quickSort(arr)
result //[4, 16, 21, 32, 55, 86, 96]

选择排序https://segmentfault.com/a/1190000009366805
希尔排序https://segmentfault.com/a/1190000009461832
冒泡排序

堆栈http://juejin.im/entry/58759e79128fe1006b48cdfd
队列
链表

递归https://segmentfault.com/a/1190000009857470

编码题

用js实现一个随机打乱数组顺序的函数

1
2
3
4
5
var arr = [4,1,7,3,10,7];
arr.sort(function(){
return Math.random()>0.5 ? 1 : -1;
//用Math.random()函数生成0~1之间的随机数与0.5比较,返回-1或1
})

千分位标注,使得1234567变成1,234,567

1
2
var n = 1234567;
n.toLocaleString()

手写发布订阅

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
let events = {};
let pubsub = {
on(name,callback){
let list = events[name] || (events[name] = []);
list.push(callback);
return this; //用于链式调用
},
off(name){
if(!name){
events = {};//没传入具体的name,则认为是清空全部events
}else{
delete events[name];
}
return this;
},
emit(name,data){
let list = events[name];
if(list){
for(let i=0;i<list.length;i++){
list[i](data);//执行该函数
}
}
return this;
}
};
//必须先on,再emit!!!
pubsub.on('a',data=>{
console.log(data);
}).on('b',data=>{
console.log(data)
})
pubsub.off('a');
//pubsub.off();//不传事件名,则全部清除
pubsub.emit('a','我是a').emit('b','我是b')

编写一个函数,获取最大值

1
2
3
4
5
function getMax(){
return Math.max.apply(null,arguments)
}
getMax(38,28,48,10); //48

sort排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var students = ['小明','小红','小花'];
var scores = { 小明: 59, 小红: 99, 小花: 80 };
students.sort(function(a,b) {
return scores[a]-scores[b]; //按照scores从小到大排序
});
//["小明", "小花", "小红"]
////////////////////////////
[
{ name: "张三", age: 30 },
{ name: "李四", age: 24 },
{ name: "王五", age: 28 }
].sort(function (o1, o2) {
return o1.age - o2.age;
})
// [
// { name: "李四", age: 24 },
// { name: "王五", age: 28 },
// { name: "张三", age: 30 }
// ]

节流,始终以均匀的流速出结果
疯狂点击,始终以每2s的速度打印出1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function throttle(fn,wait=1000){
var timer;
return function(){
var _self = this;
var args = arguments;
if(!timer){
timer = setTimeout(function(){
fn.apply(_self,args);
timer = null;
},wait);
}
}
}
btn.onclick=throttle(function(){
console.log(wait);
},2000)

另一种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function throttle(fn, wait=1000) {
var lastTime = 0;
return function() {
var nowTime = Date.now(); //获取当前时间
if (nowTime - lastTime > wait) {
fn.apply(this, arguments);
lastTime = nowTime;       //更新最后时间
}
}
}
btn.onclick = throttle(function() {
console.log(this);
}, 2000)

防抖,当有下一个事件时,前一个事件取消
疯狂点击,不打印出结果;不点击时才打印出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function debounce(fn,wait=1000){
var timer;
return function(){
var _self = this;
var args = arguments;
clearTimeout(timer); //清除上一个定时器
timer = setTimeout(function(){ //重新开一个新的定时器
fn.apply(_self,args);
},wait);
}
}
btn.onclick=debounce(function(){
console.log(this);
},2000)

递归实现杨辉三角
假设输入3,输出[[1],[1,1],[1,2,1]]

maximum subarray,求数组中,最大连续子数组的和

1
2
3
4
5
6
7
8
9
10
11
12
function maxSubArray(nums) {
if(nums.length === 0) return nums[0];
var p = Math.max(0, nums[0]), max = nums[0];
for(var i = 1; i < nums.length; i++){
p=Math.max(p+nums[i],nums[i])
max = Math.max(p,max)
}
return max;
};
console.log(maxSubArray([-2,1,-3,4,-1,2,1,-5,4]))

代码运行结果题

1
2
3
4
5
6
7
8
9
10
11
12
13
function switchCase(value){
switch(value){
case '0':console.log('0');
case '1':console.log('1');break;
case undefined:console.log('undefined');break;
default:console.log('default');
}
}
// 写出下列输出结果
switchCase(0); // default
switchCase('0'); // 0 1 【执行完case 0之后,由于没有break,继续往下执行】
switchCase(); // undefined

变量提升,函数作用域

1
2
3
4
5
6
7
8
9
var x = 3;
function func(randomize) {
if (randomize) {
var x = Math.random(); //if里声明的x提到外面,初始值为undefined
return x;
}
return x;
}
func(false); // undefined


有了私有属性,则公共属性失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function C1(name){
if(name) this.name = name
}
function C2(name){
this.name = name
}
function C3(name){
this.name = name || 'John'
}
C1.prototype.name = 'Tom'
C2.prototype.name = 'Tom'
C3.prototype.name = 'Tom'
console.log(new C1().name + new C2().name + new C3().name)
//Tom undefined John


1
2
3
4
5
6
7
8
//请定义这样一个函数
function repeat (func, times, wait) {}
//这个函数能返回一个新函数,比如这样用
var repeatedFun = repeat(alert, 10, 5000)
//调用这个 repeatedFun("hellworld")
//会alert十次 helloworld, 每次间隔5秒

知识点:arguments 和 apply的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function repeat(func, times, wait) {
return function () {
var handle, i = 0,arg = arguments
handle = setInterval(function () {
if (i++ === times) {
clearInterval(handle);
return;
}
func.apply(null, arg);
}, wait);
}
}
var repeatFun = repeat(console.log, 4, 1000);
repeatFun("hellworld");


1
2
3
4
5
6
7
8
9
10
var arr = [1,2,3];
//问
arr.map(parseInt);
//等同于
parseInt(1,0) // 1
parseInt(2,1) // NaN
parseInt(3,2) // NaN
//因此答案是1,NaN,NaN

1
2
3
4
5
6
7
8
9
var a = {n:1};
var b = a;
a.x = a = {n:2};
//问
a.x //undefined
//a.x = (a = {n:2})
//此时的a = {n:2},里面没有x,因此a.x == undefined
-------------本文结束感谢您的阅读-------------