- var bar = 1;
- let obj = {
- bar: 2,
- foo: function(){
- console.log(this.bar);
- }
- }
- var fn = obj.foo;
- obj.foo();//2 this指向obj
- fn();//1 this执行全局对象window中的bar
严格模式下直接函数中直接调用this为undefined,非严格模式下为window
- function fn(){
- console.log(this);//window
- }
- fn();
- function Fn(){
- this.a = 1;
- console.log(this); //Fn {a: 1}
- }
- console.log(new Fn());//Fn {a: 1}
- var bar = 1;
- let obj = {
- bar: 2,
- foo: function(){
- console.log(this);//{bar: 2, foo: ƒ}
- }
- }
- obj.foo();//2 this指向obj
- var x = 0;
- var obj1 = {
- x:1
- }
- var obj2= {
- x:2
- }
-
- function fn(){
- console.log(this, this.x);
- }
- obj2.fn = fn;
- obj2.fn(); //2 this执行obj2
- obj2.fn.call(obj1); //1 this执行obj1
- obj2.fn.call(); //0 不写参数,执行window
语法: function.call(thisArg, arg1, arg2, ...)。 其中thisArg是要设置为函数执行上下文的对象,也就是this要指向的对象,从第二个参数开始,arg1, arg2, ... 是传递给函数的参数。通过使用call方法,可以将一个对象的方法应用到另一个对象上。
注意 如果省略第一个 thisArg 参数,则默认为 undefined。在非严格模式下,this 值将被替换为 globalThis(类似于全局对象)
- "use strict"
- const person1 = {
- fn: function () {
- console.log(this)
- }
- }
-
- const person2 = {
- name: 'person2'
- }
- person1.fn.call()
语法:function.apply(thisArg, [argsArray])。 其中thisArg是要设置为函数执行上下文的对象,也就是this要指向的对象,argsArray是一个包含参数的数组。通过使用apply方法,可以将一个对象的方法应用到另一个对象上,并使用数组作为参数。
使用场景:ES5下将一个数组的元素合并到另一个数组,arr.push.apply(arr,arr2)
- let arr = ['a','b'];
- let arr2 = [1,2];
- // 通过call方法可以将arr和arr2进行合并到arr中(ES6中可以使用扩展运算符)
- // arr.push(...arr2);
- // console.log(arr);//['a', 'b', 1, 2]
- // ES5中可以使用apply
- arr.push.apply(arr,arr2);
- console.log(arr);//['a', 'b', 1, 2]
语法:function.bind(thisArg, arg1, arg2, ...)。 其中thisArg是要绑定到函数执行上下文的对象,也就是this要指向的对象,从第二个参数开始,arg1, arg2, ...是传递给函数的参数。与call和apply方法不同,bind方法并不会立即执行函数,而是返回一个新函数,可以稍后调用。这对于事件处理程序和setTimeout函数等场景非常有用。
- const person1 = {
- name: 'allen',
- fn: function () {
- console.log(this.name)
- }
- }
-
- const fn = person1.fn
- fn() // undefined
-
- // 改写成,this指向person1
- // const fn = person1.fn.bind(person1)
- // 利用bind给定时器传值
- function fn(name){
- console.log("Hello, "+name);
- }
- // delayFn(name); //直接传值是会报错的
- const delayFn = fn.bind(null, 'lmf');
- setTimeout(delayFn,1000);
call和apply都是直接调用函数,bind不会立即调用
call和bind的参数为参数列表,apply的参数需要以数组的形式传递
原理:
首先,通过 Function.prototype.myCall 将自定义的 myCall 方法添加到所有函数的原型对象上,使得所有函数实例都可以调用该方法。
在 myCall 方法内部,首先通过 typeof this !== "function" 判断调用 myCall 的对象是否为函数。如果不是函数,则抛出一个类型错误。
然后,判断是否传入了上下文对象 context。如果没有传入,则将 context 赋值为全局对象;ES11 引入了 globalThis,它是一个统一的全局对象,无论在浏览器还是 Node.js 中,都可以使用 globalThis 来访问全局对象。
接下来,使用 Symbol 创建一个唯一的键 fn,用于将调用 myCall 的函数绑定到上下文对象的新属性上。
将调用 myCall 的函数赋值给上下文对象的 fn 属性,实现了将函数绑定到上下文对象上的效果。
调用绑定在上下文对象上的函数,并传入 myCall 方法的其他参数 args(因为call方法是立即执行函数所以需要调用方法并执行)。
将绑定在上下文对象上的函数删除,以避免对上下文对象造成影响。
返回函数调用的结果。
为什么要将调用 myCall 的函数绑定到上下文对象的新属性上?
- let obj1 = {
- fn: function(){}
- };
- let obj2 = {
- name:'lmf'
- }
- // 将obj1.fn的this执行改为obj2,且obj2的所有属性name也绑定到this上下文执行对象上
- obj1.fn.call(obj2);
手写方法:
- // 1.Function.prototype.myCall
- Function.prototype.myCall = function (context, ...args) {
- // 2.判断调用者是否为一个函数(如,obj1.fn.call这里的this即为obj1.fn函数)
- if (typeof this !== 'function') {
- throw new TypeError("Function.prototype.myCall - 被调用的对象必须是函数");
- }
-
- // 3.判断是否传入上下文对象(第一个参数)
- // 如果没有传入上下文对象,则默认为全局对象
- // ES11 引入了 globalThis,它是一个统一的全局对象
- // 无论在浏览器还是 Node.js 中,都可以使用 globalThis 来访问全局对象。
- context = context || globalThis;
-
- // 4.通过Symbol创建唯一的fn防止命名冲突,用于调用函数的上下文对象绑定到新的属性上
- let fn = Symbol('key');
-
- // 5.将上下文对象绑定到新的属性上
- context[fn] = this;
-
- // 6.调用绑定好上下文的新函数,并传入参数,并接受
- const result = context[fn](...args);
-
- // 7.删除通过Symbol绑定的上下文新属性,以防对原有属性产生影响
- delete context[fn];
-
- // 8.返回调用的函数结果
- return result;
- }
-
- // 测试手写的call方法
- let obj1 = {
- fn: function () {
- console.log(this.name);
-
- }
- };
- let obj2 = {
- name: 'lmf'
- }
- // 将obj1.fn的this执行改为obj2,且obj2的所有属性name也绑定到this上下文执行对象上
- obj1.fn.call(obj2); //lmf
- obj1.fn.myCall(obj2); //lmf
原理:apply的实现思路跟call类似,就是apply传入参数是以数组的形式传入,所以多了一步判断传入的参数是否为数组以及在调用方法的时候使用扩展运算符 ... 将传入的参数数组 argsArr 展开
- Function.prototype.myApply = function (context, argsArr) {
- if (typeof this !== 'function') {
- throw new TypeError("Function.prototype.myCall - 被调用的对象必须是函数");
- }
-
- if(argsArr && !Array.isArray(argsArr)){
- throw new TypeError("Function.prototype.myApply - 第二个参数必须是数组")
- }
-
- context = context || globalThis;
-
- let fn = Symbol('key');
-
- context[fn] = this;
-
- // 判断argsArr是否为一个数组(apply方法参数必须是一个数组),不是数组直接执行
- const result = Array.isArray(argsArr) ? context[fn](...argsArr) : context[fn]();
-
- delete context[fn];
-
- return result;
- }
-
- // 测试手写的apply方法
- let obj1 = {
- fn: function () {
- console.log(this.name);
-
- }
- };
- let obj2 = {
- name: 'lmf-apply'
- }
- // 将obj1.fn的this执行改为obj2,且obj2的所有属性name也绑定到this上下文执行对象上
- obj1.fn.apply(obj2); //lmf-apply
- obj1.fn.myApply(obj2); //lmf-apply
原理:
首先,通过 Function.prototype.myBind 将自定义的 myBind 方法添加到所有函数的原型对象上,使得所有函数实例都可以调用该方法。
在 myBind 方法内部,首先通过 typeof this !== "function" 判断调用 myBind 的对象是否为函数。如果不是函数,则抛出一个类型错误。
然后,判断是否传入了上下文对象 context。如果没有传入,则将 context 赋值为全局对象;ES11 引入了 globalThis,它是一个统一的全局对象,无论在浏览器还是 Node.js 中,都可以使用 globalThis 来访问全局对象。
保存原始函数的引用,使用 _this 变量来表示。(因为调用bind后返回的函数可能会用new的方式再创建)
返回一个新的闭包函数 fn 作为绑定函数(bind方法不是立即执行函数)。这个函数接受任意数量的参数 innerArgs。
在返回的函数 fn 中,首先判断是否通过 new 关键字调用了函数。这里需要注意一点,如果返回出去的函数被当作构造函数使用,即使用 new 关键字调用时,this 的值会指向新创建的实例对象。通过检查 this instanceof fn,可以判断返回出去的函数是否被作为构造函数调用。这里使用 new _this(...args, ...innerArgs) 来创建新对象。
如果不是通过 new 调用的,就使用 apply 方法将原始函数 _this 绑定到指定的上下文对象 context 上。这里使用 apply 方法的目的是将参数数组 args.concat(innerArgs) 作为参数传递给原始函数。
args表示myBind()创建时的23,12,使用...args是因为bind函数需要参数列表。而后面再次调用_This.apply(context, args.concat(innerArgs))时,apply参数是数组。arg就表示没有展开的数组
- Function.prototype.myBind = function(context, ...args){
- if(typeof this !== 'function'){
- throw new TypeError("Function.prototype.myCall - 被调用的对象必须是函数");
- }
-
- context = context || globalThis;
-
- // bind函数不会立即执行,所以不需要绑定新属性并执行
- let _This = this;
-
- // bind函数会返回一个函数,所以通过匿名函数方法返回。innerArgs表示调用bind后返回的函数执行时传入的参数
- return function fn(...innerArgs){
- // 判断如果fn通过new Fn()形式被调用( new delayFn(2,3))
- if(this instanceof fn){ //判断fn在this的原型上
- return new _This(...args,...innerArgs);
- }
-
- // 使用apply方法将原函数绑定到指定的上下文对象上
- return _This.apply(context, args.concat(innerArgs));
- }
- }
- // 测试手写的bind方法
- let obj1 = {
- hello: function () {
- console.log(this.name);
- }
- };
- let obj2 = {
- name: 'lmf-bind'
- }
- // 将obj1.fn的this执行改为obj2,且obj2的所有属性name也绑定到this上下文执行对象上
- let delayFn1 = obj1.hello.bind(obj2); //lmf-bind
- let delayFn2 = obj1.hello.myBind(obj2,23,12); //lmf-bind
-
- delayFn1(1,2);
- delayFn2(1,2);
- console.log(new delayFn1(3,4));
- console.log(new delayFn2(3,4));
手写call方法时第4步,为什么要将调用 myCall 的函数绑定到上下文对象的新属性上?
我的理解比如将obj1.fn的this改变为obj2,那么obj1的fn里面就可以通过this调用到obj2的name属性
但是obj1.fn.call(obj2);这句话真正的目的是想要将obj1.fn的this指向obj2,即通过obj2.fn也能调用obj1.fn的方法
当然最后是能实现将obj1.fn的this改变为obj2,那么obj1的fn里面就可以通过this调用到obj2的name属性
- let obj1 = {
- fn: function(){}
- };
- let obj2 = {
- name:'lmf'
- }
- // 将obj1.fn的this执行改为obj2,且obj2的所有属性name也绑定到this上下文执行对象上
- obj1.fn.call(obj2);(可以调用obj1上的fn)