# JS原理题

# call,apply,bind 三者的区别:

  • 共同点就是修改this指向,不同点就是
  • 1.call()和apply()是立刻执行的, 而bind()是返回了一个函数
  • 2.call则可以传递多个参数,第一个参数和apply一样,是用来替换的对象,后边是参数列表。
  • 3.apply最多只能有两个参数——新this对象和一个数组argArray

# 1. 实现一个call函数

  • 思路:将要改变this指向的方法挂到目标this上执行并返回,核心是借助this的隐式绑定
  • https://github.com/mqyqingfeng/Blog/issues/11
  • call做了什么:
    • 将函数设为对象的属性
    • 执行&删除这个函数
    • 指定this到函数并传入给定参数执行函数
    • 如果不传入参数,默认指向为 window
Function.prototype.myCall = function(ctx) {
  if (typeof this !== 'function') {
    throw new TypeError('not function')
  }
  //1.判断有没有传入要绑定的对象,没有默认是window,
  //如果是基本类型的话通过Object()方法进行转换
  ctx = Object(ctx) || window
  let aArgs = Array.prototype.slice.call(arguments, 1)
  /**
    在指向的对象obj上新建一个fn属性,值为this,也就是fn()
    相当于obj变成了
    {
        value: 'foo',
        fn: function fn() {
          console.log(this.value);
        }
    }
  */
  ctx.fn = this
  let result = ctx.fn(...args);
  // 删除该属性
  delete ctx.fn;
  return result;
};

//下面是三种截取除第一个参数之外剩余参数的方法
//const args = [...arguments].slice(1);
//const args = Array.prototype.slice.call(arguments, 1);
//const args = Array.from(arguments).slice(1);

# 2. 实现一个apply函数

Function.prototype.myApply = function(ctx) {
  ctx = Object(ctx) || window;
  ctx.fn = this;
  let args = Array.prototype.slice(arguments, 1)
  let result;
  if (!args) {
    result = ctx.fn();
  } else {
    result = ctx.fn(...args)
  }
  delete ctx.fn;
  return result;
};

# 3、实现一个bind函数

  • https://github.com/mqyqingfeng/Blog/issues/12
  • https://juejin.im/post/5e17f16f5188254d3f73c7df
  • 实现bind要做什么
    • 返回一个函数,绑定this,传递预置参数
    • bind返回的函数可以作为构造函数使用。故作为构造函数时应使得this失效,但是传入的参数依然有效
