引用GitHub 上 ltadpoles的前端面试

https://github.com/ltadpoles

目录

1. JavaScript 有哪些数据类型

2. 怎么判断不同的JS数据类型

3. undefined 和 null 有什么区别

4. 数组对象有哪些常用方法

5. Js 有哪几种创建对象的方式

6. 怎么实现对对象的拷贝(浅拷贝与深拷贝)

7. 什么是闭包,为什么要用它

8. 介绍一下 JavaScript 原型,原型链,它们有何特点

9. JavaScript 如何实现继承

10. new 操作符具体干了什么

11. 同步和异步的区别,怎么异步加载 JavaScript

12. 跨域问题的产生,怎么解决它

13. 对 this 的理解

14. apply()、call()和 bind() 是做什么的,它们有什么区别

15. 什么是内存泄漏,哪些操作会造成内存泄漏

16. 什么是事件代理,它的原理是什么

17. 对AMD和CMD的理解,它们有什么区别

18. 对ES6的了解

19. 箭头函数有什么特点

20. Promise 对象的了解

21. async 函数以及 awit 命令

22. export 与 export default有什么区别

23. 前端性能优化

24. 对JS引擎执行机制的理解

1. JavaScript 有哪些数据类型

6种原始数据类型:

  • Boolean: 布尔表示一个逻辑实体,可以有两个值:true 和 false
  • Number: 用于表示数字类型
  • String: 用于表示文本数据
  • Null: Null 类型只有一个值: null,特指对象的值未设置
  • Undefined: 一个没有被赋值的变量会有个默认值 undefined
  • Symbol: 符号(Symbols)是ECMAScript第6版新定义的。符号类型是唯一的并且是不可修改的

引用类型:Object

详见 JavaScript中的数据类型

2. 怎么判断不同的JS数据类型
  • typeof操作符:返回一个字符串,表示未经计算的操作数的类型
  • instanceof: 用来判断A 是否是 B的实例,表达式为 A instanceof B,返回一个Boolean类型的值
let a = [];
a instanceof Array // true
a instanceof Object // true
  • constructor: 当一个函数被定义时,JS引擎会为其添加prototype原型,然后再在 prototype上添加一个 constructor属性,并让其指向该函数的引用
function F() {};
var f = new F;
f.constructor == F // true F.prototype = {a: 1}
var f = new F
f.constructor == F // false
  • toString: Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型
Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(11) ; // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call([]) ; // [object Array]
3. undefined 和 null 有什么区别

典型用法:

  1. 作为函数的参数,表示该函数的参数不是对象
  2. 作为对象原型链的终点

典型用法:

  1. 变量被声明了,但没有赋值时,就等于undefined
  2. 调用函数时,应该提供的参数没有提供,该参数等于undefined
  3. 对象没有赋值的属性,该属性的值为undefined
  4. 函数没有返回值时,默认返回undefined

详见: undefined和null的区别-阮一峰

4. 数组对象有哪些常用方法
  • pop(): 删除数组的最后一个元素,并返回这个元素
  • push():在数组的末尾增加一个或多个元素,并返回数组的新长度
  • reverse(): 颠倒数组中元素的排列顺序
  • shift(): 删除数组的第一个元素,并返回这个元素
  • unshift(): 在数组的开头增加一个或多个元素,并返回数组的新长度
  • sort(): 对数组元素进行排序,并返回当前数组
  • splice(): 在任意的位置给数组添加或删除任意个元素
  • concat(): 返回一个由当前数组和其它若干个数组或者若干个非数组值组合而成的新数组
  • join(): 连接所有数组元素组成一个字符串
  • slice(): 抽取当前数组中的一段元素组合成一个新数组
  • indeOf(): 返回数组中第一个与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1
  • lastIndexOf(): 返回数组中最后一个(从右边数第一个)与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1
  • forEach(): 为数组中的每个元素执行一次回调函数,最终返回 undefined
  • every(): 如果数组中的每个元素都满足测试函数,则返回 true,否则返回 false
  • some(): 如果数组中至少有一个元素满足测试函数,则返回 true,否则返回 false
  • filter(): 将所有在过滤函数中返回 true 的数组元素放进一个新数组中并返回
  • map(): 返回一个由回调函数的返回值组成的新数组

更多方法请参考 MDN 传送门

5. Js 有哪几种创建对象的方式
var obj = {}
var obj = new Object()
function Person(name, age) {
var o = new Object()
o.name = name;
o.age = age;
o.say = function() {
console.log(name)
}
return o
}

