欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

js手写call、apply、bind

程序员文章站 2022-07-14 14:28:22
...

三者异同

三者都用于改变this指向

  • call:func.call(thisArg, arg1, arg2, ...)
    • 第一个参数作为this(func 的 this 指向 thisArg),后面的参数可以由很多个
    • 返回函数执行结果
    • func 会被调用
  • apply:func.call(thisArg, [arg1, arg2, ...])
    • 第一个参数作为 this,后面只有一个数组参数
    • 返回函数执行结果
    • func 会被调用一次
  • bind:func.call(thisArg)
    • 参数作为 this
    • 返回一个函数
    • func 不会被调用

手写call

// 保证所有函数都可以调用,所以得写到Function的原型上
Function.prototype.call2 = function (con) {
	// 确保是函数在调用call方法
	if (typeof this !== 'function') { // this指向调用call的函数
		throw new TypeError('Error')
	}
	
	// con为null、undefined则赋值window,否则转化为对象类型
	con = Object(con) || window // T1
	const args = [...arguments].slice(1) // 从第二个参数开始存进args
	// 给con添加属性fn
	con.fn = this               // T2
	// 将args传入fn并执行它
	const n = con.fn(...args)   // T3
	delete con.fn // 删掉fn属性
	return n
}
  • 关于T1
    • 此处源码则写了一个类型处理的函数,根据不同类型做不同的包装。引用类型可以直接扩展属性,原始类型则得被相应的包装对象所代替(例:Number(num)
  • 关于T2、T3步骤的解释
    • 假设是 func.call(…),T1的 this 是 func 的,赋值给 con 的新属性 fn,然后再执行 con.fn,则fn的this指向了 con,则相当于 func 的 this 指向了 con
  • 注意:虽然只写了 con 一个参数,但是传参时并不代表只能传一个,可以传任意多个甚至不传,因为参数都会传入一个数组,然后再被函数接收,所以数组里包含了什么并不重要
  • 如果 this 不熟悉可以参考详解this

手写apply

  • 实现apply只要修改传参方式即可,且相同注释不再粘贴
Function.prototype.apply2 = function (con) {
	if (typeof this !== 'function') {
		throw new TypeError('Error')
	}
	
	con = Object(con) || window
	con.fn = this
	let n
	// 此处只对第二个参数起效,多余参数忽略
	if (arguments[1]) {
		// 假如传入了第二个参数,则将该参数传入fn并调用fn
		n = con.fn(...arguments[1])
	} else {
		// 未传入第二个参数则不传参给fn
		n = con.fn
	}
	delete con.fn
	return n
}

手写bind

  • bind 得返回一个新函数。注意:MDN中介绍到,当 bind 返回的函数被 new 时,忽略传入的 this 值
Function.prototype.bind2 = function (con) {
	if (typeof this !== 'function') {
		throw new TypeError('Error')
	}
	
	const args = [...arguments].slice(1)
	const that = this // 牵涉到 “闭包”,得一次性定死this的值,指向调用函数
	return function f () {
		if (this instanceof f) { // T1
			return new that(...args, ...arguments)
		}
		return that.apply(con, args.concat(...arguments))
	}
}
  • 关于T1
    • 判断this是否是构造函数的实例(通过new 函数名()而来,或者说调用了 new 运算符),如果是则会创建了新对象,而 this 指向了新对象,所以得重新调用new that()将this指回去(因为that是不变的)
    • instanceof 用于判断一个对象的原型链是否存在于一个构造函数的prototype属性上,即可以用来判断 this 是否通过 new 而来
  • 如果对 new 不了解,可以参考js的new操作符的详解和实现
  • 如果对闭包不了解,可以参考js闭包详解