// mdn的实现
if (!Function.prototype.bind) {
  Function.prototype.bind = function(ctx) {
    //判断调用bind的是不是一个函数,不是的话就要抛出错误
    if (typeof this !== 'function') {
      throw new Error('not function')
    }
    var aArgs = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(
            (this instanceof fBound ? this : ctx), 
            aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 维护原型关系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();

    return fBound;
  };
}

let obj = {
  a:1
}
function fn(name, age){
  this.test = '测试数据1' 
  console.log(this.a)
  console.log(name, age)
  return this.test
}
fn.prototype.f = "测试数据2"

let a = fn.myBind(obj, 'xiao')
let b = new a('ming')
b.f = '测试数据3'
console.log(b)
console.log(b.__proto__ === fn.prototype)
console.log(b.__proto__.__proto__ === fn.prototype)

# 4、Object.create的基本实现原理

  • Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
function create(obj) {
  function F() {}
  F.prototype = obj
  return new F()
}

function create(obj) {
  return {
    '__proto__': obj
  }
}

function create(obj){
  return Object.setPrototypeOf({}, obj)
}

# 5、instanceof的原理

  • 思路:右边变量的原型存在于左边变量的原型链上
function instance_of(L, R) {
  //L 表示左表达式,R 表示右表达式
  var O = R.prototype; // 取 R 的显示原型
  L = L.__proto__; // 取 L 的隐式原型
  while (true) {
    if (L === null) return false;
    if (O === L)
      // 这里重点:当 O 严格等于 L 时,返回 true
      return true;
    L = L.__proto__;
  }
}

# 6、模拟new

  • https://juejin.im/post/5bde7c926fb9a049f66b8b52
  • new操作符做了这些事:
    • 它创建了一个全新的对象
    • 它会被执行[[Prototype]](也就是__proto__)链接
    • 它使this指向新创建的对象
    • 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上
    • 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用
/**
 * 模拟实现 new 操作符
 * @param  {Function} ctor [构造函数]
 * @return {Object|Function|Regex|Date|Error}      [返回结果]
 */
function newOperator(ctor){
    if(typeof ctor !== 'function'){
      throw 'newOperator function the first param must be a function'
    }
    newOperator.target = ctor // ES6 new.target 是指向构造函数
    // 1.创建一个全新的对象,
    // 2.并且执行[[Prototype]]链接
    // 4.通过`new`创建的每个对象将最终被`[[Prototype]]`链接到这个函数的`prototype`对象上。
    var newObj = Object.create(ctor.prototype);
    // ES5 arguments转成数组 当然也可以用ES6 [...arguments], Array.from(arguments);
    // 除去ctor构造函数的其余参数
    var argsArr = [].slice.call(arguments, 1);
    // 3.生成的新对象会绑定到函数调用的`this`。
    // 获取到ctor函数返回结果
    var ctorReturnResult = ctor.apply(newObj, argsArr);
    // 小结4 中这些类型中合并起来只有Object和Function两种类型 typeof null 也是'object'所以要不等于null,排除null
    var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null;
    var isFunction = typeof ctorReturnResult === 'function';
    if(isObject || isFunction){
        return ctorReturnResult;
    }
    // 5.如果函数没有返回对象类型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),那么`new`表达式中的函数调用会自动返回这个新的对象。
    return newObj;
}

// 无注释版
function newOperator(ctor){
  if(typeof ctor !== 'function'){
    throw new TypeError('not function')
  }
  newOperator.target = ctor;
  var args = [].slice.call(arguments,1)
  var newObj = Object.create(ctor.prototype)
  var res = ctor.apply(newObj, args)
  if((typeof res === 'object'&& res !== null) || typeof res === 'function'){
    return res
  }
  return newObj;
}

let child1 = newOperator(Child1, 'child', 18)

# 7、实现类的继承

  • 类的继承在几年前是重点内容,有n种继承方式各有优劣,es6普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。
  • https://juejin.im/post/5c433e216fb9a049c15f841b
  • https://juejin.im/post/5c8e409ee51d4534977bc557
function Parent(name) {
    this.parent = name
}
Parent.prototype = {
  constructor: Parent, //需要手动绑定constructor属性
  say: function() {
      console.log(`${this.parent}: 你打篮球的样子像kunkun`)
  }
}

function Child(name, parent) {
    // 将父类的构造函数绑定在子类上
    Parent.call(this, parent)
    this.child = name
}

/** 
 1. 这一步不用Child.prototype =Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类
 2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性
 3. Object.create是创建了父类原型的副本,与父类原型完全隔离
*/
Child.prototype = Object.create(Parent.prototype)

Child.prototype.say = function() {
    console.log(`${this.parent}好,我是练习时长两年半的${this.child}`);
}


Child.prototype.constructor = Child // 注意记得把子类的构造指向子类本身
Object.setPrototypeOf(Child, Parent) //继承父类的静态属性

var parent = new Parent('father');
parent.say() // father: 你打篮球的样子像kunkun

var child = new Child('cxk', 'father');
child.say() // father好,我是练习时长两年半的cxk

# 7、实现一个基本的Promise

// 未添加异步处理等其他边界情况
// ①自动执行函数,②三个状态,③then
class Promise {
  constructor (fn(resolve, reject)) {
    // 三个状态
    this.state = 'pending'
    this.value= undefined
    this.reason = undefined
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
      }
    }
    let reject = value => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = value
      }
    }
    // 自动执行函数
    try {
      fn(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  // then 方法 有两个参数onFulfilled onRejected
  then(onFulfilled, onRejected) {
    switch (this.state) {
      case 'fulfilled':
        onFulfilled(this.value);
        break
      case 'rejected':
        onRejected(this.reason);
        break
      default:
    }
  }
}