前端面试题之JavaScript

export与export default的区别

参考答案
  • 相同点:它们均可用于导出常量、函数、文件、模块等等。
  • 不同点:
    • 在一个文件或模块中,export、import 可以有多个,但 export default 仅能有一个;
    • 通过 export 方式导出,在 import 时导入时需要{ }
    • 通过 export default,在 import 时导入时不需要{ }
    • 使用 export default 命令,为模块指定默认输出,不需要知道加载模块的变量名。

import与require的区别

参考资料

require,import区别?


ES6语法有哪些,分别怎么用

参考资料

ECMAScript 6

阮一峰 - ES6入门教程


new的执行过程

参考答案
  1. 创建一个空对象;
  2. 将构造函数的 prototype 属性赋值给新对象的 __proto__ 属性,并将构造函数的 this 指向新对象;
  3. 执行构造函数中的代码(为这个新对象添加属性);
  4. 将新对象返回;
function Student(name) {
  this.name = name
}

let studentA = new Student('a')
console.log(studentA)

// 等价于

let studentB = (function() {
  let obj = {}
  obj.__proto__ = Student.prototype
  Student.call(obj, 'b')
  return obj
})()
console.log(studentB)

异常捕获

参考资料

如何优雅处理前端异常

错误处理机制


call、apply、bind区别

参考答案

call

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

fun.call(thisArg, arg1, arg2, ...)

apply

apply() 方法调用一个具有给定 this 值的函数,以及作为一个数组(或类似数组对象)提供的参数。当不确定参数的个数时,就可以使用 apply。

func.apply(thisArg, [argsArray])

bind

bind() 方法会返回一个新的函数,在 bind() 被调用时,这个新函数会 call 原来的函数,新函数的 this 被 bind 的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。

function.bind(thisArg[,arg1[,arg2[, ...]]])

总结

其实 apply 和 call 基本类似,他们的区别只是传入的参数不同。call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。
bind 和 call、apply 方法作用是一致的,只是该方法会返回一个新函数,并且我们必须要手动去调用。


什么是JSONP

参考答案

它的基本思想是,网页通过添加一个 <script> 元素,向服务器请求 JSON 数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

请求方:xxx.com的前端程序员(浏览器)
响应方:yyy.com的后端程序员(服务器)

  1. 请求方创建 script,src 指向响应方,同时传一个查询参数 ?callback=xxx
  2. 响应方根据查询参数 callback,构造形如这样的响应:
      1. xxx.call(undefined,’需要的数据’)
      2. xxx(‘需要的数据’)
  3. 浏览器接收到响应,就会执行 xxx.call(undefined,’需要的数据’)
  4. 那请求方就知道了所需要的数据了

为什么不支持POST?

  1. JSONP 是通过动态创建 script 的
  2. 动态创建 script 时只能用 GET,没法用 POST

代码实现 zhouwanwen.com:8001 和 jack.com:8002 之间的 JSONP 请求:Github - nodejs-demo-1


参考资料

阮一峰 - 浏览器同源政策及其规避方法

随它去吧 - 说说JSON和JSONP,也许你会豁然开朗,含jQuery用例

JSON官网


全局函数eval()有什么作用

参考答案

eval() 函数会将传入的字符串当做 JavaScript 代码进行执行。
eval() 是全局对象的一个函数属性

  • 如果 eval() 的参数是一个字符串。如果字符串表示的是表达式,eval() 会对表达式进行求值。如果参数表示一个或多个 JavaScript 语句,那么 eval() 就会执行这些语句。
  • 如果 eval() 的参数不是字符串,eval() 会将参数原封不动地返回。

参考资料

MDN - eval()


为什么0.1+0.2!==0.3

参考答案
0.1 + 0.2    // 0.30000000000000004

因为 JavaScript 存储数值采用双精度浮点数,会出现精度丢失的问题。

0.100000000000000000000002 === 0.1    // true
console.log(0.100000000000000002)    // 0.1

如何使得 0.1+0.2===0.3 呢?

parseFloat((0.1 + 0.2).toFixed(10)) === 0.3    // true

参考资料