缺点: 每次通过Person创建对象的时候,所有的say方法都是一样的,但是却存储了多次,浪费资源

function Person(name, age) {
this.name = name
this.age = age
this.say = function() {
console.log(name)
}
}
var person = new Person('hello', 18)

构造函数模式隐试的在最后返回return this 所以在缺少new的情况下,会将属性和方法添加给全局对象,浏览器端就会添加给window对象,可以根据return this 的特性调用call或者apply指定this

function Person() {}
Person.prototype.name = 'hanmeimei';
Person.prototype.say = function() {
alert(this.name);
}
Person.prototype.friends = ['lilei'];
var person = new Person();

实现了方法与属性的共享,可以动态添加对象的属性和方法。但是没有办法创建实例自己的属性和方法,也没有办法传递参数

function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function() {
console.log(this.name)
}
var person = new Person('hello')

还有好几种模式,感兴趣的小伙伴可以参考 红宝书,你们肯定知道的了!

6. 怎么实现对对象的拷贝(浅拷贝与深拷贝)
  • 拷贝原对象引用
  • 可以使用Array.prototype.slice()也可以完成对一个数组或者对象的浅拷贝
  • Object.assign()方法
  • 最常用的方式就是 JSON.parse(JSON.stringify(目标对象),缺点就是只能拷贝符合JSON数据标准类型的对象

更多参考 JavaScript 中的浅拷贝与深拷贝

7. 什么是闭包,为什么要用它
function Person() {
var name = 'hello'
function say () {
console.log(name)
}
return say()
}
Person() // hello

用途:

注意点:

更多参考 JavaScript 中的闭包

8. 介绍一下 JavaScript 原型,原型链,它们有何特点

首先明确一点,JavaScript是基于原型的

前端面试题-JavaScript-LMLPHP

图解:

  • 每一个构造函数都拥有一个prototype属性,这个属性指向一个对象,也就是原型对象
  • 原型对象默认拥有一个constructor属性,指向指向它的那个构造函数
  • 每个对象都拥有一个隐藏的属性[[prototype]],指向它的原型对象

那么什么是原型链:

前端面试题-JavaScript-LMLPHP

更多参考 JavaScript 中的原型与原型链

9. JavaScript 如何实现继承
  • 原型链继承
function Animal() {}
Animal.prototype.name = 'cat'
Animal.prototype.age = 1
Animal.prototype.say = function() {console.log('hello')} var cat = new Animal() cat.name // cat
cat.age // 1
cat.say() // hello
  1. 来自原型对象的所有属性被所有实例共享
  2. 创建子类实例时,无法向父类构造函数传参
  3. 要想为子类新增属性和方法,必须要在new语句之后执行,不能放到构造器中
  • 构造继承
function Animal() {
this.species = "动物"
}
function Cat(name, age) {
Animal.call(this)
this.name = name
this.age = age
} var cat = new Cat('豆豆', 2) cat.name // 豆豆
cat.age // 2
cat.species // 动物
  • 组合继承
function Animal() {
this.species = "动物"
} function Cat(name){
Animal.call(this)
this.name = name
} Cat.prototype = new Animal() // 重写原型
Cat.prototype.constructor = Cat
  • extends 继承 ES6新增继承方式,Class 可以通过extends关键字实现继承
class Animal {

}

class Cat extends Animal {
constructor() {
super();
}
}

当然,还有很多种实现继承的方式,这里就不多说了。然后,再推荐一波 红宝书

更多参考 JavaScript 中的继承

10. new 操作符具体干了什么
  • 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型
  • 属性和方法被加入到 this 引用的对象中
  • 新创建的对象由 this 所引用,并且最后隐式的返回 this
11. 同步和异步的区别,怎么异步加载 JavaScript

同步模式,又称阻塞模式。javascript 在默认情况下是会阻塞加载的。当前面的 javascript 请求没有处理和执行完时,会阻止浏览器的后续处理

异步加载又叫非阻塞,浏览器在下载执行 js 同时,还会继续进行后续页面的处理

  • 动态添加 script 标签
  • defer
  • async
12. 跨域问题的产生,怎么解决它

解决跨域问题有很多种方式,常用的就是以下几种:

  • jsonp 跨域:动态创建script,再请求一个带参网址实现跨域通信.缺点就是只能实现 get 一种请求
  • document.domain + iframe跨域:两个页面都通过js强制设置document.domain为基础主域,就实现了同域.但是仅限主域相同,子域不同的跨域应用场景
  • 跨域资源共享(CORS):只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置
  • nginx反向代理接口跨域:同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题
  • WebSocket协议跨域
13. 对 this 的理解

在 JavaScript 中,研究 this 一般都是 this 的指向问题,核心就是 this 永远指向最终调用它的那个对象,除非改变 this 指向或者箭头函数那种特殊情况

function test() {
console.log(this);
} test() // window var obj = {
foo: function () { console.log(this.bar) },
bar: 1
}; var foo = obj.foo;
var bar = 2; obj.foo() // 1
foo() // 2 // 函数调用的环境不同,所得到的结果也是不一样的
14. apply()、call()和 bind() 是做什么的,它们有什么区别

