前端面试题-JavaScript

引用GitHub 上 ltadpoles的前端面试

https://github.com/ltadpolesjavascript

目录

1. JavaScript 有哪些数据类型html

2. 怎么判断不一样的JS数据类型前端

3. undefined 和 null 有什么区别java

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

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

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

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

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

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操做符:返回一个字符串,表示未经计算的操做数的类型

typeof 操做符对于简单数据类型,返回其自己的数据类型,函数对象返回 function ,其余对象均返回 Object

null 返回 Object

  • instanceof: 用来判断A 是不是 B的实例,表达式为 A instanceof B,返回一个Boolean类型的值

instanceof 检测的是原型,只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪一种类型

let a = []; a instanceof Array // true a instanceof Object // true

变量a 的 __proto__ 直接指向Array.prototype,间接指向 Object.prototype,因此按照 instanceof 的判断规则,a 就是Object的实例.针对数组的这个问题,ES5 提供了 Array.isArray() 方法 。该方法用以确认某个对象自己是否为 Array 类型

  • constructor: 当一个函数被定义时,JS引擎会为其添加prototype原型,而后再在 prototype上添加一个 constructor属性,并让其指向该函数的引用

nullundefined是无效的对象,所以是不会有constructor存在的,这两种类型的数据须要经过其余方式来判断

函数的constructor是不稳定的,这个主要体如今自定义对象上,当开发者重写prototype后,原有的constructor引用会丢失,constructor会默认为 Object

function F() {}; var f = new F; f.constructor == F // true F.prototype = {a: 1} var f = new F f.constructor == F // false 

在构造函数 F.prototype 没有被重写以前,构造函数 F 就是新建立的对象 f 的数据类型。当 F.prototype 被重写以后,原有的 constructor 引用丢失, 默认为 Object

所以,为了规范开发,在重写对象原型时通常都须要从新给 constructor 赋值,以保证对象实例的类型不被篡改

  • 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 有什么区别

null表示"没有对象",即该处不该该有值

典型用法:

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

undefined表示"缺乏值",就是此处应该有一个值,可是尚未定义

典型用法:

  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 = {}

Object 构造函数

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 特殊的做用域,函数外部没法直接读取内部的变量,内部能够直接读取外部的变量,从而就产生了闭包的概念

用途:

最大用处有两个,一个是前面提到的能够读取函数内部的变量,另外一个就是让这些变量的值始终保持在内存中

注意点:

因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,因此不能滥用闭包,不然会形成网页的性能问题,在IE中可能致使内存泄露

更多参考 JavaScript 中的闭包

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

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

每一个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例(instance)都包含一个指向原型对象的内部指针.

image

图解:

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

那么什么是原型链:

JavaScript中全部的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有本身的原型对象,这样层层上溯,就造成了一个相似链表的结构,这就是原型链

全部原型链的终点都是Object函数的prototype属性。Objec.prototype指向的原型对象一样拥有原型,不过它的原型是null,而null则没有原型

image

更多参考 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 // 动物

使用call或apply方法,将父对象的构造函数绑定在子对象上.

  • 组合继承
function Animal() { this.species = "动物" } function Cat(name){ Animal.call(this) this.name = name } Cat.prototype = new Animal() // 重写原型 Cat.prototype.constructor = Cat 

若是没有Cat.prototype = new Animal()这一行,Cat.prototype.constructor是指向Cat的;加了这一行之后,Cat.prototype.constructor指向Animal.这显然会致使继承链的紊乱(cat1明明是用构造函数Cat生成的),所以咱们必须手动纠正,将Cat.prototype对象的constructor值改成Cat

  • extends 继承 ES6新增继承方式,Class 能够经过extends关键字实现继承
class Animal { } class Cat extends Animal { constructor() { super(); } }

使用 extends 实现继承,必须添加 super 关键字定义子类的 constructor,这里的super() 就至关于 Animal.prototype.constructor.call(this)

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

更多参考 JavaScript 中的继承

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

同步模式

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

异步模式

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

异步加载 JavaScript

  • 动态添加 script 标签
  • defer
  • async

defer属性和async都是属于 script 标签上面的属性,二者都能实现 JavaScript 的异步加载。不一样之处在于:async 在异步加载完成的时候就立刻开始执行了,defer 会等到 html 加载完毕以后再执行

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();

