面试题总结之JavaScript篇

概念基础类

AJAX中GET和POST的区别

  • 语义上的区别
    GET是获取资源,POST是处理资源
    GET和POST最初被设计的初衷也是如此
  • 现象上的区别
    GET是通过URL请求,POST是通过header请求
    HTTP协议对GET和POST都没有对长度的限制,而对于URL长度上的限制是浏览器跟服务器造成的

let、var、const的区别

博客–let、var、const的区别

如何解决异步回调地狱

DOM结构 —— 两个节点之间可能存在哪些关系以及如何在节点之间任意移动

DOM操作 —— 如何添加、移除、移动、复制、创建和查找节点等

事件 —— 如何使用事件,以及IE和标准DOM事件模型之间存在的差别

XMLHttpRequest —— 这是什么、怎样完整地执行一次GET请求、怎样检测错误

JSON —— 作用、用途、设计结构

new操作符都做了些什么

使用new命令时,它后面的函数调用就不是正常的调用,而是依次执行下面的步骤。

1.创建一个空对象,作为将要返回的对象实例
2.将这个空对象的原型,指向构造函数的prototype属性
3.将这个空对象赋值给函数内部的this关键字
4开始执行构造函数内部的代码

什么是事件代理

事件代理(Event Delegation),又称之为事件委托。是 JavaScript 中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。使用事件代理的好处是可以提高性能。

说说你对MVC和MVVM的理解

MVC

View 传送指令到 Controller

Controller 完成业务逻辑后,要求 Model 改变状态

Model 将新的数据发送到 View,用户得到反馈
所有通信都是单向的。

MVVM

Angular它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。

组成部分Model、View、ViewModel

View:UI界面

ViewModel:它是View的抽象,负责View与Model之间信息转换,将View的Command传送到Model;

Model:数据访问层

引用类型有哪些?非引用类型有哪些

基本类型值(数值、布尔值、null和undefined):指的是保存在栈内存中的简单数据段; 引用类型值(对象、数组、函数、正则):指的是那些保存在堆内存中的对象,变量中保存的实际上只是一个指针,这个指针执行内存中的另一个位置,由该位置保存对象

如下代码输出什么?为什么

1
2
3
4
5
var obj1 = {a:1, b:2};
var obj2 = {a:1, b:2};
console.log(obj1 == obj2);//false 两个对象是占用的两个内存地址
console.log(obj1 = obj2); //a:1 b:2 把obj2的内存地址赋给obj1
console.log(obj1 == obj2);//true obj1内存地址是obj2传递的,所以相等

如下代码输出什么? 为什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a = 1
var b = 2
var c = { name: 'yoowinsu', age: 2 }
var d = [a, b, c]
var aa = a
var bb = b
var cc = c
var dd = d
a = 11
b = 22
c.name = 'hello'
d[2]['age'] = 3
console.log(aa) //11 值传递
console.log(bb) //22 值传递
console.log(cc) //name: 'hello', age: 3 传址传递,只要改变此地址的参数,此对象的参数就会改变
console.log(dd) //[1,2,{name: 'hello', age: 3}] d对象中的a、b是值传递,c是传址传递

如下代码输出什么? 为什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a = 1
var c = { name: 'yoowinsu', age: 2 }
function f1(n){
++n
}
function f2(obj){
++obj.age
}
f1(a)
f2(c)
f1(c.age)
console.log(a)
//1 调用f1函数默认把a值传递给n,改变n的值a不受影响,所以打印结果还是1
console.log(c)
//name: 'yoowinsu', age: 3
//调用f2函数是传址传递, ++obj.age会改变age的值为3
//调用f1函数默认把c.age值传递给n,改变n的值c.age不受影响,所以打印结果还是3

以下代码的输出结果是?为什么?

1
2
3
4
5
6
7
8
9
10
11
console.log(1+1);
//2 数值相加
console.log("2"+"4");
//24 字符串拼接
console.log(2+"4");
//24 数字和字符串相加等同于字符串相加
console.log(+"4");
//4 一元运算符转换为数值类型

下面函数的返回值是什么?

1
2
3
4
5
6
7
function foo2()
{
return
{
bar: "hello"
};
}

返回undefined
对于 return 、break、continue 等语句,如果后面紧跟换行,解析器会自动在后面填充分号(;)

综合实战类

实现下面函数

1
double([1,2,3,4,5]) //[1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
1
2
3
4
5
6
7
function double(arr){
for(i=arr.length-1;i>=0;i--){
arr.splice(i,0,arr[i])
}
return arr
}
double([9,6,3,88,777]) //[9, 9, 6, 6, 3, 3, 88, 88, 777, 777]

