js手写题

写在最前:文章转自掘金

时间戳格式化

  formatTime(originVal) {
    const dt = new Date(Number(originVal*1000))
    const y= dt.getFullYear()
    // padStart 是ES2017 引入的字符串补全长度方法
    const m = (dt.getMonth() + 1 + '').padStart(2, '0')
    const d = (dt.getDate() + '').padStart(2, '0')
    const hh = (dt.getHours() + '').padStart(2, '0')
    const mm = (dt.getMinutes() + '').padStart(2, '0')
    const ss = (dt.getSeconds() + '').padStart(2, '0')
    // return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
    return `${y}-${m}-${d}`
  }

数据类型判断

typeof 可以正确识别:UndefinedBooleanNumberStringSymbolFucntion 等类型的数据,但是对于其他的都会认为是 object,比如NullDate等,所以通过 typeof 来判断数据类型会不准确。但是可以使用Object.prototype.toString 实现。对于 Object.prototype.toString() 方法,会返回一个形如 [object XXX]的字符串。如果对象的 toString() 方法未被重写,就会返回如上面形式的字符串。

({}).toString();     // => "[object Object]"
Math.toString();     // => "[object Math]"

但是,大多数对象,toString() 方法都是重写了的,这时,需要用call()Reflect.apply() 等方法来调用。

function typeOf(obj) {
  return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()
}
typeOf([])        // 'array'
typeOf({})        // 'object'
typeOf(new Date)  // 'date'

function typeOf1(obj) {
  return Reflect.apply(Object.prototype.toString, obj, []).slice(8, -1).toLowerCase()
}
typeOf1([])        // 'array'
typeOf1({})        // 'object'
typeOf1(new Date)  // 'date'

继承

原型链继承
function Animal() {
    this.colors = ['black', 'white']
}
function Dog() {}
Dog.prototype =  new Animal()

let dog1 = new Dog()
dog1.colors.push('brown')
let dog2 = new Dog()
console.log(dog2.colors)  // ['black', 'white', 'brown']

原型链继承存在的问题:

  • 问题1:原型中包含的引用类型属性将被所有实例共享;
  • 问题2:子类在实例化的时候不能给父类构造函数传参;
借用构造函数实现继承
function Animal(name){
  this.name = name
  this.getName = function() {
    return this.name
   }
}
function Dog(name) {
   Animal.call(this, name)
}
let dog1 = new Dog()
dog1.colors.push('brown')
let dog2 = new Dog()
console.log(dog2.colors)  // ['black', 'white', 'brown']

借用构造函数实现继承解决了原型链继承的2个问题:引用类型共享问题以及传参问题。但方法必须定义在构造函数中,所以导致每次创建子类实例,父类都会创建一遍方法,严重影响性能。

组合继承

组合继承结合了原型链和盗用构造函数,将两者的优点集中了起来。基本的思路是使用原型链继承重复且不变的原型上的属性和方法,通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

function Animal(name) {
    this.name = name
    this.colors = ['black', 'white']
}
Animal.prototype.getName = function() {
    return this.name
}
function Dog(name, age) {
    Animal.call(this, name)
    this.age = age
}
Dog.prototype =  new Animal()

let dog1 = new Dog('奶昔', 2)
dog1.colors.push('brown')
let dog2 = new Dog('哈赤', 1)

console.log(dog2) // { name: "哈赤", colors: ["black", "white"], age: 1 }
dog2.getName() // '哈赤'

class 实现继承

class Animal {
  constructor(name){
    this.name = name 
  }
  getName() {
    return this.name
  }
}
class Dog extends Animal {
  constructor(name, age) {
    super(name)
    this.age = age
  }
}

数组去重

es5 实现:

let arr = [1,2,2,4,5,6,1,7]
let newarr = arr.filter((item, index, array)=>{
// 判断元素位置与首次出现位置是否一致,
//如果一致说明是第一次出现,相反是重复出现给过滤掉
  return array.indexOf(item) == index
})
console.log(newarr)

es6实现

let arr = [1,2,2,4,5,6,1,7]
let newarr = new Set([...arr])
console.log(newarr)

数组扁平化

在es6中有现成的方法Array.prototype.flat是数组扁平化
现在就是要实现flat这种效果。
es5递归实现