掘金 - 0.1 + 0.2不等于0.3?为什么JavaScript有这种“骚”操作?


==、=== 与 Object.is()

参考答案
  1. ==:相等运算符,会在比较时进行类型转换
  2. ===:严格运算符:比较时不进行隐式转换,类型不同则返回 false
  3. Object.is():该方法判断两个值是否为同一个值,返回 true / false。它不会强制转换两边的值。
  • 对于 string、number 等基础类型,是有区别的:
    1. 不同类型:==:转换成同一类型后的值看值是否相等;===:如果类型不同,结果就不等。
    2. 相同类型:直接进行值比较
  • 对于 Array、Object 等高级类型,是没有区别的
  • 对于基础类型与高级类型,是有区别的:
    1. ===:类型不同,结果不等;
    2. ==:将高级类型转化为基础类型,进行值比较。

Object.is() 方法如果满足以下条件则两个值相等:

  1. 都是 undefined;
  2. 都是 null;
  3. 都是 true 或 false;
  4. 都是相同长度的字符串且相同字符按相同顺序排列;
  5. 都是相同对象(意味着每个对象有同一个引用);
  6. 都是数字且都是 +0;都是 -0;都是 NaN;或都是非零而且非 NaN 且为同一个值;
Object.is('hello','hello')    // true
Object.is('hello','hi')   // false

Object.is([],[])    // false

Object.is(null,null)    // true
Object.is(null,undefined)   // false

Object.is(0,+0)   // true
Object.is(0,-0)   // false
Object.is(-0,+0)    // false
Object.is(NaN,0/0)    // true

扩展:

let obj = {
  name: 'LqZww'
}

if (obj.age == null) {}
// 等价于
if (obj.age === null || obj.age === undefined) {}

什么是原型

参考资料

JavaScript中的原型与原型链

冴羽 - JavaScript深入之从原型到原型链


什么是闭包

参考资料

JS中的闭包是什么?

冴羽 - JavaScript深入之闭包


什么是CORS,什么是跨域

参考资料

跨域资源共享CORS详解


typeof与instanceof区别

参考答案

typeof

typeof 可用于基本数据类型的类型判断,例如:number、string、boolean、function、undefined、symbol 等,返回值都是小写的字符串。

console.log(typeof undefined)   // undefined
console.log(typeof 1)   // number
console.log(typeof "a")   // string
console.log(typeof true)    // boolean
console.log(typeof Symbol())    // symbol

console.log(typeof function(){})    // function
console.log(typeof new Function())    // function

console.log(typeof null)    // object
console.log(typeof [1,2])   // object
console.log(typeof {})    // object
console.log(typeof new String())    // object
console.log(typeof new Object())    // object
console.log(typeof new Number())    // object

instanceof

instanceof 是判断变量是否为某个对象的实例,返回值为 true 或 false。

var arr = []
var obj = {}

console.log(arr instanceof Array)   // true
console.log(arr instanceof Object)    // true

console.log(obj instanceof Array)   // false
console.log(obj instanceof Object)    // true

区别

  1. typeof 用于基本数据类型的类型判断,无法判断对象的具体类型(除function)
  2. instanceof 可以用来区分数组、对象,不能用来判断字符串、数字等

参考资料

MDN:instanceof运算符


封装一下typeof方法

参考答案
function myTypeof(val) {
  if (val === null) return 'null';

  let type = typeof (val)
  let objectTypes = {
    '[object Object]': 'o-Object',
    '[object Array]': 'o-Array',
    '[object Number]': 'o-Number',
    '[object Boolean]': 'o-Boolean',
    '[object String]': 'o-String'
  }

  return type === 'object' ? objectTypes[Object.prototype.toString.call(val)] : type;
}

console.log(myTypeof(1));    // number
console.log(myTypeof('1'));    // string
console.log(myTypeof(null));    // null
console.log(myTypeof([]));    // o-Array
console.log(myTypeof({}));    // o-Object
console.log(myTypeof(new Number()));    // o-Number
console.log(myTypeof(new Boolean()));    // o-Boolean
console.log(myTypeof(new String()));    // o-String

如何实现深拷贝

参考答案

JS之深拷贝