写一个函数判断一个字符串是不是回文字符串

1
2
3
4
function isPalindrome(string) {
var str = string.replace(/[^\w]/g, "")
return (str === str.split('').reverse().join(''));
}

编写一个方法 求一个字符串的字节长度

假设:一个英文字符占用一个字节,一个中文字符占用两个字节

1
2
3
4
5
6
7
8
9
function GetBytes(str){
var len = str.length;
var bytes = len;
for(var i=0; i<len; i++){
if (str.charCodeAt(i) > 255) bytes++; //是中文字符就+1
}
return bytes;
}
console.log(GetBytes("我是yoowin")); //10

如何判断某变量是否为数组数据类型?

正则题

var string = “我的账户余额:2,235,467.20”;
console.log(?);
// 请用js计算出我到底有多少钱(输出Number类型数字,代码尽量简洁,考虑通用情况)

作用域

1
2
3
4
5
6
7
8
function person() {
return this.name;
}
var someOne = {
name: 'Jenny',
age: 18
};
// 此处如何输出 'Jenny'

语法题 有一个合法的 JSON 对象(即不包含函数等值的对象),设计一个函数,取出该对象内所有 key 为 “id” 并且其值不为对象、数组的值,装入一个数组并返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function extractIds(data) {
// implement
}
//样例数据:
var data = {
id: 1,
items: [
{ id: 2 },
{ item: 3, id: [
{ id: 4 },
{ id: 5 }
]}
]
};

extractIds(data); // should return [ 1, 2, 4, 5 ]

