JavaScript———原理题

Posted 、妤

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript———原理题相关的知识,希望对你有一定的参考价值。

@

前言

本文针对目前常见的面试题,实现了相应方法的核心原理,部分边界细节未处理。

实现一个call函数

// 通过this获取指定方法,然后挂载在传入的上下文
Function.prototype.myCall = function (context) 
  // 判断指定方法是否为函数
  if (typeof this !== \'function\') 
    throw new TypeError(\'not funciton\')
  

  // 检测上下文是否为空
  context = context || window

  // 上下文保存指定方法
  context.myFun = this

  // 获取其余参数
  let args = [...arguments].slice(1)
  let result = context.myFun(args)

  // 删除该属性
  delete context.myFun
  return result

实现一个apply函数

Function.prototype.myApply = function (context) 
  // 判断指定方法是否为函数
  if (typeof this !== \'function\') 
    throw new TypeError(\'not funciton\')
  

  // 检测上下文是否为空
  context = context || window

  // 上下文保存指定方法
  context.myFun = this

  let result
  // 参数判断 其余参数是否为一个数组
  if (arguments[1] && Array instanceof arguments[1]) 
    result = context.myFun(...arguments[1])
   else 
    result = context.myFun()
  
  // 删除该属性
  delete context.myFun
  return result

实现一个bind函数

Function.prototype.myBind = function (context) 
  // 判断指定方法是否为函数
  if (typeof this !== \'function\') 
    throw new TypeError(\'not funciton\')
  

  // 保存指定方法
  let _this = this

  // 获取初始参数
  let arg = [...arguments].slice(1)

  // 返回绑定了上下文的函数
  return function F(...args) 
    // 处理函数被使用new的情况
    if (this instanceof F) 
      return new _this(...arg, ...args)
     else 
      return _this.apply(context, arg.concat(...args))
    
  

instanceof的原理


// 可通过constructor属性比较,但是constructor属性具有被改写的风险
// 用原型和原型对象进行比较
function myInstanceof(obj, cons) 
  let leftVal = obj.__proto__
  let rigthVal = cons.prototype

  while (true) 
    if (leftVal) 
      return false
    

    if (leftVal === rigthVal) 
      return true
    

    leftVal = leftVal.__proto__
  


Object.create的基本实现原理


function create(obj) 
  function F() 
  F.prototype = obj
  return new F()
 
 

new本质

function myNew (fun) 
  return function () 
    // 创建一个新对象且将其隐式原型指向构造函数原型
    let obj = 
      __proto__ : fun.prototype
    
    // 执行构造函数
    fun.call(obj, ...arguments)
    // 返回该对象
    return obj
  


function person(name, age) 
  this.name = name
  this.age = age

let obj = myNew(person)(\'chen\', 18) // name: "chen", age: 18

实现一个基本的Promise

  // 判断变量否为function
  const isFunction = variable => typeof variable === \'function\'
  // 定义Promise的三种状态常量
  const PENDING = \'PENDING\'
  const FULFILLED = \'FULFILLED\'
  const REJECTED = \'REJECTED\'

  class MyPromise 
    constructor(handle) 
      if (!isFunction(handle)) 
        throw new Error(\'MyPromise must accept a function as a parameter\')
      
      // 添加状态
      this._status = PENDING
      // 添加状态
      this._value = undefined
      // 添加成功回调函数队列
      this._fulfilledQueues = []
      // 添加失败回调函数队列
      this._rejectedQueues = []
      // 执行handle
      try 
        handle(this._resolve.bind(this), this._reject.bind(this))
       catch (err) 
        this._reject(err)
      
    
    // 添加resovle时执行的函数
    _resolve(val) 
      const run = () => 
        if (this._status !== PENDING) return
        this._status = FULFILLED
        // 依次执行成功队列中的函数,并清空队列
        const runFulfilled = (value) => 
          let cb;
          while (cb = this._fulfilledQueues.shift()) 
            cb(value)
          
        
        // 依次执行失败队列中的函数,并清空队列
        const runRejected = (error) => 
          let cb;
          while (cb = this._rejectedQueues.shift()) 
            cb(error)
          
        
        /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
          当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
        */
        if (val instanceof MyPromise) 
          val.then(value => 
            this._value = value
            runFulfilled(value)
          , err => 
            this._value = err
            runRejected(err)
          )
         else 
          this._value = val
          runFulfilled(val)
        
      
      // 为了支持同步的Promise,这里采用异步调用
      setTimeout(run, 0)
    
    // 添加reject时执行的函数
    _reject(err) 
      if (this._status !== PENDING) return
      // 依次执行失败队列中的函数,并清空队列
      const run = () => 
        this._status = REJECTED
        this._value = err
        let cb;
        while (cb = this._rejectedQueues.shift()) 
          cb(err)
        
      
      // 为了支持同步的Promise,这里采用异步调用
      setTimeout(run, 0)
    
    // 添加then方法
    then(onFulfilled, onRejected) 
      const 
        _value,
        _status
       = this
      // 返回一个新的Promise对象
      return new MyPromise((onFulfilledNext, onRejectedNext) => 
        // 封装一个成功时执行的函数
        let fulfilled = value => 
          try 
            if (!isFunction(onFulfilled)) 
              onFulfilledNext(value)
             else 
              let res = onFulfilled(value);
              if (res instanceof MyPromise) 
                // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
                res.then(onFulfilledNext, onRejectedNext)
               else 
                //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
                onFulfilledNext(res)
              
            
           catch (err) 
            // 如果函数执行出错,新的Promise对象的状态为失败
            onRejectedNext(err)
          
        
        // 封装一个失败时执行的函数
        let rejected = error => 
          try 
            if (!isFunction(onRejected)) 
              onRejectedNext(error)
             else 
              let res = onRejected(error);
              if (res instanceof MyPromise) 
                // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
                res.then(onFulfilledNext, onRejectedNext)
               else 
                //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
                onFulfilledNext(res)
              
            
           catch (err) 
            // 如果函数执行出错,新的Promise对象的状态为失败
            onRejectedNext(err)
          
        
        switch (_status) 
          // 当状态为pending时,将then方法回调函数加入执行队列等待执行
          case PENDING:
            this._fulfilledQueues.push(fulfilled)
            this._rejectedQueues.push(rejected)
            break
            // 当状态已经改变时,立即执行对应的回调函数
          case FULFILLED:
            fulfilled(_value)
            break
          case REJECTED:
            rejected(_value)
            break
        
      )
    
    // 添加catch方法
    catch (onRejected) 
      return this.then(undefined, onRejected)
    
    // 添加静态resolve方法
    static resolve(value) 
      // 如果参数是MyPromise实例,直接返回这个实例
      if (value instanceof MyPromise) return value
      return new MyPromise(resolve => resolve(value))
    
    // 添加静态reject方法
    static reject(value) 
      return new MyPromise((resolve, reject) => reject(value))
    
    // 添加静态all方法
    static all(list) 
      return new MyPromise((resolve, reject) => 
        /**
         * 返回值的集合
         */
        let values = []
        let count = 0
        for (let [i, p] of list.entries()) 
          // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
          this.resolve(p).then(res => 
            values[i] = res
            count++
            // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
            if (count === list.length) resolve(values)
          , err => 
            // 有一个被rejected时返回的MyPromise状态就变成rejected
            reject(err)
          )
        
      )
    
    // 添加静态race方法
    static race(list) 
      return new MyPromise((resolve, reject) => 
        for (let p of list) 
          // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
          this.resolve(p).then(res => 
            resolve(res)
          , err => 
            reject(err)
          )
        
      )
    
    finally(cb) 
      return this.then(
        value => MyPromise.resolve(cb()).then(() => value),
        reason => MyPromise.resolve(cb()).then(() => 
          throw reason
        )
      );
    
  

参考
Promise实现原理(附源码)
Promise的运行原理以及重写Promise内置类

实现浅拷贝


// 1. ...实现
let copy1 = ...x:1

// 2. Object.assign实现

let copy2 = Object.assign(, x:1)

实现一个基本的深拷贝


// 1. JOSN.stringify()/JSON.parse()
let obj = a: 1, b: x: 3
JSON.parse(JSON.stringify(obj))

// 2. 递归拷贝
function deepClone(obj) 
  let copy = obj instanceof Array ? [] : 
  for (let i in obj) 
    if (obj.hasOwnProperty(i)) 
      copy[i] = typeof obj[i] === \'object\' ? deepClone(obj[i]) : obj[i]
    
  
  return copy

使用setTimeout模拟setInterval


// 可避免setInterval因执行时间导致的间隔执行时间不一致
setTimeout (function () 
  // do something
  setTimeout (arguments.callee, 500)
, 500)

实现一个基本的Event Bus

class EventEmitter 
    constructor() 
        this._event = this._event || new Map() // 储存事件
        this._maxListeners = this._maxListeners || 10 // 设置监听上限
    


EventEmitter.prototype.emit = function(type, ...args) 
    let handler = null
    handler = this._event.get(type)

    if (Array.isArray(handler)) 
        // 有多个监听者,需要依次触发
        for (let i = 0; i < handler.length; i++) 
            if (args.length > 0) 
                handler[i].apply(this, args)
             else 
                handler[i].call(this)
            
        
     else if (handler && typeof handler === \'function\') 
        if (args.length > 0) 
            handler.apply(this, args)
         else 
            handler.call(this)
        
    

    return true


EventEmitter.prototype.addListener = function(type, fn) 
    let handler = this._event.get(type)

    if (!handler) 
        this._event.set(type, fn)
     else if (handler && typeof handler === \'function\') 
        // 如果handler是函数,说明目前已经存在一个监听者
        this._event.set(type, [handler, fn])
     else 
        // 已经有多个监听者,直接push
        handler.push(fn)
    


EventEmitter.prototype.removeListener = function(type, fn) 
    let handler = this._event.get(type)

    if (handler && typeof handler === \'function\') 
        // 只有一个监听者,直接删除
        this._event.delete(type, fn)
     else if (Array.isArray(handler)) 
        // 是数组,说明被监听多次,要找到对应的函数
        let position = -1

        for (let i = 0; i < handler.length; i++) 
            if (handler[i] === fn) 
                position = i
            
        

        // 如果匹配,从数组中移除
        if (position !== -1) 
            handler.splice(position, 1)

            // 移除后,如果监听只剩一个,那么取消数组,以函数形式保存
            if (handler.length === 1) 
                this._event.set(type, handler[0])
            
         else 
            return this
        
    

实现一个双向数据绑定

let obj = 
let input = document.getElementById(\'input\')
let span = document.getElementById(\'span\')
Object.defineProperty(obj, \'text\', 
    configurable: true,
    enumerable: true,
    get() 
        console.log(\'获取数据了\')
        return obj.text
    ,
    set(newVal) 
        console.log(\'数据更新了\')
        input.value = newVal
        span.innerHTML = newVal
    
)
input.addEventListener(\'keyup\', function(e) 
    obj.text = e.target.value
)

实现一个简单路由


class Route 
    constructor() 
        // 保存所有路由
        this.routes = 

        // 当前hash
        this.currentHash = \'\'

        // 绑定this,避免监听时this指向改变
        this.freshRoute = this.freshRoute.bind(this)

        // 初始化监听函数
        window.addEventListener(\'load\', this.freshRoute, false)
        window.addEventListener(\'hash\', this.freshRoute, false)
    

    // 注册并存储路由
    storeRoute(path, fn) 
        let callback = fn || function() 
        this.routes[path] = callback
    

    // hash值改变
    freshRoute() 
        this.currentHash = location.hash.slice(1) || \'/\'
        this.routes[this.currentHash]()
    

实现懒加载

// html:
// <ul>
//   <li><img src="./imgs/default.png" data="./imgs/1.png" ></li>
//   <li><img src="./imgs/default.png" data="./imgs/2.png" ></li>
//   <li><img src="./imgs/default.png" data="./imgs/3.png" ></li>
//   <li><img src="./imgs/default.png" data="./imgs/4.png" ></li>
//   <li><img src="./imgs/default.png" data="./imgs/5.png" ></li>
//   <li><img src="./imgs/default.png" data="./imgs/6.png" ></li>
//   <li><img src="./imgs/default.png" data="./imgs/7.png" ></li>
//   <li><img src="./imgs/default.png" data="./imgs/8.png" ></li>
//   <li><img src="./imgs/default.png" data="./imgs/9.png" ></li>
//   <li><img src="./imgs/default.png" data="./imgs/10.png" ></li>
// </ul>

let imgs = document.querySelectorAll(\'img\')

// 获取窗口的显示高度
let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight

// 获取滚动条滚动的高度
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop

// 懒加载函数
function lazyLoad() 

    // 获取元素到文档顶部的距离
    function getTop(e) 
        let h = e.offsetTop
        while (e = e.offsetParent) 
            h += e.offsetTop
        
        return h
    

    imgs.forEach(ele => 
        // 元素顶部到视窗底部的距离差
        let x = scrollTop + clientHeight - getTop(ele)
        if (x > 0 && x < clientHeight + ele.height) 
            ele.src = ele.getAttribute(\'data\')
        
    )



// 也可直接通过 e.getBoundingClientRect 获取元素相对视窗的距离做判断
// function lazyLoad() 

//     function isIn(e) 
//         // 返回元素的大小及其相对于视口的位置。
//         let bound = e.getBoundingClientRect();
//         let is = (bound.top <= clientHeight) && (bound.top > 0)
//         return is
//     
//     imgs.forEach(ele => 
//         if (is) 
//             ele.src = ele.getAttribute(\'data\')
//         
//     )
// 

// 定时操作
setInterval(lazyLoad, 1000)

rem实现原理

function setRem () 
  let doc = document.documentElement
  let width = doc.getBoundingClientRect().width
  // 假设设计稿为宽750,则rem为10px
  let rem = width / 75
  doc.style.fontSize = rem + \'px\'

手写实现AJAX

// 手写实现AJAX

// xhr 简单实现

let xhr = new XMLHttpRequest()

// 初始化
xhr.open(method, url, async)

// 状态检测
// readyState 属性,该属性表示请求/响应过程的当前活动阶段。
// 0:未初始化。尚未调用 open()方法。
// 1:启动。已经调用 open()方法,但尚未调用 send()方法。
// 2:发送。已经调用 send()方法,但尚未接收到响应。
// 3:接收。已经接收到部分响应数据。
// 4:完成。已经接收到全部响应数据,而且已经可以在客户端使用了。
// 只要 readyState 属性的值由一个值变成另一个值,都会触发一次 readystatechange 事件。

// status:响应的 HTTP 状态。
xhr.onreadystatechange = function () 
  if (xhr.readyStatus === 4 && xhr.status === 200) 
    console.log(xhr.responseText)
  


// 发送
xhr.send(data)


// 基于promise
function ajax(options) 
  // 初始化参数
  let url = options.url
  let method = options.method.toLocaleLowerCase() || \'get\'
  let async = options.async
  let data = options.data

  let xhr = new XMLHttpRequest()
  if (options.timeout && options.timeout > 0) 
    xhr.timeout = options.timeout
  

  // 返回Promise结果
  return new Promise((resolve, reject) => 
    // 必须在调用 open()之前指定 onreadystatechange事件处理程序才能确保跨浏览器兼容性。
    xhr.onreadystatechange = function () 
      if (xhr.readyStatus === 4) 
        try 
          if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) 
            alert(xhr.responseText)
            // 决议成功
            resolve && resolve(xhr.responseText)
           else 
            // alert(\'Request was unsuccessful: \' + xhr.status)
            // reject && reject(e)
            resolve && resolve()
          
         catch (e) 
          // 决议失败
          reject && reject(e)
          console.log(\'异常处理\')
        
      
    

    // XMLHttpRequest事件
    // loadstart:在接收到响应数据的第一个字节时触发。
    // progress:在接收响应期间持续不断地触发。
    // error:在请求发生错误时触发。
    // abort:在因为调用 abort()方法而终止连接时触发。
    // load:在接收到完整的响应数据时触发。
    // loadend:在通信完成或者触发 error、 abort 或 load 事件后触发。
    // 每个请求都从触发 loadstart 事件开始,接下来是一或多个 progress 事件,
    // 然后触发 error、abort 或 load 事件中的一个,最后以触发 loadend 事件结束。

    // 请求发生错误监听
    xhr.onerror = (err) => reject && reject(err)

    // 超时监听
    xhr.ontimeout = (err) => reject && reject(err)

    // 判断请求方法
    // get方法 拼接查询字符串
    if (method === \'get\') 
      let paramArray = []
      let encodeParam

      if (typeof data === \'object\') 
        for (let key in data) 
          paramArray.push(
            encodeURIComponent(key) + \'=\' + encodeURIComponent(data[key])
          )
        
        encodeParam = paramArray.join(\'&\')
      
      // 判断是否已有查询字符串 没有添加?号 有则添加&
      url += url.indexOf(\'?\') == \'-1\' ? \'?\' : \'&\'
      // 拼接查询字符串
      url += encodeParam

      xhr.open(method, url, async)
    

    if (method === \'post\') 
      let dataForm = new FormData(data)
      xhr.send(dataForm)
     else 
      xhr.send(null)
    
  )


参考:
XMLHttpRequest—必知必会
你不知道的 XMLHttpRequest

实现一个节流(throttle)函数

函数频繁操作,在规定时间内只执行一次,在大于等于执行周期时才执行,周期内调用不执行。

// 时间戳版本 比较时间
function throttle(fn, delay) 
  // 利用闭包保存时间
  let prev = Date.now()
  return function () 
    let context = this
    let arg = arguments
    let now = Date.now()
    if (now - prev >= delay) 
      fn.apply(context, arg)
      prev = Date.now()
    
  



// 定时器版本
function throttle(fn, delay) 
  let time
  return function () 
    let context = this
    let arg = arguments
    if (!time) 
      time = setTimeout(() => 
        time = null
        fn.apply(context, args)
      , delay)
    
  



// 精简版

function throttle(fn, delay) 
  let bool = true
  return function () 
    let context = this
    let arg = arguments
    if (bool) 
      fn.apply(context, args)
      bool = false
      setTimeout(() => 
        bool = true
      , 2000)
    
  

实现一个防抖(debounce)函数

在函数需要频繁触发时,只有当有足够空闲的时间时,才执行一次。
和节流函数的区别:在发生持续触发事件时,防抖设置事件延迟并在空闲时间去触发事件,而节流则是隔一定的时间触发一次。

function debounce(fn, time) 
  // 利用闭包保存定时器
  let delay
  return function () 
    let context = this
    let arg = arguments
    // 清除定时器
    clearTimeout(delay)
    // 重新指定定时器
    delay = setTimeout(() => 
      fn.apply(context, arg)
    , time)
  

JavaScript高级学习笔记目录(持续更新)

【JavaScript高级】this绑定、绑定优先级、相关面试题与箭头函数
【JavaScript高级】浏览器原理:渲染引擎解析页面步骤、回流和重绘、composite合成、defer与async
【JavaScript高级】JavaScript的运行原理:V8引擎,JS代码执行原理,作用域和作用域链面试题
【JavaScript高级】内存管理与闭包:垃圾回收GC、闭包定义、访问和执行过程、内存泄漏
【JavaScript高级】函数相关知识:函数、纯函数、柯里化、严格模式
【JavaScript高级】对象相关:对象属性、属性描述符(数据属性,存取属性)
【JavaScript高级】原型和继承相关:原型对象、函数原型、原型链和继承、继承的优化、对象判断相关方法
【JavaScript高级】class类、ES6实现继承、ES6对象增强
【JavaScript高级】手写apply()、call()、bind()
【JavaScript高级】ES6常见新特性:词法环境、let、const、模板字符串、函数增强、Symbol、Set、Map
【JavaScript高级】ES7-ES13常用新特性
【JavaScript高级】Proxy和Reflect

以上是关于JavaScript———原理题的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript高级学习笔记目录(持续更新)

JavaScript高级学习笔记目录(持续更新)

前端面试题之JavaScript篇

Javascript面试题一套

理解JavaScript Call()函数原理。

22 道高频 JavaScript 手写面试题及答案