判断数组有哪几种方法

参考答案
  1. instanceof
    instanceof 是判断变量是否为某个对象的实例,返回值为 true 或 false。

  2. Object.prototype.toString.call()
    每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的话,会返回 [Object type],其中 type 为对象的类型。但当除了 Object 类型的对象外,其他类型直接使用 toString 方法时,会直接返回都是内容的字符串,所以我们需要使用 call 或者 apply 方法来改变 toString 方法的执行上下文。该方法对于所有基本的数据类型都能进行判断。

  3. Array.isArray()
    此方法用来判断对象是否为数组

参考代码


操作数组方法有哪些,分别有什么用

参考答案
let arr = [11, 22, 33]
// 1. pop() 方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。
let arr1 = arr.pop()
console.log(arr)    // (2) [11, 22]
console.log(arr1)    // 33
// 2. shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
let arr2 = arr.shift()
console.log(arr)    // (2) [22, 33]
console.log(arr2)    // 11
// 3. push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
let arr3 = arr.push(44)
console.log(arr)    // (4) [11, 22, 33, 44]
console.log(arr3)    // 4
// 4. unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度。
let arr4 = arr.unshift(1)
console.log(arr)    // (4) [1, 11, 22, 33]
console.log(arr4)    // 4
// 5. concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
let arr5 = arr.concat([44, 55])
console.log(arr5)    // (5) [11, 22, 33, 44, 55]
// 6. slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end),原始数组不会被改变。
let arr6 = arr.slice(2)
console.log(arr6)    // [33]
// 7. filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素,不改变原数组。
let arr7 = arr.filter(num => num > 20)
console.log(arr7)    // (2) [22, 33]
// 8. map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
let arr8 = arr.map(num => num * 2)
console.log(arr8)    // (3) [22, 44, 66]
// 9. splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
let arr9 = arr.splice(1, 1, 44)
console.log(arr)    // (3) [11, 44, 33]
console.log(arr9)    // [22]

数组降维

参考资料

JS中数组降维的几种方法


null与undefined的区别

参考答案

null

null 是一个字面量,表示空,没有对象。

用法:

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

undefined

undefined 表示"缺少值",就是此处应该有一个值,但是还没有定义。

用法:

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

惯例:

  1. 如果一个变量没有赋值,那它就是 undefined。
  2. 如果一个对象 Object,现在还不想赋值,则可以给它一个 null,即:let obj = null,表示空对象。
  3. 如果有一个非对象,现在还不想赋值,则给它一个 undefined。

注意

null == undefined   // true
null === undefined    // false

typeof null   // "object"
typeof undefined    // "undefined"

1 + null    // 1
1 + undefined   // NaN

Promise、Promise.all、Promise.race分别怎么用

参考资料

Promise从入门到放弃


async/await语法了解吗,目的是什么

参考资料

Promise从入门到放弃


什么是立即执行函数,使用立即执行函数的目的是什么

参考答案

立即执行函数就是说这个函数是立即执行函数体的,不需要额外的去主动调用,要成为立即执行函数,需要满足两个条件:

  1. 声明一个匿名函数
  2. 立马调用这个匿名函数

立即执行函数的目的是 创建独立的作用域,让外部无法访问作用域内部的变量,从而避免 变量污染

下面这段代码就是一个立即执行函数:

(function(){
  console.log("这是立即执行函数")
})()

除了上面这种写法,还有如下写法:

// (匿名函数())
(function(){
  console.log("这是立即执行函数")
}())

// !匿名函数()
!function(){
  console.log("这是立即执行函数")
}()

// +匿名函数()
+function(){
  console.log("这是立即执行函数")
}()

// -匿名函数()
-function(){
  console.log("这是立即执行函数")
}()

// ~匿名函数()
~function(){
  console.log("这是立即执行函数")
}()

// void 匿名函数()
void function(){
  console.log("这是立即执行函数")
}()

// new 匿名函数()
new function(){
  console.log("这是立即执行函数")
}()

如何实现数组去重

参考资料

传送门 - 数组去重的几种方法


如何用正则实现string.trim()

参考答案

