关于call、apply 和 bind 都是用来改变函数的 this 指向,前两者的区别只是传递参数的形式不一样,apply的第二个参数需要以数组的方式传入。bind则是返回了修改this后的函数。这三种方法在每天搬砖的过程中都会被高频率的使用,让我们试试手动来实现一下吧。
call :
实现
Function.prototype.myCall = function (context) {
let obj = Object(context) || window //在没有传入修改后的上下文对象时,模式是全局上下文 window (ps:浏览器中)
let fn = Symbol() // 创建一个唯一标识
obj.fn = this // 将新建空对象上的属性赋值为将要执行的函数
let res = obj.fn(...[...arguments].splice(1)) // 将除上下文对象的参数传递到函数内执行
delete obj.fn // 删除新建的属性
return res // 当函数存在返回值时,返回对应值
}
var a = 1, b = 2;
var obj ={a: 10, b: 20}
function test(key1, key2, key3){
console.log(this[key1] + this[key2])
}
test('a', 'b') // 3
test.myCall(obj, ['a', 'b']) // 30
apply :
关于 apply 的实现实际上和 call 区别不大,只在参数的传递上存在区别。
Function.prototype.myApply = function (context) {
let obj = Object(context) || window
let fn = Symbol()
obj.fn = this
let arg = arguments[1].length ? [...arguments[1]] : [] // 传参时的区别
let res = obj.fn(...arg)
delete obj.fn
return res
}
var a = 1, b = 2;
var obj ={a: 10, b: 20}
function test(key1, key2){
console.log(this[key1] + this[key2])
}
test('a', 'b')
test.myApply(obj, ['a', 'b'])
bind:
bind与前两者的区别就是,返回了修改this后的函数并不会立即执行。
Function.prototype.myBind = function (context) {
let obj = Object(context) || window
let fn = Symbol()
obj.fn = this
let arg = [...arguments].splice(1)
return function () {
let res = obj.fn(...arg, ...arguments)
delete obj.fn
return res
}
}
var a = 1, b = 2, c = 3;
var obj ={a: 10, b: 20, c: 30}
function test(key1, key2, key3){
console.log(this[key1] + this[key2] + this[key3])
}
test('a', 'b', 'c') //6
test.myBind(obj, 'a', 'b')('c') //60
就这么结束了???too young...too simple...
function Foo (name, age) {
this.name = name
this.age = age
}
obj = {
name: 'lalala',
age: 2
}
name = 'heiheihei'
age = 3
console.log(new Foo(name, age)) // heiheihei 3
let fn1 = Foo.bind(obj, name, age)
console.log(new fn1(name, age)) // heiheihei 3
let fn2 = Foo.myBind(obj, name, age)
console.log(new fn2(name, age)) // {}
bind是会返回修改this后的函数,所以就存在修改后的函数被当做构造函数的可能。在补全这部分功能之前先了解new做了什么。
new 运算符
内部操作
查找资料后发现 new 的内部操作:
- 创建一个空的简单 JavaScript 对象;
- 链接该对象(即设置该对象的构造函数)到另一个对象 ;
- 将新创建的对象作为
this的上下文 ; - 如果该函数没有返回对象,则返回新创建的对象;
实现
function MyNew (fn) {
// 使用 Object.create 创建一个空对象并把新对象的 __proto__ 指向 fn 的原型
// 这样才会将构造函数实例的方法同步到新的对象中,例如👇实例中 person1.getName
let obj = Object.create(fn.prototype)
// 不使用 Object.create
//let obj = {}
//obj.__proto__ = fn.prototype
// 收集需要传递的参数
let args = [...arguments].splice(1)
// 将新建对象作为函数执行的上下文
// 这步操作就是让新建的对象会有函数中操作的属性值,例如👇示例中的 person1
let res = fn.apply(obj, args)
// 当函数的返回值为一个对象或一个函数时返回对应值;反之返回新建的对象。
return typeof res === 'object' || typeof res === 'function' ? res : obj
}
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.getName = function() {
return this.name
}
var person1 = MyNew(Person, '张三', 18);
var person2 = MyNew(Person, '李四', 18);
console.log(1111, person1) // 张三 18
console.log(222, person1.getName()) // 18
console.log(1111, person2) //李四 18
console.log(2222, person2.getName()) // 18
关于 Object.create()
语法
Object.create(proto,[propertiesObject])
- proto 新创建对象的原型对象
- propertiesObject 可选。需要传入一个对象,该对象的属性类型参照
Object.defineProperties()的第二个参数。如果该参数被指定且不为undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
与 new 和 字面量新建对象的区别
字面量和new关键字创建的对象是 Object 的实例,原型指向Object.prototype,继承内置对象Object;
Object.create(arg, pro)创建的对象的原型取决于arg,arg为null,新对象是空对象,没有原型,不继承任何对象;arg为指定对象,新对象的原型指向指定对象,继承指定对象;
理解
function create ( obj ) {
function f(){}
f.prototype = obj
return new f
}
// 更简单的理解~~
function create(obj) {
return {
__proto__: obj
}
}
bind 实现重写
Function.prototype.myBind2 = function (context) {
let fn = this
let arg = [...arguments].splice(1)
let fnForReturn = function () {
// 判断是否被 new 调用,也可以通过 ES6 中的 new.target 进行判断;
// 当作为构造函数时,this 指向的是 new 之后的实例;
if (this instanceof fnForReturn) {
return fn.apply(this, [...arg, ...arguments])
} else {
return fn.apply(context || window, [...arg, ...arguments])
}
}
// 将返回函数的实例赋值为初始被 bind 的函数实例,实例可以继承绑定原函数的值
fnForReturn.prototype = this.prototype
return fnForReturn
}
//---------------------------------测试一下效果👇--------------------------------------
function Foo (name, age) {
this.name = name
this.age = age
}
obj = {
name: 'lalala',
age: 2
}
name = 'heiheihei'
age = 3
console.log(new Foo(name, age)) // heiheihei 3
let fn1 = Foo.bind(obj, name, age)
console.log(new fn1(name, age)) // heiheihei 3
let fn2 = Foo.myBind2(obj, name, age)
console.log(new fn2(name, age)) // heiheihei 3
以上是自己在复习时的思路和简单实现,如果有哪里有问题还希望大佬们指正~~