前端常见手写面试题梳理(持续更新)

125 阅读6分钟

html css 部分

1.flex 弹性布局: 语法篇

2.BFC (块级格式化上下文): 语法篇

3.实现左右宽度固定中间自适应的布局方法(1.定位 2.浮动 3.flex 4.grid)

4.盒模型(标准模型、IE怪异盒模型)

5.css 选择器优先级

6.伪类、伪元素的区别

js 部分

一、new 运算符的原理:

1)一个继承自 Foo.prototype 的新对象被创建。

2)使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数时,Foo 不带任何参数调用的情况。

3)如果构造函数返回了一个“对象”,那么这个对象会取代整个 new 出来的结果。如果构造函数没有返回对象,那么 new 出来的结果为步骤(1)创建的对象

function mockNew() { // es5的实现方式
  const Foo = Array.prototype.shift.call(arguments) // 相当于[].shift.call(arguments)
  const obj = {}
  obj.__proto__ = Foo.prototype
  const r = Foo.apply(obj, arguments)
  return r instanceof Object ? r : obj
}

// 代码测试
function Animal(type) {
  this.type = type
  return 1
  // return function() {}
  // return { name: 'animal' }
}
Animal.prototype.shout = function() {
  console.log('shout')
}
const animal = mockNew(Animal, '动物')

二、Javascript 中数据类型判断的几种方法:

基本数据类型 (number、string、boolean、null、undefined、symbol)

引用数据类型 (object、array、function)

  1. typeof 判断类型返回值为(number、string、boolean、object、undefined、symbol、function)。
typeof 123 // 'number'
typeof '123' // 'string'
typeof null // 'object'
typeof [] // 'object'
typeof new Date // 'object'
typeof function() {} // 'function'

2.想要区分某个对象属于哪种内置类型,单纯使用 typeof 是不行的。我们可以通过Object.prototype.toString 方法来判断:

Object.prototype.toString.call([]);// "[object Array]" 
Object.prototype.toString.call({});// "[object Object]"

缺点: 不能校验变量是否属于某个对象的实例,需要用 instanceof 方法。

3.instanceof 运算符的原理:

应用场景: 用于判断变量是否属于某个对象的实例,根据原型链进行搜寻。内部会调用**[Symbol.hasInstance]** 方法去做判断。

// 使用方式
function A () {}
const a = new A()
a instanceof A // true 
a instanceof Object // true 

// 实现原理
function Myinstanceof(ins, Foo) {
  ins = ins.__proto__
  Foo = Foo.prototype
  while(true) {
    if (ins === null) return false
    if (ins === Foo) {
      return true
    } 
    ins = ins.__proto__
  }
}

但如果基本类型使用 instanceof 时,判断会失效,如下面的示例:

'123' instanceof String // false
// 相当于下面的调用方式
String[Symbol.hasInstance]('123')

魔改方式:

class String {
  static [Symbol.hasInstance](str) {
    return typeof str === 'string'
  }
}
String[Symbol.hasInstance]('123') // true

43.png

4.constructor方式:

43.png

constructor 来判断类型看起来是完美的。然而,如果我创建一个对象,并更改它的原型,这种方式也变得不可靠了。

43.png 因此,当要修改对象的 proptotype 时,一定要设置 constructor 指向其构造函数。

43.png

三、Javascript 中深浅拷贝:

浅拷贝: 对象共用一个内存地址,对象的变化相互影响。。 如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

1.数组 slice 浅拷贝。

2.... 扩展运算符: 一层是深拷贝,多层是浅拷贝。

  1. Object.assign 浅拷贝。

深拷贝: 拷贝前后没有关系。 1.JSON.parse(JSON.stringify(str)) 会忽略掉undefined,不能序列化函数,不能解决循环引用的对象。

2.手写递归深拷贝。

// 1.普通版本
function deepClone(obj) {
  if (obj == null) return obj
  if (obj instanceof Date) return new Date(obj)
  if (obj instanceof RegExp) return new RegExp(obj)
  if (typeof obj !== 'object') return obj // 基本类型不需要拷贝
  const cloneObj = new obj.constructor
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) { // 仅拷贝实例属性
      cloneObj[key] = deepClone(obj[key]) // 实现递归拷贝关键
    }
  }
  
  return cloneObj
}

const obj = {a:1, b:{ c: 2 }}
const cloneObj = deepClone(obj)
obj.b.c = 100
console.log(cloneObj) // { a: 1, b: { c: 2 } }

// 2.解决循环引用版本
function deepClone(obj, hash = new WeakMap) { // WeakMap为弱引用,会自动被垃圾回收机制回收。
  if (obj == null) return obj
  if (obj instanceof Date) return new Date(obj)
  if (obj instanceof RegExp) return new RegExp(obj)
  if (typeof obj !== 'object') return obj // 基本类型不需要拷贝
  `if (hash.has(obj)) return hash.get(obj) `
  const cloneObj = new obj.constructor
  `hash.set(obj, cloneObj)`
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) { // 仅拷贝实例属性
      cloneObj[key] = deepClone(obj[key], hash) // 实现递归拷贝关键
    }
  }
  
  return cloneObj
}