let arr = [1,[2,[3]]]
function flatten(arr){
  let newArr = []
  for(let i = 0; arr.length > i; i++){
    if(Array.isArray(arr[i])) newArr = newArr.concat(flatten(arr[i])) // 保存外部newArr
    else newArr.push(arr[i])
  }
  return newArr
}
flatten(arr)

es6实现

function flatten(arr){
  while(arr.some(item=> Array.isArray(item))){
    // 层层剥开嵌套数组,直至无嵌套数组
    let arr= [].concat([...arr])
  }
  return arr
}

深浅拷贝

浅拷贝:只考虑对象类型

function shallowCopy(obj){
  if(typeof obj == 'object') return
  let newObj = obj instanceof Array ? [] : {}  // 计算obj的构造函数是否为数组
  for (let key in obj){
    if(obj.hasOwnProperty(key)){  // 只复制自身属性
      newObj[key] = obj[key]
    }  
  }
  return newObj
}

简单深拷贝:只考虑普通对象属性,不考虑内置对象和函数。

function deepClone(obj){
  if(typeof obj == 'object') return 
  let newObj = obj instanceof Array ? [] : {}
  for (let key in obj){
    if(obj.hasOwnProperty(key)){
      newObj[key] = typeof obj[key] == 'object' ? deepClone(obj[key]) : obj[key]
    }
  }
  return newObj
}

复杂版深克隆:基于简单版的基础上,还考虑了内置对象比如 Date、RegExp 等对象和函数以及解决了循环引用的问题。

事件总线(发布订阅者模式)

class EventEmitter {
  constructor() {
    this.cache = {} // 初始化事件对象
  }
  on(name, fn) {
    if (typeof fn == 'function') {
      if (this.cache[name]) {
        this.cache[name].push(fn)
      } else {
        this.cache[name] = [fn]
      }
    }
  }
  off(name) {
    if (this.cache[name]) {
      delete this.cache[name]
    }
  }
  emit(name, ...agr) {
    if (this.cache[name]) {
      for (let fn of this.cache[name]) {
        fn(...agr)
      }
    }
  }
}
let eventBus = new EventEmitter()
let fn1 = function (name, age) {
  console.log(`我是${name},今年${age}`)
}
let fn2 = function (name, age) {
  console.log(`今年${age},我是${name}`)
}
eventBus.on('aaa', fn1)
eventBus.on('aaa', fn2)
eventBus.emit('aaa', '狗狗', '2岁')

解析URL参数为对象

简单版

function parsePrama(){
  let search = window.location.search.slice(1).split('&')
  let prama = {}
  search.forEach(item=>{
    let arr = item.split('=')
    prama[arr[0]] = arr[1]
  })
  return prama
}
parsePrama()

复杂版 未阅读

function parseParam(url) {
    const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
    const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
    let paramsObj = {};
    // 将 params 存到对象中
    paramsArr.forEach(param => {
        if (/=/.test(param)) { // 处理有 value 的参数
            let [key, val] = param.split('='); // 分割 key 和 value
            val = decodeURIComponent(val); // 解码
            val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
    
            if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
                paramsObj[key] = [].concat(paramsObj[key], val);
            } else { // 如果对象没有这个 key,创建 key 并设置值
                paramsObj[key] = val;
            }
        } else { // 处理没有 value 的参数
            paramsObj[param] = true;
        }
    })
    
    return paramsObj;
}

字符串模板

function render(template, data){
  const reg = /\{\{(\w+)\}\}/  // 获取双括号规则
  if(reg.test(template)){
    const name = reg.exec(template)[0] // 获取第一个模板字符
    template = template.replace(reg, data[name])
    return render(template, data)
  }
  return template
}

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let person = {
    name: '布兰',
    age: 12
}
render(template, person); // 我是布兰,年龄12,性别undefined

图片懒加载

  let imgList = Array.from(document.querySelectorAll('img'))  // 获取img节点并转为数组
  let length = imgList.length
  const imgLazyload = (function () {
    let count = 0
    return function () {
      let deleteIndexList = []
      imgList.forEach((img, idx) => {
        let rect = img.getBoundingClientRect()
        if (rect.top < window.innerHeight) {  // 出现在可视范围内的图片
          img.src = img.dataset.src  // 给图片赋值真实的图片地址
          deleteIndexList.push(idx)  // 记录赋值图片的下标
          count++  // 记录赋值图片的个数
          console.log(rect.top)
          if (count == length) {  // 如果计数与img数相同,移除滚动懒加载函数
            document.removeEventListener('scroll', debounce)
          }
        }
      })
      imgList = imgList.filter((img, idx) => !deleteIndexList.includes(idx))  // 很重要,删除已经显示的图片列表
    }
  })()
  window.onload = imgLazyload   // 初次打开页面后展示可视范围内图片
  document.addEventListener('scroll', debounce(imgLazyload), 500)   // 滚动加载可视范围内图片,加防抖函数

