JS手写题(call、apply、bind、new、instanceOf、原型继承)

引言

希望通过以下这几道手写题能够加深对JS基础知识点:this绑定、new、原型继承相关知识的理解。

this的指向

this它代表了一个函数执行上下文中的当前对象,this的值取决于函数是如何被调用的,而不是如何被定义的。

【函数调用模式】由于foo是直接调用的,所以this绑定的是全局对象。

var a = 1
function foo(){
    //"use strict";
    var b = 2
    console.log(this.b);
}
foo()

【方法调用模式】而通过obj.sayHello的方式调用方法时,函数调用中的this会绑定到对象obj上。此时name打印出来的是Alice。

let obj = { 
  name: 'Alice',
  sayHello: function() { 
  console.log(this.name); 
  }
}
obj.sayHello() 

【构造器调用模式】如果一个函数用new调用时,函数执行前会创建一个新对象,this指向这个新创建的对象。

function foo(){
    var b = 2
    console.log('b', this);
}
new foo()

【apply、call和bind调用模式】这三个方法都可以显示的指定调用函数的 this 指向。

当我们了解了this指向的相关知识后,我们需要了解call、apply、bind内部是如何实现this的重新绑定的,下面的call、apply、bind的手写题目。

1.call

js中call方法可以设置this的绑定,实现代码如下:核心思路是将this指针赋值给context.fn然后通过context.fn()来执行方法,这样子就将函数的this绑定到context对象上。

Function.prototype.myCall = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError("Not a function")
    }

    // 不传参数默认是window
    context = context || window
    // 保存this
    context.fn = this
    // Array.from 把伪数组转为数组,然后调用slice去掉第一个参数
    let args = Array.from(arguments).slice(1)
    
    // 调用函数
    let result = context.fn(...args)

    delete context.fn
    return result
}

function foo() {
    console.log(this.a)
}

var obj = {
    a: 2
}

foo.call(obj) // 2
2.apply

和call差不多,只是获取参数的方式不一样

  Function.prototype.myApply = function (context) {
      if (typeof this !== 'function') {
          throw new TypeError("Not a function")
      }
  
      // 不传参数默认是window
      context = context || window
      // 保存this
      context.fn = this
      
      console.log('arguments', ...arguments)
      // 调用函数
      let result = context.fn(...arguments[1])
  
      delete context.fn
      return result
    }
    function foo(b, c) {
      console.log("my call", this.a, b, c) // 2 b c
    }
  
    var obj = {
      a: 2
    }
  
    foo.myApply(obj, ["b", "c"])
3.bind
Function.prototype.myBind = function(context) {
      // this指的是需要进行绑定的函数本身,例如foo
      const fn = this

      const args = Array.from(arguments).slice(1)

      const bindFn = function () {
       
        return fn.apply(
          // 如果是对象的方式创建则需要绑定this
          this instanceof bindFn ? this : context,
           // arguments是myBind返回的函数传入的参数
          args.concat(...arguments)
        )
      }
      // 返回的绑定函数的原型对象改为原函数的原型对象,即可满足原来的继承关系
      bindFn.prototype = Object.create(fn.prototype)
      return bindFn
}

function foo(b, c) {
    console.log("my call",this, this.a, b, c)
}
var obj = {
   a: 2
}
foo.prototype.sayHi = function() {
   console.log('hello')
}
const fn = foo.myBind(obj)
fn("bind:1", "bind:2") // my call {a: 2} 2 bind:1 bind:2
const a = new fn("bind:3", "bind:4") // my call bindFn {} undefined bind:3 bind:4
a.sayHi() // hello

可以看到直接通过方法调用fn()/通过new调用(new fn())两种方式得到的this是完全不同的。通过new调用this指向的是这个新创建的对象,而不是传入的context,所以this打印出来的是bindFn{}。

​​​​​​​​​​​​​​构造函数and原型and原型链

构造函数是用来生成特定类型对象的模版,通常使用构造函数来新建一个对象。

原型对象是存放该类型所有实例共享属性和方法的地方。

