手写call apply bind bind源码解析

一、作用

call、apply、bind用于改变函数执行时的this指向

二、用法

fn.call(fn,...params)
fn.apply(fn,[...params])
fn.bind(fn,..params)
注:call和apply执行fn返回结果并改变this指向 bind函数只改变this指向不执行返回一个新的函数,具体用法本文不做多于介绍

三、手写函数

通过对象调用属性方法的方式来修改this指向
1.手写$call

		let obj = {
			name: '哈哈',
			age: 12
		}
		Function.prototype.$call =  function() {
			let fn = this
			let o = arguments[0]
			let params = [...arguments].splice(1)
			o.fn = fn
			o.fn(...params)
		}
		function see(pa) {
			this.age = pa
			console.log(this.name, this.age)
		}
		see.$call(obj, 13)

2.手写$apply

		let obj = {
			name: '123'
		}
		function fn(str) {
			console.log(this.name,str)
		}
		Function.prototype.$apply = function() {
			let con = [...arguments][0]
			con.fn = this
			let params = [...arguments][1]
			return con.fn(...params)
		}
		fn.$apply(obj,['haha'])

call与apply实现思路基本相同只是对参数传递形式做了不同处理

3.手写$bind

		let obj = {
			name: '123',
			age: 12
		}
		Function.prototype.$bind =  function() {
			let fn = this
			let o = [...arguments][0]
			let args = [...arguments].splice(1)
			console.log(args)
			return function() {
				fn.call(args === [] ? o : o,...args)
			}
		}
		function see(pa) {
			this.age = pa
			console.log(this.name, this.age)
		}
		see.$bind(obj)()

bind方法返回一个函数

以上代码只展现了方法实现的具体思路,在源码中还会对继承问题兼容问题进行处理下面以bind为例研究一下

  var ArrayPrototypeSlice = Array.prototype.slice;
  Function.prototype.bind = function(otherThis) {
    var baseArgs= ArrayPrototypeSlice.call(arguments, 1),
        baseArgsLength = baseArgs.length,
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          baseArgs.length = baseArgsLength; // reset to default base arguments
          baseArgs.push.apply(baseArgs, arguments);
          return fToBind.apply(
            fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
          );
        };

    if (this.prototype) {
      fNOP.prototype = this.prototype;
    }
    fBound.prototype = new fNOP();

    return fBound;
  };

首先来看继承,源码中的继承关系通过一个空函数fNOP来完成,此过程相当于在原函数的prototype后方加上一条新的分支返回的新函数可以通过这条分支访问原函数原型链上的所有属性和方法
在这里插入图片描述

–新的原型链关系图–

疑问1 为什么不将原函数的prototype 直接赋值给fBound的prototype,而通过空函数FNOP来实现原型关系
解答: 首先我们要明白实现继承的目的是为了bind返回的新函数可以使用所有原函数原型上的方法和属性,如果直接将原函数的prototype 直接赋值给fBound的prototype,由于引用类型的特点指向同一个内存地址,当返回的新函数的原型链发生改变时会同时影响原函数的原型链关系,这是我们不希望看到的

疑问2 fNOP.prototype.isPrototypeOf(this)的作用
解答: bind()返回的新函数在执行时会去判断fnop的prototype是否在当前this的原型链上,那么什么情况下的this的原型链存在fnop的prototype呢?
这时候我们就需要搞清楚bind返回的新函数fBound的this指向问题, 由于bind的作用是修改this指向,我们很容易想当然的认为fBound中的this 指向bind函数中传入的obj对象。这是不对的,我们始终要去关注fBound的执行环境。

		var ArrayPrototypeSlice = Array.prototype.slice;
		let obj = {
			name: '张三'
		}
		Function.prototype.$bind = function(otherThis) {
		var baseArgs= ArrayPrototypeSlice.call(arguments, 1),
			baseArgsLength = baseArgs.length,
			fToBind = this,
			fNOP    = function() {},
			fBound  = function() {
				this.haha = 1
				baseArgs.length = baseArgsLength; // reset to default base arguments
				baseArgs.push.apply(baseArgs, arguments);
				console.log(fNOP.prototype.isPrototypeOf(this), this, '12312312')
				return fToBind.apply(
				// {↓此行标记↓}
				fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
				);
			};
		if (this.prototype) {
		  // Function.prototype doesn't have a prototype property
		  fNOP.prototype = this.prototype;
		}
		fBound.prototype = new fNOP();

		return fBound;
		};
		function see(a){
			console.log(a)
			console.log(this, this.name)
		}
		see.$bind(obj,1)()
		let xxx = see.$bind(obj,1)
		let o = new xxx()
		

执行see.$bind(obj,1)()此时see中this指向obj 打印this.name 输出张三 同时bind返回的函数fBound中this的打印结果为window对象 显然window对象的原型链上不存在 fNOP.prototype fNOP.prototype.isPrototypeOf(this)为false
执行new xxx() fBound打印this结果为fBound对象
fNOP.prototype.isPrototypeOf(this)为true

由此可见fNOP.prototype.isPrototypeOf(this) 的目的是 当fBound作为构造函数出现时我们希望原函数的this同样指向实例对象 避免fBound作为构造函数去new时产生意料之外的结果

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值