前端面试题集锦1

149 阅读13分钟

1、宏任务微任务

console.log(1);
setTimeout(() => {
    console.log(2);
    process.nextTick(() => {
	console.log(3);
    });
    new Promise((resolve) => {
	console.log(4);
	resolve();
    }).then(() => {
	console.log(5);
    });
});
new Promise(resolve => {
    resolve();
}).then(() => {
    process.nextTick(() => {
        console.log(6);
    })
    new Promise(resolve => {
        resolve();
    }).then(() => {
        console.log(7)
    })
})
process.nextTick(() => {
    console.log(8);
});
new Promise((resolve) => {
    console.log(9);
    resolve();
}).then(() => {
    console.log(10);
});
setTimeout(() => {
    console.log(11);
    process.nextTick(() => {
	console.log(12);
    });
    new Promise((resolve) => {
        console.log(13);
    }).then(() => {
	console.log(14);
    });
});

// 输出:1 9 8 10 7 6 2 4 3 5 11 13 12

2、

<script>
    function side(arr) {
	arr[0] = arr[2]
    }
    function a(a, b, c = 3) {
	c = 10;
	side(arguments);
	return a + b + c;
    }
    console.log(a(1, 1, 1));
</script>
// 输出:12

解析: arguments中c的值还是1不会变成10

3、

var min = Math.min();
var max = Math.max();
console.log(min < max);
console.log(min > max);
// false
// true

解析:Math.min() 返回零个或多个数值中的最小值,如果任一参数不能转化为数值,则返回NaN,如果参数为空,则返回 Infinity;

Math.max() 返回零个或多个数值中的最大值,如果任一参数不能转化为数值,则返回NaN,如果参数为空,则返回 -Infinity

4、

<script>
	var a = 1;
	(function a() {
		a = 2;
		console.log(a);
	})();
</script>
// 输出:ƒ a() {
		a = 2;
		console.log(a);
	}

解析:立即调用的函数表达式(IIFE)有一个自己独立的作用域

IIFE:developer.mozilla.org/zh-CN/docs/…

5、写出执行结果(与上一题比较进行理解)

(function () {
    var a = (b = 5);
})();
console.log(b); 
console.log(a);
输出: // 5   Uncaught ReferenceError: a is not defined
// 需要注意的是在严格模式下,要显式的引用全局作用域:var a = (window.b = 5), 
// 否则会报 Uncaught ReferenceError: b is not defined

6、this指向问题

var fullname = 'a';
var obj = {
fullname: 'b',
    prop: {
	fullname: 'c',
	getFullname: function () {
	    return this.fullname;
	}
    }
}
console.log(obj.prop.getFullname());
var test = obj.prop.getFullname;
console.log(test());
// 输出:c  a

解析:this的指向取决于函数的执行环境;1、getFullname()作为obj.prop对象的方法调用,此时执行环境为这个对象;2、getFullname() 被分配个test是执行环境变成了全局对象(window),因为test是在全局作用域中定义的

7、Object.create()和 delete操作符

<script>
    var obj = {
        hello: 'world'
    }
    var test = Object.create(obj);
    delete test.hello;
    console.log(test.hello);
</script>
// 输出:hello