构造函数通过其prototype属性链接到原型对象,而新创建的实例则通过内部的__proto__/Object.getPrototype(obj)链接到构造函数的原型对象上,形成原型链。

当访问一个对象的时候,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性。而原型对象又会有自己的原型,这就是原型链。

constructor、prototype和__proto__的关系?

构造函数的prototype指向原型对象。原型对象的constructor指向构造函数。实例对象的__proto__指向原型对象。

现在我们已经了解原型对象、原型链、构造函数的含义,我们可以接着来看看js中的new和Object.create具体都做了什么。

1.new

用途:根据传入的构造函数,创建一个指向构造函数原型的对象。注意处理构造函数的this绑定。

function myNew(fn, ...args) {
      // 创建一个空对象
      let obj = {}
      // 使得空对象的隐式原型指向原函数的显示原型
      obj.__proto__ = fn.prototype
      // this 指向obj
      let result = fn.apply(obj, args)
      // 返回
      return result instanceof Object ? result : obj
}
function foo(b) {
      console.log(this, b) // foo{}, b
}
foo.prototype.sayHi = function() {
   console.log('hello')
}
myNew(foo, 'b')
2.原型式继承(Object.create)

用途:根据传入的原型对象o新建返回一个继承自o的实例对象。

创建一个方法,把方法的原型改成传入的原型对象,然后再用new新建一个方法对象。

function myobject(o) {
      function F() {} // 创建一个方法
      F.prototype = o // 把方法的原型对象改成o
      return new F()  // 通过new返回一个原型对象
}
let father = function() {}
father.prototype.getName = function() {
      console.log('minor')
}

let son = myobject(father)
son.prototype.getName()

其他高频JS手写题

1.通用数据类型判断
function getType(obj) {
    let type = typeof obj
    if (type !== "object") { // 先用typeof判断基础类型数据
        return type
    }
    // 对象类型再调用toString进行判断,正则返回最后的类型
    return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1')
}

console.log('gettype []:', getType([])) // gettype []: Array
2.模拟instanceOf(LO, RF) 
function myInstance(L, R) {
    let RP = R.prototype // 取R的显式原型
    let LP = L.__proto__ // 取L的隐式原型
    while(true) {
        if (LP === null) return false
        if (RP === LP) return true
        LP = LP.__proto__
    }
}

3.lodash.get(obj, path, default)​​​​​​​​​​​​​​

function _get(obj, path, defaultValue) {
// 匹配[]""''.这几个字符之外的任意字符 例如a[0].b.c匹配后 返回[ 'a', '0', 'b', 'c' ]
  const result = path.match(/[^\[\]""''.]+/g).filter(Boolean)
      .reduce((o, k) => o === undefined ? o : o[k], obj);
 return result === undefined ? defaultValue : result;
}
// 使用示例
const obj = { a: [{ b: { c: 3 } }] };
console.log(_get(obj, 'a[0].b.c', 'default')); // 输出: 3
console.log(_get(obj, 'a[0].b.d', 'default')); // 输出: 'default'
console.log(_get(obj, 'a[0]["b"]["c"]', 'default')); // 输出: 3
 4.deepClone​​​​​​​

function deepClone(startObj) {
    const obj = Array.isArray(startObj) ? [] : {}
    for (let key in startObj) {
        if (typeof startObj[key] === 'object') {
            obj[key] = deepClone(startObj[key])
        } else {
            obj[key] = startObj[key]
        }
    }
    return obj
}

function test() {
   const a = { 
        b : { c: 1},
        d : [1, 2]
     }
    const b = [1, 2, a ]
    console.log(deepClone(a), deepClone(b))
}
​​​​​​​5.debounce,throllte  
/ 单位时间内频繁触发一个事件,以最后一次触发为准
function debounce(func, delay) {
    let timer
    return function() {
        clearTimeout(timer)
        timer = setTimeout(() => {
            func.call(this)
        }, delay)
    }
}
// 单位时间内频繁触发一个事件,只触发一次
function throllte(func, delay) {
    let timer
    return function() {
        if (timer) return
        timer = setTimeout(() => {
            func.call(this)
            timer = null
        }, delay)
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值