trim() 方法会从一个字符串的两端删除空白字符。

function trim(string) {
  return string.replace(/^\s+|\s+$/g, '')
}
console.log(trim("   LqZww   "))    // LqZww

了解ES6 class的用法吗

参考答案

基本用法

在没有 ES6 class 之前的常规写法:

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function(sing) {
  console.log(this.name + "唱了" + sing)
}

var zname = new Person('zww', 22)
console.log(zname)    // Person {name: "zww", age: 22}
zname.sayHi('啊哈哈')   // zww唱了啊哈哈

在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。

类抽象了对象的公共部分,它泛指某一大类。

对象特指某一个,通过类实例化一个具体的对象。

class Star {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  sayHi(sing) {
    console.log(this.name + "唱了" + sing);
  }
}
var zname = new Star("zww", 11)
console.log(zname);   // Star {name: "zww", age: 11}
zname.sayHi("我爱你")   // zww唱了我爱你

注意:

  1. 类必须使用 new 实例化对象
  2. 通过 class 关键字创建类,类名首字母一般大写
  3. 类里面有个 constructor 函数,可以接收传递过来的参数,同时返回实例对象
  4. 类里面所有函数都不需要写 function
  5. 多个函数方法之间不需要用逗号隔开

类的继承

JavaScript 中的类可以继承某个类,其中被继承的类称为父类,而继承父类的被称为子类。

子类可以有自己的函数和构造器,当子类中存在父类相同的方法时,则该方法不会从父类继承,而使用子类的方法。

class Father {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  sayHi(sing) {
    console.log(this.name + '的年龄是' + this.age + ',并且唱了' + sing)
  }
}
class Son extends Father {

}
var father = new Father('zww', 18)
console.log(father)   // Father {name: "zww", age: 18}

var son = new Son('lq', 22)
console.log(son)    // Son {name: "lq", age: 22}
son.sayHi('呵呵')   // lq的年龄是22,并且唱了呵呵

super关键字

super 关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数。

class Father {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  sayHi() {
    console.log('父类函数')
  }
}
class Son extends Father {
  constructor(name, age, sex) {
    super(name, age)
    this.sex = sex
  }
  sonfn() {
    super.sayHi()
    console.log('子类函数')
  }
}
var son = new Son('lq', 22, '女')
console.log(son)    // Son {name: "lq", age: 22, sex: "女"}
son.sonfn('丫丫')   // 父类函数 子类函数

注意:

  • 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象
  • 类里面的共有的属性和方法一定要加 this
  • this 的指向问题;constructor 里面的 this 指向的是创建的实例对象;方法里面的 this 指向这个方法的调用者

axios与ajax的区别

参考资料

axios和ajax的区别?

Jquery ajax, Axios, Fetch区别之我见

ajax和axios、fetch的区别


如何实现一个call函数

参考答案
Function.prototype.myCall = function(context) {
  if (typeof this !== 'function') {
    console.log("error")
  }
  let args = [...arguments].slice(1)
  let result = null
  context = context || window
  context.fn = this
  result = context.fn(...args)
  delete context.fn
  return result
}

参考资料

冴羽 - JavaScript深入之call和apply的模拟实现


如何实现一个apply函数

参考答案
Function.prototype.myApply = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError("Error")
  }
  let result = null
  context = context || window
  context.fn = this
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}

如何实现一个bind函数

参考答案
Function.prototype.myBind = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError("Error")
  }
  var args = [...arguments].slice(1),
    fn = this
  return function Fn() {
    return fn.apply(this instanceof Fn ? this : context, args.concat(...arguments))
  }
}

Github - 详细代码


参考资料

冴羽 - JavaScript深入之bind的模拟实现


手写一个AJAX

参考答案
let xhr = new XMLHttpRequest()
xhr.open('GET', '/xxx', true)
xhr.onreadystatechange = function(){
  if(xhr.readyState === 4){
    if(xhr.status >= 200 && xhr.status < 300){
      console.log('请求成功')
    }else{
      console.log('请求失败')
    }
  }
}
xhr.send()

参考资料

AJAX从入门到放弃


手写一个函数防抖

参考答案