解析:Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
delete 操作符用于删除对象的某个属性;如果没有指向这个属性的引用,那它最终会被释放。
delete操作只会在自身的属性上起作用delete在删除一个不可配置的属性时,在严格模式下会抛出异常,
非严格模式下返回false
delete能删除的:      
    (1)可配置对象属性      
    (2)隐式声明的全局变量
    (3)用户定义的属性
    (4ES6中,通过constlet声明指定的TDZ(暂时性锁区)对delete也会起作用
delete不能删除的:
    (1)显式声明的全局变量
    (2)内置对象的内置属性
    (3)原型链继承属性

delete删除数组元素:
    删除一个元素时,数组的长度是不会变小,被删除的元素变成undefined

9、函数表达式

var foo = function bar() { return 12 };
console.log(bar());

// Uncaught ReferenceError: bar is not defined

解析:命名函数表达式函数只能在函数体内有效

10、写出执行结果

var x = 1;
if (function f(){}) {
    x += typeof f
}
console.log(x);

// 输出:'1undefined'

解析:运算符中的函数声明在执行阶段是找不到的,typeof返回undefined

11、写出执行结果

function f() {
    return f;
}
console.log(new f() instanceof f); 

// 输出:false

解析:a instanceof b 用于检测a是否为b的实例,由于return f的存在,new f()得到的是f的函数对象,而非f的实例

12、写出执行结果

<script>
	var foo = {
		bar: function() {
			return this.baz;
		},
		baz: 1
	}
	console.log(typeof (f = foo.bar)()); 
</script>

// 输出:undefined

解析:赋值给f,f()的执行环境为全局作用域,得到undefined,typeof undefined得到undefined(可与第6题搭配理解)

13、js模块化规范

一、script引入

即将所有js文件都简单的放在一起,由于每一个模块都暴漏在全局,会污染全局作用域,
并且依赖方式不明显

二、CommonJS规范:

通过require方法同步加载所依赖的其他模块,然后通过 exportsmodule.exports
来到处需要暴漏的接口(如下)

var i = 6;
var foo = function (v) {
    return i + v
}
module.exports = {
    i,
    foo
}

CommonJS规范在服务端完成了 js 的模块化,
    1、所有代码都运行在模块作用域,解决了依赖、全局污染的问题,(如果想在多个文件分享变量,必须
       定义global对象的属性 global.waring=true2、模块可以多次加载,即只在第一次加载时运行一次,后续加载直接读取缓存,清除缓存则模块重新运行
    3、模块的加载顺序即为在代码中出现的顺序

CommonJS主要是浏览器端js的模块化(同步加载),在服务器端文件都保存在硬盘上,同步加载没有问题,
但是在浏览器端文件需要从服务器端请求获得,那么同步加载就不适用了

三、AMD规范。

非同步加载模块,允许指定回调函数,适用于浏览器环境,通过requireJS实现
通过 define 定义模块,通过 require 加载模块

define(id?, [dependencies?], callback)
require([module], callback)

使用requirejs可以在浏览器中并行加载多个模块,但是必须提前加载所有依赖

三、CMD规范

实现js库为 sea.js
实现了浏览器的异步加载,可以实现延迟加载(依赖就近)

define(function(require, exports, module) {
    var $ = require('jquery');
    // $(...) 
    module.exports = ...
})

AMD和CMD区别:

AMD是对依赖的模块提前执行,CMD是延迟执行,前者推崇依赖前置,后者提倡依赖就近,即用到某模块时
再require,如上,用到 jquery 时,才require('jquery')

四、ES模块化

通过 import 引入,通过 export 导出,由于部分浏览器暂不支持,需要使用 babel 进行编译

14、SPA单页面

前后端分离,初次加载耗时多,单页应用在一个页面展示所有内容,对于SEO有天然弱势,不适用于浏览器的
前进后退逻辑,页面切换需要自己建立堆栈逻辑

18、

<script>
    const num = {
        a: 10,
        add() {
            return this.a + 2;
        },
        reduce: () => this.a - 2
    }
    console.log(num.add());
    console.log(num.reduce());
</script>

// 输出:12 NaN

解析:对于箭头函数来说,this关键字指向它所在的上下文环境(定义时的位置),num定义时的环境,this.a 为 undefined,最后输出 NaN

19、call、bind

<script>
	const person = { name: 'tom' };
	function sayHi(age) {
		return `${this.name} is ${age}`
	}
	console.log(sayHi.call(person, 5));
	console.log(sayHi.bind(person, 5));
</script>

// 输出:tom is 5 
//       ƒ sayHi(age) {
//           return `${this.name} is ${age}`
//	 }

解析:bind返回绑定上下文环境的函数的拷贝值,不会立即执行

20、['1', '2', '3'].map(parseInt)

['1', '2', '3'].map(parseInt)

// [1, NaN, NaN]

解析: parseInt(string,radix) 解析一个字符串并返回指定基数的十进制整数, 

radix 是2-36之间的整数,表示被解析字符串的基数。如果省略或者为0,则按10进制进行解析。如果string参数的第一个字符不属于radix指定进制下的字符,则返回NaN

parseInt('1', 0)、parseInt('2', 1)、parseInt('3', 2)

21、[typeof null, null instanceof Object]

[typeof null, null instanceof Object]

// 输出:["object", false]

解析:typeof返回列表

console.log(typeof undefined) // undefined
console.log(typeof null)      // object
console.log(typeof String)    // function  
console.log(typeof Boolean)   // function  
console.log(typeof Number)    // function  
console.log(typeof Symbol)    // function  
console.log(typeof Object)    // function  
var a = null;
console.log(typeof a); // object
var a = undefined;
console.log(typeof a); // undefined
var a = '1234';
console.log(typeof a); // string
var a = false;
console.log(typeof a); // boolean
var a = 1;
console.log(typeof a); // number
var a = Symbol(3.14);
console.log(typeof a); // symbol
var a = {};
console.log(typeof a); // object

instanceof运算符用来检测 constructor.prototype 是否存在于参数object的原型链上

22、

<script>
    function f() {}
    const a = f.prototype, b = Object.getPrototypeOf(f);
    console.log(a === b);
</script>

// 输出:false

解析:f.prototype是使用new关键字创建的 f 实例的原型,而Object.getPrototypeOf(f)是 f 函数的原型

 a === Object.getPrototypeOf(new f()) // true

 b === Function.prototype // true

23

<script>
	function showCase(value) {
		switch(value) {
		case 'A':
			console.log('Case A');
			break;
		case 'B':
			console.log('Case B');
			break;
		case undefined:
			console.log('undefined');
			break;
		default:
			console.log('Do not know!');
		}
	}
	showCase(new String('A'));
</script>

// 输出:Do not know!

解析:switch 是严格比较,String实例和string字符串不一样

var str1 = '1234';

var str2 = new String('1234');

console.log(typeof str1);    // string

console.log(typeof str2);    // object

24

<script>
    console.log([2, 1, 0].reduce(Math.pow));
    console.log([].reduce(Math.pow));
</script>

// 输出:1 Uncaught TypeError: Reduce of empty array with no initial value

解析:Math.pow(2, 1) => 2     Math.pow(2, 0) => 1

25

<script>
	function test() {
		var a = 1;
		return function() {
			eval("");
		}
	}
	test()
</script>

变量a会被GC(垃圾回收)吗?

不会,应为 eval 会欺骗词法作用域,例如function() {eval("var a = 1234")} 创建了一个变量,不确定
eval是否对a进行了引用,所以为了保险,不对其进行优化。try catchwith也不会被回收,with会创建新的
作用域。

26

<script>
	const value = 'Value is ' + !!Number(['0']) ? 'test' : 'undefined';
	console.log(value);
</script>

// 输出:test

解析:+ 的优先级大于 ?

'Value is false' ? 'test' : 'undefined'

27

<script>
	var arr = [0, 1];
	arr[5] = 5;
	var newArr = arr.filter(x => {
		return x === undefined;
	})
	console.log(newArr.length);
</script>

// 输出: 0

解析:

28、宏任务微任务补充(可与第一题比较理解)

<script>
	async function async1() {
		console.log('async1 start');
		await async2();
		console.log('async1 end');
	}
	async function async2() {
		console.log('async2');
	}
	console.log('script start');
	setTimeout(function() {
		console.log('setTimeout');
	}, 0)
	async1();
	new Promise(function (resolve) {
		console.log('promise1');
		resolve();
	}).then(function() {
		console.log('promise2');
	});
	console.log('script end');
</script>

// 输出:script start
        async1 start
        async2
        promise1
        script end
        async1 end
        promise2
        setTimeout

解析:

1、首先,事件循环从宏任务(macrotask)队列开始,这个时候,宏队列中只有script(整体代码)任务;
   从宏任务队列中取一个任务出来执行。
        a.首先执行console.log('script start'),输出 'script start'。
	b.遇到setTimeout把console.log('setTimeout')放到macroTask队列中。
	c.执行async1(),输出'async1 start''async2',把console.log('async1 end')放到micro队列中。
	d.执行到promise,输出'promise1',把console.log('promise2')放到micro队列中。
	e.执行console.log('script end')。输出'script end'
2、macrotask执行完之后执行microtask,把microtask quene里面的microtask全部拿出来一次性执行完,
   所以输出 'async1 end''promise2'
3、开始新一轮事件循环,去除macrotask执行,所以会输出 'setTimeout'

29、如何让(a == 1 && a == 2)成立

var a = {
    value: 1,
    toString: function() {
        return this.value++;
    }
}

console.log(a == 1 && a == 2) // true

30、类数组

<script>
	const obj = {
		'2': 3,
		'3': 4,
		'length': 2,
		'splice': Array.prototype.splice,
		'push': Array.prototype.push
	}
	obj.push(1);
	obj.push(2);
	console.log(obj);
</script>

// 输出:Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]

解析:

这个obj中定义了两个key值,分别为splicepush分别对应数组原型中的splicepush方法,因此这个obj可以
调用数组中的pushsplice方法,调用对象的push方法,push(1) ,因为此时obj中定义 length2,所以从
数组的第三项(下标为2)开始插入,这时已经定义了下标为23这两项,所以它会替换第三项,也就是下标为2的
值,两次push执行完毕,key为23的属性值分别为12。此时输出结果为:
Object(4)[empty × 2, 1, 2, splice: f, push: f],因为只定义了23两项,所以前面会是 empty

31

<script>
	let a = {n: 1};
	let b = a;
	a.x = a = {n: 2};
	console.log(a.x);
	console.log(b.x);
</script>
// 输出:undefined  {n: 2}
  1. '.'的优先级大于等号的优先级
  2. 对象以指针的形式进行存储,每个新对象都是一份新的存储地址

解析:

先执行 ‘.’,所以此时 a 和 b 都是 { n: 1, x: undefined }

'=' 从右向左执行,a = { n: 2 },此时a指向 { n: 2 }

然后执行 a.x = a,由于 a.x 是最开始执行的,已是 { n: 1, x: undefined },不是开始的那个旧的a,而b的地址和旧的a地址一样

32

<script>
	var a1 = {}, b1 = '123', c1 = '123';
	a1[b1] = 'b';
	a1[c1] = 'c';
	console.log(a1[b1]);
	var a2 = {}, b2 = Symbol('123'), c2 = Symbol('123');
	a2[b2] = 'b';
	a2[c2] = 'c';
	console.log(a2[b2]);
	var a3 = {}, b3 = {key: '123'}, c3 = {key: '456'};
	a3[b3] = 'b';
	a3[c3] = 'c';
	console.log(a3[b3]);
</script>

// 输出:c b c
  • 对象的键名只能是字符串或 Symbol 类型
  • 其它类型的键名会被转化为字符串类型
  • 对象转字符串会默认调用 toString 方法

解析:任何 Symbol 类型的值都是不相等的,对象类型的键值会被转为 "[object Object]"

另:Objects和Map类似,都是键值对存储数据

  1. Map键值可以是任意值,Object只能是字符串或 Symbol
  2. Map中的键值是有序的,遍历时是按插入的顺序

33

<script>
	function Foo() {
		Foo.a = function() {
			console.log(1);
		}
		this.a = function() {
			console.log(2);
		}
	}
	Foo.prototype.a = function() {
		console.log(3);
	}
	Foo.a = function() {
		console.log(4);
	}
	Foo.a();
	let obj = new Foo();
	obj.a();
	Foo.a();
</script>

// 输出:4 2 1

解析:

Foo.a() 调用 Foo 函数的静态方法a,Foo内部有优先级更高的属性方法,但 Foo 没有被调用, new Foo() 返回函数实例对象,Foo函数内部的属性方法初始化,原型方法建立。 

 obj.a()调用 obj 实例上的方法 a,其内部有属性方法a,原型方法a,重名时,原型方法优先级高

Foo.a()同上

34

<script>
	function user(obj) {
		obj.name = 'Hello';
		obj = new Object();
		obj.name = 'World!';
	}
	let person = new Object();
	user(person);
	console.log(person.name); // Hello
</script>

// 输出:Hello  

// obj = new Object() 将obj指向了一个新的对象

35

<script>
	let x, y;
	try {
		throw new Error();
	} catch (x) {
		x = 1;
		y = 2;
		console.log(x);
	}
	console.log(x);
	console.log(y);
</script>

// 输出: 1 undefined 2

解析:catch 块接收参数 x,当我们传递参数时,这与变量的 x 不同。这个变量 x 是属于 catch 作用域的。 需要注意的是 catch 的作用域,其实并不是常见的块作用域,并不能绑定自己的内部声明的变量。 catch创建的块作用域,只对catch的参数有效。对于在内部声明的变量,catch并没有创建一个新的 作用域,只是一个普通的代码块。

36、(与33题结合理解)

<script>
	function fn() {
		getValue = function () { console.log(1); };
		return this;
	}
	fn.getValue = function () { console.log(2) }
	fn.prototype.getValue = function () { console.log(3) }
	var getValue = function () { console.log(4) }
	function getValue() { console.log(5) }

	getValue();
	fn().getValue();
	getValue();
	new fn.getValue();
	new fn().getValue();
</script>

// 输出:4 1 1 2 3

解析:

  1. 变量提升,var getValue 覆盖了function getValue,所以输出 4
  2. fn()执行,内部的 getValue又一次覆盖,返回this,this只有在函数执行时在能确定,直接调用方式,this指向window,然后fn().getValue() 得到1(覆盖之后的)
  3. 输出1,(两次覆盖)
  4. 点的优先级高于new,new (fn.getValue()) ,输出2
  5. 输出3

37

<script>
	let length = 10;
	function fn() {
		console.log(this.length);
	}
	var obj = {
		length: 5,
		method: function (fn) {
			fn();
			arguments[0]();
		},
		fn: function() {
			console.log(this.length);
		}
	};
	obj.method(fn, 1);
	obj.fn()
</script>

// 输出: 0 2 5

解析:**任意函数里如果嵌套了非箭头函数,那这个时候嵌套函数里的this在未指定的情况下,应该指向的是window 对象,**所以这里执行fn会打印window.length, let 声明的变量会形成块级作用域,且不存在声明提升。则 length属性并没有添加到window对象中。 

argument0 在方法调用中,执行函数体的时候,作为属性访问主体的对象和数组便是其调用方法内this的指向。 (通俗的说,调用谁的方法,this指向谁)

38

<script>
	var a = 10;
	var foo = {
		a: 20,
		bar: function () {
			var a = 30;
			return this.a;
		}
	}
	console.log(foo.bar());
	console.log((foo.bar)());
	console.log((foo.bar = foo.bar)());
	console.log((foo.bar, foo.bar)());
</script>

// 输出:20 20 10 10

解析:

  1. foo.bar(),foo调用,this指向foo,输出20
  2. 括号的作用只是改变表达式的运算顺序,输出20
  3. 重新给foo.bar定义,即 foo.bar = function() { var a = 30; return this.a },此时foo.bar是在window作用域下,输出10
  4. 逗号运算符,求解过程:先计算表达式1的值,在计算表达式2的值...计算表达式n的值,最后逗号运算符的返回值是表达式n的值。经过逗号运算符之后,就是纯粹的函数了,不再是对象方法的引用,所以此时this指向window,输出10

知识点:

  • 默认绑定:独立函数调用时,this指向全局对象window,严格模式下,全局对象无法使用默认绑定,this绑定至undefined
  • 隐式绑定:函数this指向调用者(隐式指向),obj.foo() this ->obj、obj1.obj2.foo() this -> obj2
  • 隐式丢失:指的是函数中this丢失绑定对象的情况,即应用默认绑定规则,从而将this绑定到window或undefined上,以下情况会发生隐式丢失: 

       (1)绑定至上下文对象的函数被赋给一个新函数,然后调用这个新函数时

       (2)传入回调函数时

  • 显示绑定:call()、apply()和bind()
  • new绑定:构造函数的this是new之后的新对象(构造器)

39

<script>
	function getName() {
		for(let i = 0; i < 5; i++) {
			setTimeout(function() {
				console.log(i)
			}, i * 1000)
		}
		return 
                {
			name: 'whl'
		}
	}
	console.log(getName());
</script>

// 输出: undefined 0 1 2 3 4
// 数字的输出间隔1秒,改成var则每间隔1秒输出5(五次)

解析:return后面换行了

40、const num = parseInt('2*4', 10);

<script>
	const num = parseInt('2*4', 10);
	console.log(num);
</script>

// 输出:2

41、Object.defineProperty

<script>
	const company = { name: 'Hello' };
	Object.defineProperty(company, 'address', { value: 'World' });
	console.log(company);
	console.log(Object.keys(company));
</script>

// 输出:{ name: 'Hello', address: 'World' }  ['name']

解析:Object.defineProperty ()直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。新属性默认不可枚举,修改,可通过以下方式修改:

Object.defineProperty(company, 'address', {
    value: 'World',
    configurable: true, // 该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
    enumerable: true, // 可枚举
    writable; true, // 可以被赋值运算符改变
})

42、自增运算符

<script>
	let num = 10;
	const increaseNumber = () => num++;
	const increasePassedNumber = number => number++;
	const num1 = increaseNumber();
	const num2 = increasePassedNumber(num1);
	console.log(num1);
	console.log(num2);
	console.log(num);
</script>

// 输出:10 10 11

解析:++先返回操作数,再执行累加值

43

<script>
	const value = { number: 10 };
	const multiply = (x = {...value}) => {
		console.log(x.number *= 2);
	};
	multiply(); // 20
	multiply(); // 20
	multiply(value); // 20
	multiply(value); // 40
</script>

44

<script>
	[1, 2, 3, 4].reduce((x, y) => console.log(x, y));
</script>

// 输出:1, 2  undfined, 3  undefined, 4

解析:无返回值,默认返回 ‘undefined’,如果改为以下形式:

<script>
	let sum = [1, 2, 3, 4].reduce((x, y) => {
		console.log(x, y)
		return x + y;
	});
	console.log('sum: ', sum);
	// 1, 2  
	// 3, 3  
	// 6, 4  
	// sum: 10
</script>

45

// index.js
console.log('running index.js');
import { sum } from './sum.js';
console.log(sum(1, 2));

// sum.js
console.log('running sum.js');
export const sum = (a, b) => a + b;

// 输出:running sum.js  running index.js  3

解析:import是编译阶段执行,因此被导入的模块会先执行,导入模块的文件会后执行。

这是CommonJS中require()和import之间的区别,使用require(),可以在运行代码时根据需要加载依赖项,如果使用require,则会依次打印:running index.js  running sum.js  3

46

<script>
	function addToList(item, list) {
		return list.push(item);
	}
	const result = addToList('Hello', ['World']);
	console.log(result);
</script>

// 输出: 2

解析:push() 方法返回新数组的长度,如果想返回新数组,可将addToList()改为:

function addToList(item, list) {
    list.push(item);
    return list
}

47

<script>
	var a = 0;
	if (true) {
		a = 10;
		console.log(a, window.a);
		function a() {};
		console.log(a, window.a);
		a = 20;
		console.log(a, window.a);
	}
	console.log(a);
</script>

// 输出
// 10 0
// 10 10
// 20 10
// 10

48

<script>
	var obj = { x: 1, y: 2, z: 3 }
	obj[Symbol.iterator] = function* () {
		yield 'x';
		yield 'y';
		yield 'z';
	}
	console.log([...obj]);
</script>

参考:developer.mozilla.org/zh-CN/docs/…

49、完成safeGet函数

<script>
	function safeGet(obj, str) {
		// ...
	}
	var data = {a: {b: {c: 'hello'}}}
	safeGet(data, 'a.b.c') // hello
	safeGet(data, 'a.b.c.d') // undefined
	safeGet(data, 'a.b.c.d.e.f.g') // undefined
</script>

参考:
(1)
function safeGet(obj, str) {
    var keys = str.split('.');
    try {
        for (var i = 0; i < keys.length; i++) {
            obj = obj[keys[i]]
        }
        return obj;
    } catch (e) {
        return undefined
    }
}
(2)
function safeGet(obj, str) {
    try {
        return str.split('.').reduce((o, k) => o[k], obj)
    } catch (e) {
        return undefined;
    }
}

50、写一个isPrime()函数

function isPrime(number) {
    if (typeof number !== 'number' || !Number.isInteger(number)) {
        return false;
    }
    if (number < 2) {
        return false;
    }
    if (number === 2) {
        return true;
    } else if (number % 2 === 0) {
        return false;
    }
    let num = Math.sqrt(number);
    for (let i = 3; i <= num; i+=2) {
        if (number % i === 0) {
            return false;
        }
    }
    return true;
}