const obj = {a:1, b:{ c: 2 }}
obj.o = obj // 循环引用(自己引用自己)
const cloneObj = deepClone(obj)
obj.b.c = 100
console.log(cloneObj) // { a: 1, b: { c: 2 }, o: [Circular] }

四、Javascript 中的原型、原型链

原型: prototypo 原型链: proto

43.png

每一个函数都有一个 prototypo 属性,每一个对象都有 __proto__ 属性。

判断属性存在实例上需要用 hasOwnProperty 方法。

判断属性存在实例或原型上需要用 in 方法。

function Animal(type) {this.type = type}
Animal.prototype.shout = function() {}
const animal = new Animal('dog')

console.log(animal.hasOwnProperty('type')) // true
console.log('shout' in animal) // true

五、Javascript 中的函数柯理化

六、Javascript 中的call、apply、bind

1.call、apply 都可改变当前函数 this 指向,并且让当前函数执行。只不过 apply 第二个参数是一个数组,里面放参数, call 从第二个参数开始,依次放置函数参数。

// 用法
const obj = {a: 1}
function fn() {
  console.log(this, arguments)
}
fn.call(obj, 1, 2, 3)
fn.apply(obj, [1, 2, 3])

2.多次调用 call 函数时,如: fn.call.call.call(fn2),this 会指向 fn2,并且执行 fn2

// call 代码实现方式
Function.prototype.myCall = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  context = Object(context) || window
  context.fn = this
  const args = [...arguments].slice(1)
  const result = context.fn(...args)
  delete context.fn
  return result
}
const obj = {a: 1}
function fn() {
  console.log(this, arguments)
}
function f2() {
  console.log(this, arguments)
}
fn.myCall(obj, 1, 2, 3)
fn.myCall.myCall.myCall(fn2, 1, 2, 3) // this变为fn2, 并且让fn2执行。
// apply 代码实现方式(仅处理参数和 call 有区别)
Function.prototype.myApply = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  context = Object(context) || window
  context.fn = this
  let result 
  if (arguments[1]) { 
    result = context.fn(...arguments[1]) 
  } else { 
    result = context.fn() 
  }
  delete context.fn
  return result
}
  1. bind 方法创建一个新的函数(高阶函数),当新函数被调用时,将其 this 关键字设置为提供的值。 当 new 调用新函数时,context 参数无效,new 操作符修改 this 指向的优先级更高。
// bind 代码实现方式
Function.prototype.MyBind = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  const that = this
  const args = [...arguments].slice(1)
  const fBound = function(...arguments) {
    that.apply(this instanceof fBound ? this : context, args.concat(arguments))
  }
  // fBound.prototype = Object.create(this.prototype)
  
  // ** Object.create 原理 **
  function F() {} 
  F.prototype = this.prototype
  fBound.prototype = new F()
  
  return fBound
}

const obj = {a: 1}
function fn() {
  console.log(this)
}
fn.prototype.b = 2
let fn3 = fn.MyBind(obj) 
let a = new fn3() // fn {}
fn3() // { a: 1 }

七、Javascript 中的防抖、节流

八、Javascript 中的Object.create原理(重要)

Object.create 接收对象作为参数(这个对象将作为新对象的原型对象),创建一个新对象并返回。

const obj = {a: 1}
let newObj = Object.create(obj)
console.log(newObj.__proto__ === obj)

// 实现原理
Object.create = function(obj) {
  function F() {} // 1.创建一个构造函数
  F.prototype = obj // 2.将构造函数的原型指向obj
  return new F() // 3.通过new创建对象并返回
}

九、手写Javascript 中的EventEmitter

十、手写Javascript 中数组的多个方法

1.reduce MDN链接

43.png

// reduce用法
const arr = [1, 2, 3]
const sum = arr.reduce((prev, curr, index, array) => {
  return prev + curr
}, 10) // 16

// 代码实现
Array.prototype.MyReduce = function (callback, initialValue) {
  if (this.length === 0) {
    if (!initialValue) {
      throw new TypeError('Reduce of empty array with no initial value')
    } else {
      return initialValue
    }
  }
  let prev = initialValue ? initialValue : this[0]
  let idx = initialValue ? 0 : 1
  for(let i = idx; i < this.length; i++) {
    prev = callback(prev, this[i], i, this)
  }
  return prev
}

2.map MDN链接

// map用法
const arr = [1, 2, 3]
const doubleArr = arr.map((item, index, array) => {
  return item * 2
})

// 代码实现
Array.prototype.MyMap = function (callback) {
  if (!Array.isArray(this) || this.length === 0 || typeof callback !== 'function') {
    return []
  }
  const result = []
  for (let i = 0; i < this.length; i++) {
    result.push(callback(this[i], i, this))
  }
  return result
}

十一、手写一个完整的 Promise

我的另一篇文章

十二、Javascript 中的 EventLoop

十三、Javascript 中的 高阶函数、compose等

未完待续,敬请期待,持续更新...