相同点:三者都可以改变 this 的指向

不同点:

  • apply 方法传入两个参数:一个是作为函数上下文的对象,另外一个是作为函数参数所组成的数组
var obj = {
name : 'sss'
} function func(firstName, lastName){
console.log(firstName + ' ' + this.name + ' ' + lastName);
} func.apply(obj, ['A', 'B']); // A sss B
  • call 方法第一个参数也是作为函数上下文的对象,但是后面传入的是一个参数列表,而不是单个数组
var obj = {
name: 'sss'
} function func(firstName, lastName) {
console.log(firstName + ' ' + this.name + ' ' + lastName);
} func.call(obj, 'C', 'D'); // C sss D
  • bind 接受的参数有两部分,第一个参数是是作为函数上下文的对象,第二部分参数是个列表,可以接受多个参数
var obj = {
name: 'sss'
} function func() {
console.log(this.name);
} var func1 = func.bind(null, 'xixi');
func1();
function func(a, b, c) {
console.log(a, b, c);
}
var func1 = func.bind(this, 'xixi');
func1(1,2) // xixi 1 2
15. 什么是内存泄漏,哪些操作会造成内存泄漏

可能造成内存泄漏的操作:

  • 意外的全局变量
  • 闭包
  • 循环引用
  • 被遗忘的定时器或者回调函数

你可能还需要知道 垃圾回收机制 此外,高程上面对垃圾回收机制的介绍也很全面,有兴趣的小伙伴可以看看

16. 什么是事件代理,它的原理是什么
17. 对AMD和CMD的理解,它们有什么区别

AMD和CMD最大的区别是对依赖模块的执行时机处理不同

参考:AMD-中文版 CMD-规范

18. 对ES6的了解

新增的特性:

  • 声明变量的方式 let const
  • 变量解构赋值
  • 字符串新增方法 includes() startsWith() endsWith() 等
  • 数组新增方法 Array.from() Array.of() entries() keys() values() 等
  • 对象简洁写法以及新增方法 Object.is() Object.assign() entries() keys() values()
  • 箭头函数、rest 参数、函数参数默认值等
  • 新的数据结构: Set 和 Map
  • Proxy
  • Promise对象
  • async函数 await命令
  • Class
  • Module 体系 模块的加载和输出方式

了解更多,参考 ES6入门-阮一峰

19. 箭头函数有什么特点
var f = v => v;

// 等同于
var f = function (v) {
return v;
}

注意点:

  • 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象
  • 不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误
  • 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替
20. Promise 对象的了解

特点:

  • 对象的状态不受外界影响
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果
  • Promise 新建后就会立即执行
const promise = new Promise(function(resolve, reject) {
// ... some code if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
})
promise.then(function(value) {
// success
}, function(error) {
// failure
})
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});

出去上述方法,Promise还有其他用法,小伙伴们可以在这里查看大佬写的文章 ES6入门-阮一峰

21. async 函数以及 awit 命令

了解Generator函数的小伙伴,这里 传送门

async 特点:

function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
} async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
} asyncPrint('hello world', 50);
async function f() {
// 等同于
// return 123;
return await 123;
} f().then(v => console.log(v))
// 123

使用注意点:

  • await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中
  • 多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发
  • await 命令只能用在 async 函数之中,如果用在普通函数,就会报错

了解更多,请点击 这里

22. export 与 export default有什么区别
23. 前端性能优化

参见 雅虎14条前端性能优化

24. 对JS引擎执行机制的理解

首选明确两点:

console.log(1)

setTimeout(function(){
console.log(2)
},0) console.log(3) // 1 3 2

视频详解,移步 这里

setTimeout(function(){console.log(1);},0);
new Promise(function(resolve){
console.log(2);
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log(3)
}); console.log(4); // 2 4 3 1

在异步任务中,定时器也属于特殊的存在。有人将其称之为 宏任务、微任务,定时器就属于宏任务的范畴。

05-29 00:16