applycall 方法都会使函数当即执行,所以它们也能够用来调用函数

bind 方法不会当即执行,而是返回一个改变了上下文 this 后的函数。而原函数 func 中的 this 并无被改变,依旧指向全局对象 window

bind 在传递参数的时候会将本身带过去的参数排在原函数参数以前

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的理解,它们有什么区别

AMDCMD都是为了解决浏览器端模块化问题而产生的,AMD规范对应的库函数有 Require.jsCMD规范是在国内发展起来的,对应的库函数有Sea.js

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

一、AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块

二、CMD推崇就近依赖,只有在用到某个模块的时候再去require

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

18. 对ES6的了解

ECMAScript 6.0 是 JavaScript 语言的下一代标准

新增的特性:

  • 声明变量的方式 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. 箭头函数有什么特色

ES6 容许使用“箭头”(=>)定义函数

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

注意点:

  • 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象
  • 不能够看成构造函数,也就是说,不可使用 new 命令,不然会抛出一个错误
  • 不可使用 arguments 对象,该对象在函数体内不存在。若是要用,能够用 rest 参数代替
20. Promise 对象的了解

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大.所谓Promise,简单说就是一个容器,里面保存着某个将来才会结束的事件(一般是一个异步操做)的结果 --ES6入门-阮一峰

Promise 对象表明一个异步操做,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。只有异步操做的结果,能够决定当前是哪种状态,任何其余操做都没法改变这个状态

特色:

  • 对象的状态不受外界影响
  • 一旦状态改变,就不会再变,任什么时候候均可以获得这个结果
  • Promise 新建后就会当即执行
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操做成功 */){ resolve(value); } else { reject(error); } })

Promise实例生成之后,能够用then方法分别指定resolved状态和rejected状态的回调函数

promise.then(function(value) { // success }, function(error) { // failure })

then 方法返回的是一个新的Promise实例

Promise.prototype.catch 用于指定发生错误时的回调函数,具备“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误老是会被下一个catch语句捕获

getJSON('/post/1.json').then(function(post) { return getJSON(post.commentURL); }).then(function(comments) { // some code }).catch(function(error) { // 处理前面三个Promise产生的错误 });

catch 方法返回的仍是一个 Promise 对象,所以后面还能够接着调用 then 方法

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

21. async 函数以及 awit 命令

async 函数是什么?一句话,它就是 Generator 函数的语法糖

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

async 特色:

async 函数返回一个 Promise 对象,可使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到异步操做完成,再接着执行函数体内后面的语句

async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数

async 函数返回的 Promise 对象,必须等到内部全部 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误

async 函数内部抛出错误,会致使返回的 Promise 对象变为 reject 状态。抛出的错误对象会被 catch 方法回调函数接收到

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);

await 命令: await 命令后面是一个 Promise 对象,返回该对象的结果。若是不是 Promise 对象,就直接返回对应的值

async function f() { // 等同于 // return 123; return await 123; } f().then(v => console.log(v)) // 123

await 命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象.也就是说就算一个对象不是Promise对象,可是只要它有then这个方法, await 也会将它等同于Promise对象

使用注意点:

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

了解更多,请点击 这里

22. export 与 export default有什么区别

export 与 export default 都可用于导出常量、函数、文件、模块等

在一个文件或模块中,exportimport 能够有多个,export default 仅有一个

经过 export 方式导出,在导入时要加 { }export default 则不须要

使用 export default命令,为模块指定默认输出,这样就不须要知道所要加载模块的变量名; export 加载的时候须要知道加载模块的变量名

export default 命令的本质是将后面的值,赋给 default 变量,因此能够直接将一个值写在 export default 以后

23. 前端性能优化

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

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

首选明确两点:

JavaScript 是单线程语言

JavaScript 的 Event Loop 是 JS 的执行机制, 也就是事件循环

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

JavaScript 将任务分为同步任务和异步任务,执行机制就是先执行同步任务,将同步任务加入到主线程,遇到异步任务就先加入到 event table ,当全部的同步任务执行完毕,若是有可执行的异步任务,再将其加入到主线程中执行

视频详解,移步 这里

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

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

相关文章
相关标签/搜索