函数防抖

触发高频事件N秒后只会执行一次,如果N秒内事件再次触发,则会重新计时。
给待防抖的函数包装一层函数,该函数使用闭包保存了一个 timer 变量,当document调用时,仅限接受document调用时传递的参数。
简单版:函数内部支持使用thisevent对象

function debounce(func, wait){
  var timer;
  // return函数是为了返回未执行的函数
  return function(){
    let that = this;               // 保留当前this指向 document对象
    let agrs = arguments;          // 获取事件 event 对象
    clearTimeout(timer);           // 每次调用清空调用函数计时
    // 重新计时调用函数
    timer = setTimeout(function(){
      func.apply(that, args)       // 指定调用函数当前的this值 和 event 事件对象
    }, wait)
  }
}

使用:

 function test1(e){
    console.log(this, e) // 分别打印:document 这个节点 和 MouseEvent
}
document.addEventListener('scroll',debounce(test1,1000))

最终版:除了支持thisevent外,还支持以下功能:

  • 支持立即执行;
  • 函数可能有返回值;
  • 支持取消功能;
function debounce(func, wait, immediate) {
  let timer, result;
  let debounced = function() {
    let that = this;
    let args = argoumnets;
    if(timer) clearTimeout(timer);
    if(immediate) {
      let callNow = !timeout;
      timeout = setTimeout(function(){
        func.apply(that, args)
      }, wait)
      if(callNow) result = func.apply(that, args)
    } else {
         timer = setTimeout(function(){
            func.apply(that, args)
          }, wait);
      }
      return result;
  };
  debounced.cancel = function() {
    clearTimeout(timer);
    timer = null;
  };
  return debounced;
}

使用:

var setUseAction = debounce(getUserAction, 10000, true);
// 使用防抖
node.onmousemove = setUseAction
// 取消防抖
setUseAction.cancel()

函数节流

触犯高频事件,且N秒内只执行一次。

简单版:使用时间戳来实现,立即执行一次,然后每N秒执行一次。

function throttle(func, wait){
  let previous = 0;            // 第一次直接执行
  let that, args;
  return function() {
    that = this;
    args = arguments;
    let now = +new Date();    // 获取当前时间
    // 如果当前时间与上一个时间节点差大于 wait 那么执行函数,并把当前时间置为上一个时间节点
    if(now-previous >wait){
      func.apply(that, args);
      previous = now
    }
  }
}

最终版:支持取消节流;另外通过传入第三个参数,options.leading 来表示是否可以立即执行一次,options.trailing 表示结束调用的时候是否还要执行一次,默认都是true。注意设置的时候不能同时将leadingtrailing 设置为false。太复杂没看。

function throttle(func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;                    
    if (!options) options = {};

    var later = function() {
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
        if (!timeout) context = args = null;
    };

    var throttled = function() {
        var now = new Date().getTime();
        if (!previous && options.leading === false) previous = now;
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
            if (!timeout) context = args = null;
        } else if (!timeout && options.trailing !== false) {
            timeout = setTimeout(later, remaining);
        }
    };
    
    throttled.cancel = function() {
        clearTimeout(timeout);
        previous = 0;
        timeout = null;
    }
    return throttled;
}

函数组合

函数组合就是将两个或两个以上的函数组合成一个新函数的过程。

函数组合的作用

在项目开发过程中,为了实现函数的复用,我们通常尽量保证函数的职责单一,比如我们定义了一下功能的函数:


在拥有以上功能函数的基础上,我们可以自由地对函数进行组合,来实现特定的功能:

function lowerCase(input) {
    return input && typeof input === "string" ? input.toLowerCase() : input;
  }

  function upperCase(input) {
    return input && typeof input === "string" ? input.toUpperCase() : input;
  }

  function trim(input) {
    return typeof input === "string" ? input.trim() : input;
  }

  function split(input, delimiter = ",") {
    return typeof input === "string" ? input.split(delimiter) : input;
  }

  const composeFn = function (f, g, t) {
    return function (x) {
      return t(g(f(x)))
    }
  }
  const trimLowerCaseAndSplit = composeFn(trim, lowerCase, split);
  trimLowerCaseAndSplit(" a,B,C ")  // ["a", "b", "c"]

在以上的代码中,我们通过 composeFn 函数实现了一个 trimLowerCaseAndSplit 函数,该函数会对输入的字符串,先执行去空格处理,然后在把字符串中包含的字母统一转换为小写,最后在使用 , 分号对字符串进行拆分。利用函数组合的技术,我们就可以很方便的实现一个 trimUpperCaseAndSplit 函数。

组合函数的实现
function compose(...funcs) {
  return function(x) {                      // 返回组合函数并接受参数
    // 组合函数内 存有函数的数组funcs,并按照从左至右的顺序执行,每次执行都将上一次返回值作为参数带入,最终获得组合函数返回值
    return funcs.reduce((arg, fn)=>{
      return fn(arg);
    }, x)
  }
}

以上代码执行顺序与Linux管道或过滤器的执行顺序是一致的。


如果想从右往左开始执行的话,可以使用 Array.prototype.reduceRight 方法来实现。
异步函数组合

(async () => {
    function compose (...funcs) {
      if (funcs.length === 0) {
        return args => args
      }
      if (funcs.length === 1) {
        return funcs[0]
      }
      return funcs.reduce((a, b) => async (...args) => b(await a(...args)))
    }
    function fn1 (arg) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          arg += '_Promise_fn1'
          console.log('fn1')
          resolve(arg)
        }, 1000)
      })
    }
    function fn2 (arg) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          arg += '_Promise_fn2'
          console.log('fn2')
          resolve(arg)
        }, 1000)
      })
    }
    function fn3 (arg) {
      arg += '_fn3'
      return arg;
    }
    const result = compose(fn1, fn3, fn2)
    result(1).then(res => {
      console.log('异步返回', res)
    })
  })()

函数柯里化

将使用多个参数的函数转换成一系列使用一个参数的函数的技术。有参数复用和延迟计算/运算的作用。

function check(a, b) {
    return a.test(b)
}
let checkCurry = curry(check)
checkCurry(/[a-z]+/g)('test')

现在就要实现curry这个函数,使函数从一次调用传入多个参数变成多次调用每次传一个参数。

function curry(fn) {
    let judge = (...args) => {
        if (args.length == fn.length) return fn(...args)
        return (...arg) => judge(...args, ...arg)
    }
    return judge
}

偏函数

偏函数就是将一个 n 参的函数转换成固定 x 参的函数,剩余参数(n - x)将在下次调用全部传入。没看。

惰性函数

由于不同浏览器之间存在一些兼容性问题,这导致了我们在使用一些 Web API 时,需要进行判断,比如:

function addHandler(element, type, handler) {
  if (element.addEventListener) {
    element.addEventListener(type, handler, false);
  } else if (element.attachEvent) {
    element.attachEvent("on" + type, handler);
  } else {
    element["on" + type] = handler;
  }
}

在以上代码中,我们实现了不同浏览器 添加事件监听 的处理。代码实现起来也很简单,但存在一个问题,即每次调用的时候都需要进行判断,很明显这是不合理的。对于上述这个问题,我们可以通过惰性载入函数来解决。
所谓的惰性载入就是当第 1 次根据条件执行函数后,在第 2 次调用函数时,就不再检测条件,直接执行函数。要实现这个功能,我们可以在第 1 次条件判断的时候,在满足判断条件的分支中覆盖掉所调用的函数,具体的实现方式如下所示:

function addHandler(element, type, handler) {
  if (element.addEventListener) {
    addHandler = function (element, type, handler) {
      element.addEventListener(type, handler, false);
    };
  } else if (element.attachEvent) {
    addHandler = function (element, type, handler) {
      element.attachEvent("on" + type, handler);
    };
  } else {
    addHandler = function (element, type, handler) {
      element["on" + type] = handler;
    };
  }
  // 保证首次调用能正常执行监听
  return addHandler(element, type, handler);
}

除了使用以上的方式,我们也可以利用自执行函数来实现惰性载入:

const addHandler = (function () {
  if (document.addEventListener) {
    return function (element, type, handler) {
      element.addEventListener(type, handler, false);
    };
  } else if (document.attachEvent) {
    return function (element, type, handler) {
      element.attachEvent("on" + type, handler);
    };
  } else {
    return function (element, type, handler) {
      element["on" + type] = handler;
    };
  }
})();

通过自执行函数,在代码加载阶段就会执行一次条件判断,然后在对应的条件分支中返回一个新的函数,用来实现对应的处理逻辑。

JSONP

JSONP 核心原理:script 标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于 GET 请求;

缓存函数

缓存函数是将函数的计算结果缓存起来,当下次以同样的参数调用该函数时,直接返回已缓存的结果,而无需再次执行函数。这是一种常见的以空间换时间的性能优化手段。
要实现缓存函数的功能,我们可以把经过序列化的参数作为 key,在把第 1 次调用后的结果作为 value 存储到对象中。在每次执行函数调用前,都先判断缓存中是否含有对应的 key,如果有的话,直接返回该 key 对应的值。分析完缓存函数的实现思路之后,接下来我们来看一下具体如何实现:

function memorize(fn) {
  const cache = Object.create(null); // 存储缓存数据的对象
  return function (...args) {
    const _args = JSON.stringify(args);
    return cache[_args] || (cache[_args] = fn.apply(fn, args));
  };
};

定义完 memorize 缓存函数之后,我们就可以这样来使用它:

let complexCalc = (a, b) => {
  // 执行复杂的计算
};

let memoCalc = memorize(complexCalc);
memoCalc(666, 888);
memoCalc(666, 888); // 从缓存中获取

AJAX

const getJSON = function(url) {
    return new Promise((resolve, reject) => {
        const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
        xhr.open('GET', url, false);
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.onreadystatechange = function() {
            if (xhr.readyState !== 4) return;
            if (xhr.status === 200 || xhr.status === 304) {
                resolve(xhr.responseText);
            } else {
                reject(new Error(xhr.responseText));
            }
        }
        xhr.send();
    })
}

实现数组原型方法

forEach
Array.prototype.forEach2=function(callback,thisArg){
    if(this == null){
        throw new TypeError('this is null or not defined')
    }
    if(typeof callback !== "function"){
        throw new TypeError(callback+'is not a function')
    }
    const O = Object(this) // this 就是当前的数组
    const len = O.length >>> 0
    let k = 0
    while(k<len){
        if(k in O){
            callback.call(thisArg,O[k],k,O);
        }
        k++
    }
}

O.length >>> 0 是什么操作?就是无符号右移 0 位,那有什么意义嘛?就是为了保证转换后的值为正整数。其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型。感兴趣可以阅读 something >>> 0是什么意思?

filter
some
reduce
map