当我们触发事件时,但是一定在事件触发的第 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那么就以新触发事件的时间为准,n 秒后才执行。

就是等你触发完事件 n 秒内不再触发事件才会执行。

function debounce(fn, wait) {
  let timer = null
  return function() {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => fn.apply(this, arguments), wait)
  }
}

示例代码 - 函数防抖


参考资料

冴羽 - JavaScript专题之跟着underscore学防抖


手写一个函数节流

参考答案

从上一次命令结束开始的一定时间范围 n 秒内,如果多次连续下达命令,则只执行当前时间段 n 秒内第一次命令。

如果你持续触发事件,每隔一段时间,只执行一次事件。

function throttle(fn, gapTime) {
  let lastTime = null
  let nowTime = null
  return function() {
    nowTime = Date.now()
    if (!lastTime || nowTime - lastTime > gapTime) {
      fn()
      lastTime = nowTime
    }
  }
}

示例代码 - 函数防抖

示例代码 - 自动保存案例


参考资料

冴羽 - JavaScript专题之跟着 underscore 学节流


手写EventHub

参考答案
class EventHub {
  private cache: { [key: string]: Array<(data: unknown) => void} = {}
  on(eventName: string, fn: (data: unknown) => void) {
    this.cache[eventName] = this.cache[eventName] || []
    this.cache[eventName].push(fn)
  }
  emit(eventName: string, data?: unknown) {
    let array = this.cache[eventName] || []
    array.forEach(fn => {
      fn(data)
    });
  }
  off(eventName: string, fn: (data: unknown) => void) {
    this.cache[eventName] = this.cache[eventName] || []
    let index = indexOf(this.cache[eventName], fn)
    if (index === -1) return;
    this.cache[eventName].splice(index, 1)
  }
}

export default EventHub;

Github - 详细代码


手写简易版的jQuery

参考资料

Github - 详细代码


手写一个Promise

参考资料

Github - 详细代码


什么是事件委托

参考答案

事件委托,其实就是把一个元素响应事件(click、keydown...)的函数委托到另一个元素上。

一般来说,我们会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。

使用事件委托的好处:

  1. 可以减少内存的消耗
  2. 动态绑定事件

示例代码


WebSocket是什么,了解吗

参考资料

MDN - WebSocket

廖雪峰的官方网站 - WebSocket

阮一峰 - websocket

Github - 简易聊天室的实现 - websocket-chatroom-demo


我提问你来答

Topic One

如何让下面代码打印出来!

if (a == 1 && a == 2 && a == 3) {
  console.log("OK!");
}
参考答案
let a = {
  default: 0,
  toString: function () {
    return ++this.default
  }
}

if (a == 1 && a == 2 && a == 3) {
  console.log("OK!");
}

Topic Two

如何让下面代码打印出来!

if (a === 1 && a === 2 && a === 3) {
  console.log("OK!");
}
参考答案
let _default = 0
Object.defineProperty(window, 'a', {
  get() {
    return ++_default
  }
})

if (a == 1 && a == 2 && a == 3) {
  console.log("OK!");
}

Topic Three

下面几段代码将打印出什么呢?

console.log(({} + {}).length);

console.log(([] + []).length);

console.log((function () { }).length);
参考答案

分别打印出:30 0 0。

console.log({}.toString());    // [object Object]
console.log(Object.prototype.toString.call({}))    // [object Object]
console.log(({}.toString()).length);    // 15
console.log(({} + {}).length);    // 30

console.log([].toString())    // 
console.log([].length);    // 0
console.log(([] + []).length);    // 0


function fn1() { }
console.log(fn1.length);    // 0

function fn2(a, b, c) { }
console.log(fn2.length);    // 3

function fn3(a, b, c) {
  console.log(arguments.length);    // 4
}
fn3(1, 2, 3, 4)
console.log(fn3.length);    // 3

console.log((function () { }).length);    // 0
console.log((function (a, b) { }).length);    // 2


其他

JavaScript 进阶问题列表


更多面试题请移步至 我的新博客 - 持续更新地址


posted @ 2020-08-26 23:54  LqZww  阅读(357)  评论(0编辑  收藏  举报