闭包 下面五段代码分别输出什么?并且什么时候输出什么?

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
for(var i = 0; i < 5; i++) {
console.log(i);
}
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000 * i);
}
for(var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
}
for(var i = 0; i < 5; i++) {
(function() {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
}
for(var i = 0; i < 5; i++) {
setTimeout((function(i) {
console.log(i);
})(i), i * 1000);
}

创建一个二进制相加函数,根据传入的两个二进制数字符串返回一个相加的十进制的结果。

1
2
3
4
5
6
7
function calculate(num1, num2){
// implement here
}
//结果样例:
calculate("10", "10") // => 4
calculate("10", "0") // => 2
calculate("101", "10") // => 7

解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//十进制转其他
var x=110;
alert(x);
alert(x.toString(8));
alert(x.toString(32));
alert(x.toString(16));
//其他转十进制
var x='110';
alert(parseInt(x,2));
alert(parseInt(x,8));
alert(parseInt(x,16));
//其他转其他
//先用parseInt转成十进制再用toString转到目标进制
alert(String.fromCharCode(parseInt(141,8)))
alert(parseInt('ff',16).toString(2));

图片懒加载

实现页面加载进度条

事件委托

实现extend函数

为什么会有跨域的问题以及解决方式

jsonp原理、postMessage原理

实现拖拽功能,比如把5个兄弟节点中的最后一个节点拖拽到节点1和节点2之间

动画:setTimeout何时执行,requestAnimationFrame的优点

手写parseInt的实现:要求简单一些,把字符串型的数字转化为真正的数字即可,但不能使用JS原生的

字符串转数字的API,比如Number()

编写分页器组件的时候,为了减少服务端查询次数,点击“下一页”怎样能确保还有数据可以加载(请求数据不会为空)?

ES6新增了哪些特性,使用过哪些,也有当场看代码说输出结果的

JS模块化的实践

require.js的实现原理(如果使用过webpack,进一步会问,两者打包的异同及优缺点)

promise的实现原理,进一步会问async、await是否使用过

实现gulp的功能

使用前端框架(angular/vue/react)带来哪些好处,相对于使用jQuery

vue双向数据绑定的实现

单页应用,如何实现其路由功能

代码题

(个人觉得这道题真的挺难,全答对需要非常好的功底)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Foo() {
getName = function () { alert (1); };
return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}
//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

答案(详细答案可参考链接

1
2
3
4
5
6
7
Foo.getName(); //2
getName(); //4
Foo().getName(); //1
getName(); //1
new Foo.getName(); //2
new Foo().getName(); //3
new new Foo().getName(); //3

以下代码的输出结果是?

1
2
3
4
var a = 1;
a+++a; //(a++)+a
typeof a+2;
// 输出"number2" typeof的优先级高

以下代码的输出结果是? 为什么

1
2
3
4
5
var a = 1;
var b = 3;
console.log( a+++b );
//4
//a+++b等同于(a++)+b,a++结果还是自身,即1,所以最后结果为1+3,为4

遍历数组,把数组里的打印数组每一项的平方

1
2
3
4
var arr = [3,4,5]
for(i=0;i<arr.length;i++)
{console.log(Math.pow(arr[i], 2))
}

遍历 JSON, 打印里面的值

1
2
3
4
5
6
7
8
var obj = {
name: 'yoowin',
sex: 'male',
age: 28
}
for(b in obj)
{console.log(obj[b])}
//yoowin male 28

以下代码输出结果是? 为什么

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
var a = 1, b = 2, c = 3;
var val = typeof a + b || c >0
console.log(val)
//结果为number2
//优先级依次为:typeof a、>、+、||,
//即typeof a + b || (c >0)
var d = 5;
var data = d ==5 && console.log('bb')
console.log(data)
//结果为undefined
//优先级依次为:==、&&、=,
//console.log('bb')是打印值,其返回值是undefined,并非其打印结果!!!
//即var data = {(d ==5) && console.log('bb')} => var data = true&& undefined => var data = undefined
var data2 = d = 0 || console.log('haha')
console.log(data2)
//结果为undefined
//赋值运算符从右往左
//console.log('haha')是打印值,其返回值是undefined,并非其打印结果!!!
//即var data2 = {d = (0 || console.log('haha'))} =>var data2 = {d = (0 || undefined)} =>var data2 = d = undefined =>var data2 = undefined
var x = !!"Hello" + (!"world", !!"from here!!");
console.log(x)
//结果为2
//括号里的数值作运算时,取最后一个逗号后的值
//var x = true + (false, true); =>var x = 1+ (0, 1); =>var x =1+1

求n!,用递归来实现

1
2
3
4
5
6
7
function num(n){
if(n===1){
return 1
}
return n*num(n-1)
}
num()

以下代码输出什么?

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
function getInfo(name, age, sex){
console.log('name:',name);
console.log('age:', age);
console.log('sex:', sex);
console.log(arguments);
arguments[0] = 'valley';
console.log('name', name);
}
getInfo('paul', 2, '男');
//name:paul
//age:2
//sex:男
//['paul', 2, '男']
//name valley
getInfo('小苏', 3);
//name:小苏
//age:3
//sex:undefined
//['小苏', 3]
//name valley
getInfo('男');
//name:男
//age:undefined
//sex:undefined
//['男']
//name valley

写一个函数,返回参数的平方和?

1
2
3
4
5
6
function sumOfSquares(){
}
var result = sumOfSquares(2,3,4)
var result2 = sumOfSquares(1,3)
console.log(result) //29
console.log(result) //10
1
2
3
4
5
6
7
8
9
function sumOfSquares(){
var num=0;
for(var i=0;i<arguments.length;i++){
num=Math.pow(arguments[i],2)+num;
}
return num;
}
var result = sumOfSquares(2,,4,5,7,8)
console.log(result)

如下代码的输出?为什么

1
2
3
console.log(a);//undefined,因为变量a的声明前置
var a = 1;
console.log(b);//报错,因为未声明a变量

如下代码的输出?为什么

1
2
3
4
5
6
7
8
sayName('world');//hello world,因为函数声明的调用可以在声明之前可以
sayAge(10);//报错,因为函数表达式的调用不能在声明前调用
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};

如下代码输出什么? 写出作用域链查找过程伪代码

1
2
3
4
5
6
7
8
9
10
11
12
var x = 10
bar()
function foo() {
console.log(x)
}
function bar(){
var x = 30
foo()
}
//执行结果是10
//调用foo时打印x是声明函数所在位置的打印,而不是调用位置!!
//foo函数内部没找到x,然后找全局变量,找到x=10

如下代码输出什么? 写出作用域链查找过程伪代码

1
2
3
4
5
6
7
8
9
10
11
var x = 10;
bar() //30
function bar(){
var x = 30;
function foo(){
console.log(x)
}
foo();
}
//执行结果是30
//foo函数内部没找到x,然后往外找,找到x=30

以下代码输出什么? 写出作用域链的查找过程伪代码

1
2
3
4
5
6
7
8
9
10
var x = 10;
bar()
function bar(){
var x = 30;
(function (){
console.log(x)
})()
}
//执行结果为30
//调用bar时自执行函数打印的x在自己作用域中没找到,往外找就找到bar函数作用域中的x=30

以下代码输出什么? 写出作用域链查找过程伪代码

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
var a = 1;
function fn(){
console.log(a)//undefined a声明的提升
var a = 5
console.log(a)//5 fn作用域中a被修改为5
a++
var a
fn3()
fn2()
console.log(a)//20 fn2中的a=20是fn中的局部变量,调用fn2时修改了a的值
function fn2(){
console.log(a)//6 fn2内没找到a的声明,往外找找到变量a=6
a = 20
}
}
function fn3(){
console.log(a)//1 fn3内没找到a的声明,找到全局变量a=1
a = 200
}
fn()
console.log(a)//200 fn3中把全局变量a修改为200,fn2中的a=20是函数fn内的局部变量,不是全局变量

过滤如下数组,只保留正数,直接在原数组上操作

1
2
3
4
5
6
7
8
9
10
11
var arr = [3,1,0,-1,-3,2,-5]
function filter(arr){
for(var i=0;i<arr.length;i++){
if(arr[i]<=0){
arr.splice(i,1);
i--;
}
}
}
filter(arr)
console.log(arr) // [3,1,2]

过滤如下数组,只保留正数,原数组不变,生成新数组

1
2
3
4
5
6
7
8
9
10
11
12
13
var arr = [3,1,0,-1,-3,2,-5]
function filter(arr){
var newArr=[];
for(var i=0;i<arr.length;i++){
if(arr[i]>0){
newArr.push(arr[i])
}
}
return newArr;
}
var arr2 = filter(arr)
console.log(arr2) // [3,1,2]
console.log(arr) // [3,1,0,-1,-2,2,-5]

使用数组拼接出如下字符串

1
2
3
4
5
6
7
8
9
10
var prod = {
name: '女装',
styles: ['短款', '冬季', '春装']
};
function getTplStr(data){
var str="<dl class=\"product\"><dt>"+data.name+"</dt><dd>"+data.styles[0]+"</dd><dd>"+data.styles[1]+"</dd><dd>"+data.styles[2]+"</dd></dl>"
return str;
};
var result = getTplStr(prod); //result为下面的字符串
console.log(result);
1
2
3
4
5
6
<dl class="product">
<dt>女装</dt>
<dd>短款</dd>
<dd>冬季</dd>
<dd>春装</dd>
</dl>

写出两种以上声明多行字符串的方法

  • 字符串拼接
1
2
3
var a="ab"
+"cd"
+"ef";
  • 转义
1
2
3
var a="ab \
cd \
ef";

补全如下代码,让输出结果为字符串: hello\yoowin

1
2
var str = "hello\\\\yoowin"
console.log(str)

以下代码输出什么?为什么

1
2
3
4
var str = 'yoowin\nsu'
console.log(str.length)
//9
//\n是换行符,算一个字符,总共9个字符

写一个函数,统计字符串里出现出现频率最多的字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function max(str){
var obj={};
for(var i=0;i<str.length;i++){
if(obj[str[i]]){
++obj[str[i]];
}else{
obj[str[i]]=1;
}
}
var max=0;
var maxValue;
for(var key in obj){
if(obj[key]>max){
max=obj[key];
maxValue=key;
}
}
console.log("出现频率最多的字符:"+maxValue+",出现次数:"+max);
}
max("ssdcxzzzzzzzzz")
max("ssxzaaaa")

写一个camelize函数,把my-short-string形式的字符串转化成myShortString形式的字符串,如

1
2
3
4
5
6
7
8
9
10
function camelize(str){
var arr=str.split('-')
for(var i=1;i<arr.length;i++){
arr[i] = arr[i].replace(arr[i].charAt(0),arr[i].charAt(0).toUpperCase());
}
var newStr=arr.join('');
return newStr;
}
camelize("background-color") == 'backgroundColor' //true
camelize("list-style-image") == 'listStyleImage' //true

写一个 ucFirst函数,返回第一个字母为大写的字符

1
2
3
4
5
function ucFirst(str){
var newStr = str.replace(str.charAt(0),str.charAt(0).toUpperCase());
return newStr;
}
ucFirst("apple") == "Apple" //true

写一个函数truncate(str, maxlength), 如果str的长度大于maxlength,会把str截断到maxlength长,并加上…,如

1
2
3
4
5
6
7
8
function truncate(str, maxlength){
if(str.length>maxlength){
str=str.slice(0, maxlength)+"...";
}
return str;
}
truncate("hello, this is hunger valley,", 10) == "hello, thi...";
truncate("hello world", 20) == "hello world"
0%