基于forEach的实现能够很容易写出map的实现:

 Array.prototype.map2 = function(callback, thisArg) {
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
   let k = 0, res = []
    while (k < len) {
        if (k in O) {
           res[k] = callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
   return res
}

实现函数原型方法

手写 call和apply
  // 隐式改变this的指向
  Function.prototype.myCall = function (...args) {
    let [context, ...other] = args;
    context = context ? Object(context) : window;  // 将目标作用域包装成对象
    const _symbol = Symbol('特殊属性Symbol')        // 在目标作用域中新建唯一key值
    context[_symbol] = this                        // 将foo函数赋值给目标作用域中新的key
    
    const res = context[_symbol](...other);        // 执行目标作用域中的的foo函数并返回
    delete context[_symbol];                       // 销毁目标作用域中新增的方法
    return res;                                    // 返回函数执行结果
  }

  Function.prototype.myApply = function (context,args) {
    context = context ? Object(context) : window;  // 将目标作用域包装成对象
    const _symbol = Symbol('特殊属性Symbol')        // 在目标作用域中新建唯一key值
    context[_symbol] = this                        // 将foo函数赋值给目标作用域中新的key
    
    const res = context[_symbol](...args);        // 执行目标作用域中的的foo函数并返回
    delete context[_symbol];                       // 销毁目标作用域中新增的方法
    return res;                                    // 返回函数执行结果
  }

  let obj = { name: 'obj的name' }
  function foo(a,b) {
    console.log(this.name,a,b)
  }

  foo.myCall(obj,1,2)

实现 new 关键字

首先我们再来回顾下 new 操作符的几个作用

  • new 操作符会返回一个对象,所以我们需要在内部创建一个对象
  • 这个对象,也就是构造函数中的 this,可以访问到挂载在 this 上的任意属性
  • 这个对象可以访问到构造函数原型上的属性,所以需要将对象与构造函数链接起来
  • 返回原始值需要忽略,返回对象需要正常处理

回顾了这些作用,我们就可以着手来实现功能了

function Test(name) {
  this.name = name
}
Test.prototype.sayName = function () {
    console.log(this.name)
}
function createMy(Con, ...args) {
  let obj = Object.create(Con.prototype)
//   Object.setPrototypeOf(obj={}, Con.prototype)
  let result = Con.apply(obj, args)
  return result instanceof Object ? result : obj
}
let t = createMy(Test,'ycl')
console.log(t.name)  // ycl
t.sayName() // ycl

这就是一个完整的实现代码,我们通过以下几个步骤实现了它:

  • 首先函数接受不定量的参数,第一个参数为构造函数,接下来的参数被构造函数使用
  • 然后内部创建一个空对象 obj
  • 因为 obj 对象需要访问到构造函数原型链上的属性,所以我们通过 setPrototypeOf 将两者联系起来。这段代码等同于 obj.__proto__ = Con.prototype
  • obj 绑定到构造函数上,并且传入剩余的参数
  • 判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 obj,这样就实现了忽略构造函数返回的原始值

实现 instanceof 关键字

instanceof 就是判断构造函数的prototype属性是否出现在实例的原型链上。

function instanceOf(left, right){
  let proto= left._proto_
  while(true){
    if(proto === null) return false
    if(proto === right.prototype){
      return true
    }
    proto = proto._proto_
  }
}

上面的left.proto这种这种写法可以换成

实现 Object.create

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的_proto_

Object.create2 = function(proto, propertyObject = undefined){
  if(typeof proto !=='object' && typeof proto !== 'function'){
    throw new TypeError('Object prototype may only be an Object or null.')
  }
  if(propertyObject === null){
    new TypeError('Cannot conver underfined or null to object')
  }
  function F(){}
  F.prototype = proto
  const obj = new F()
  if(propertyObject ! == undefined){
    Object.definePropertied(obj, propertyObject)
  }
  if(proto === null){
    // 创建一个 没有原型对象的对象,Object.create(null)
    obj._proto_ = null
  }
  return obj
}

实现 Object.assign

该方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象

Object.assign2= function(target, ...source){
  if(target == null){
    throw new TypeError('Connot convert undefined or ')
  }
  let ret = Object(target)
  source.forEach(funtion(obj){
    if(obj!==null)  {
      for(let key in obj){
        if(obj.hasOwnProperty(key)){
          ret[key] = obj[key]
        }
      }
    }
  })
  return ret
}

promise

const PENDING = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';

    class MyPromise {
      constructor(executor) {
        this.status = PENDING; // 给实例初始化值
        this.value = undefined; // 处理值
        this.reason = undefined; // 拒绝值
        this.onResolveCallbacks = []; // 成功回调函数的容器
        this.onRejectedCallbacks = []; // 失败回调函数的容器

        // 定义处理函数
        let resolve = value => {
          if (this.status == PENDING) {
            this.status = FULFILLED;  // 处理函数将promise状态修改为fulfilled
            this.value = value;      // 如果有处理值,将处理值赋值给实例value
            this.onResolveCallbacks.forEach(fn => fn());  // 在处理函数中执行成功回调函数
          }
        };
        // 定义拒绝函数
        let reject = reason => {
          if (this.status == PENDING) {
            this.status = REJECTED; // 拒绝函数将promise状态修改为rejected
            this.reason = reason;    // 如果有拒绝值,将拒绝值赋值给实例reason               
            this.onRejectedCallbacks.forEach(fn => fn());  // 在拒绝函数中执行失败回调函数
          }
        };
        try {
          // 避免then方法中参数函数有错误,或者promise参数函数中有错误
          executor(resolve, reject);
        } catch (err) {
          reject(err);
        }
      }
      // then会返回一个新的promise;
      then(onFulfilled, onRejected) {
        // 如果then方法中未传值,为了让链式调用正常进行下去,需要判断 onFulfilled 和 onRejected 的类型;
        onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : v => v;
        // 如果未定义onRejected函数,将抛出错误,并在catch中捕获
        onRejected = typeof onRejected == 'function' ? onRejected : e => { throw err };

        let promise2 = new MyPromise((resolve, reject) => {
          // 如果处理函数执行了
          if (this.status == FULFILLED) {
            // onFulfilled 和 onRejected 需要被异步调用,这里用 setTimeout 模拟异步;宏任务执行
            setTimeout(_ => {
              try {
                // 这里的x可能是一个promise
                let x = onFulfilled(this.value);
                resolvePromise(promise2, x, resolve, reject)
              } catch (e) {
                reject(e)
              }
            }, 0)
          }
          // 如果拒绝函数执行
          if (this.status == REJECTED) {
            setTimeout(_ => {
              try {
                let x = onRejected(this.reason);
                resolvePromise(promise2, x, resolve, reject)
              } catch (e) {
                reject(e)
              }
            }, 0)
          }
          // 如果都未执行,所以得先用 onResolvedCallbacks 和 onRejectedCallbacks 分别把成功和失败的回调存起来;
          if (this.status == PENDING) {
            this.onResolveCallbacks.push(_ => {
              setTimeout(_ => {
                try {
                  let x = onFulfilled(this.value);
                  resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                  reject(e)
                }
              }, 0)
            })
            this.onRejectedCallbacks.push(_ => {
              setTimeout(_ => {
                try {
                  let x = onRejected(this.reason);
                  resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                  reject(e)
                }
              }, 0)
            })
          }
        })
        return promise2
      }
    }
    const resolvePromise = (promise2, x, resolve, reject) => {
      // 自己等待自己完成是错误的实现,用一个类型错误,结束掉 promise  Promise/A+ 2.3.1
      if (promise2 === x) {
        return reject(
          new TypeError("Chaining cycle detected for promise #<Promise>"));
      }
      // Promise/A+ 2.3.3.3.3 只能调用一次
      let called;
      // 后续的条件要严格判断 保证代码能和别的库一起使用
      if ((typeof x === "object" && x != null) || typeof x === "function") {
        try {
          // 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候)  Promise/A+ 2.3.3.1
          let then = x.then;
          if (typeof then === "function") {
            // 不要写成 x.then,直接 then.call 就可以了 因为 x.then 会再次取值,Object.defineProperty  Promise/A+ 2.3.3.3
            then.call(
              x, (y) => {
                // 根据 promise 的状态决定是成功还是失败
                if (called) return;
                called = true;
                // 递归解析的过程(因为可能 promise 中还有 promise) Promise/A+ 2.3.3.3.1
                resolvePromise(promise2, y, resolve, reject);
              }, (r) => {
                // 只要失败就失败 Promise/A+ 2.3.3.3.2
                if (called) return;
                called = true;
                reject(r);
              });
          } else {
            // 如果 x.then 是个普通值就直接返回 resolve 作为结果  Promise/A+ 2.3.3.4
            resolve(x);
          }
        } catch (e) {
          // Promise/A+ 2.3.3.2
          if (called) return;
          called = true;
          reject(e);
        }
      } else {
        // 如果 x 是个普通值就直接返回 resolve 作为结果  Promise/A+ 2.3.4
        resolve(x);
      }
    };
Promise.resolve

Promise.resolve(value)可以将任何值转成value状态是fulfilledPromise,但如果传入的值本身是Promise则会有原样返回。

Promise.resolve = function(value){
  // 如果是Promise,则直接输出 它
  if(value instanceof Promise){
    return value
  }
  return new Promise(resolve => resolve(value))
}
Promise.reject

Pormise.resolve类似,但不同的是Promise.reject()如果传入一个Promise对象,则这个对象会成为新的Promise的值。

Promise.reject = function(reason){
  return new Promise((resolve, reject) = > reject(reason))
}
Promise.all

Promise.all的规则是这样的:

  • 传入的所有Promise都是fulfilled,则返回由它们的值组成,状态为fulfilled的新Promise;
  • 只要有一个Promiserejected,则返回rejected状态的新Promise,且它的值是第一个rejectedPromise的值;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,312评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,578评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,337评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,134评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,161评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,303评论 1 280
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,761评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,421评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,609评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,450评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,504评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,194评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,760评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,836评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,066评论 1 257
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,612评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,178评论 2 341

推荐阅读更多精彩内容