一、call
Ⅰ、call 函数的简介:
1、call函数的用法
fn.call(obj,1,2);
A、call() 函数中的第一个参数表示:想让 this 指向的对象 (obj);
B、call() 函数中的第二及以后参数表示:传进去的实参;
2、call 函数的功能:
其一、让函数立执行;
其二、可改变 this 的指向;
其三、可实现继承问题;
Ⅱ、call 函数如何调用:
1、call() 函数的调用过程:
var obj = { a: 1 };
function fn(x, y) { // 每个函数都会有 call 方法;
console.log(this);
return x + y;
}
console.log(fn.call(null, 1, 2));
// 此时的输出结果为:Window, 3;
// 若此时的 call() 函数中的第一个参数未传值,或传值为 null 时,此时的 fn 中的 this 指向为:Window;
console.log(fn.call(obj, 1, 2));
// 此时的输出结果为:{a: 1}, 3;
// 若此时的 call() 函数中的第一个参数非空而是 obj ,那么此时的 fn 中的 this 指向为:obj;
2、call() 函数的调用结果展示为:
Ⅲ、模拟 call 函数的实现原理;
1、对 call 函数原理的模拟 (即:实现自己的 myCall 方法) ;
Function.prototype.myCall = function (context) {
// 其中 context 参数是指:改变 this 指向,而待指向的的参数值(如:obj);
// 此时是在 Function 的原型链上来添加 myCall() 函数, 那么此时该函数中的 this 指向的是:调用该 myCall() 方法的实例或函数;
// 处理参数(即:实参);
var args = [...arguments].slice(1);
// 将 arguments 的值从第二个截取后,放到 args 的数组里;
// 该操作是将传入的实参值转化成数组,以待后面使用;
context = context || window;
// 当传的值为 null 或没传值时(均为:false),此时 context 的值为:Window;
// 当传值时(为:true),此时的 context 的值为:context;
context.fn = this;
// 此时 this 指向的是:将来调用 myCall 方法的那个函数;
// 而又由于 context 调用 fn 这个属性是指向 this, 因此此时的 this 就指向了 context; 那么此时就完成了 this 指向 context 的目的;
// 该操作是:让将来调用 myCall 方法的那个函数,封装到 context 的一个属性中去(即:context.fn);
// 那么此时的 context.fn 就代表的是调用 myCall 方法的那个函数,它是一个函数;
var r = context.fn(...args);
// 该操作是:调用 context.fn() 函数,也就是调用了 '调用 myCall' 的函数;
// 并传参为:'...args', 也就是将 args 数组中的值展开作为参数传入函数中;
// 此时也就完成了,将 this 的指向为:context; 且还运行了 '调用 myCall' 的函数,并将参数传进去; (即:已经实现了 call() 函数的功能);
return r;
// 然后再将操作后的值返回;
}
var obj = { a: 1 };
function fn(x, y) {
console.log(this);
return x + y;
}
var sum = fn.myCall(obj, 1, 2);
console.log(sum);
// 此时的输出结果为:'{a: 1, fn: ƒ}', 3;
// 输出结果都正确,只是 context 中添加一个 fn 属性,但添加一个属性也是没大影响的;
// 注意:此时并没有模拟 this 指向 Window 的情况;
2、模拟的 myCall() 函数的调用结果展示为:
二、apply
Ⅰ、改变this指向
这是网上一个常见的例子:
var person = {
fullName: function() {
return this.firstName + " " + this.lastName;
}
}
var person1 = {
firstName: "Bill",
lastName: "Gates",
}
person.fullName.apply(person1); // 将返回 "Bill Gates"
如何理解?可以这么理解,person
有个方法fullName
调用this.firstName
和this.lastName
这两个变量,this
指向person
这个对象,但它是没有这两个变量的。
apply
可以改变调用apply方法的函数
(这里指的就是person.fullName
这个函数)的this
指向,现在person.fullName.apply(person1)就让this指向person1
了,fullName
方法就可以访问到这两个值,就成功输出。
Ⅱ、将数组入参变为一般入参
这里说一点,就是这个作用就是apply和call的最大区别了。
就是apply的第二个参数接受的是数组,call不是。
接收数组有什么用?
比如当一个函数入参是非数组,而你目前拥有的是数组,你不想处理数组再进行入参的输入,你就可以使用apply。
例子:
Math.max(1,2,3)//3
Math.max([1,2,3])//报错
Math.max.apply(null,[1,2,3])//3
需要注意的是这里的第一个值为null时,
在 “JavaScript 严格模式”下则它将成为被调用函数的所有者(对象)也就是没改变指向
,在“非严格”模式下,它成为全局对象
。
因为我们只是测试第二个入参作用,因此,第一个入参null就用来占位,就算改变了指向我们也没有用到它。
以下的例子可以加深一下你对apply和call区别的理解。
apply:
var person = {
fullName: function(city, country) {
return this.firstName + " " + this.lastName + "," + city + "," + country;
}
}
var person1 = {
firstName:"John",
lastName: "Doe"
}
person.fullName.apply(person1, ["Oslo", "Norway"]);
call:
var person = {
fullName: function(city, country) {
return this.firstName + " " + this.lastName + "," + city + "," + country;
}
}
var person1 = {
firstName:"John",
lastName: "Doe"
}
person.fullName.call(person1, "Oslo", "Norway");
三、bind
Function.prototype.bind()
bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()中的第一个参数的值,例如:f.bind(obj),实际上可以理解为obj.f(),这时f函数体内的this自然指向的是obj;
例:
var a = {
b: function() {
var func = function() {
console.log(this.c);
}
func();
},
c: 'hello'
}
a.b();
console.log(a.c);
那么答案是什么呢?undefined和hello,大家可以打印看下,这个就是因为fun()这个函数执行的时候他的函数上下文为window,而a.b()的这个函数的执行的时候函数上下文this为a对象是什么意思呢?
var a = {
b: function() {
console.log(this.c); //hello
var func = function() {
console.log(this.c); //undefined
}
func();
},
c: 'hello'
}
a.b();
看了上面的例子和console.log()的结果大家应该是知道了这个函数上下文大概是这么一回事了把?什么还是不会好把看这里,这里有关于this指向的问题的解析那么问题来了当我们希望func()他的输出的值就是为hrllo怎么办
方法一:改变this的值
var a = {
b: function() {
var _this = this; // 通过赋值的方式将this赋值给that
var func = function() {
console.log(_this.c);
}
func();
},
c: 'hello'
}
a.b(); // hello
console.log(a.c); // hello
方法二:绑定this的值发生改变
// 使用bind方法一
var a = {
b: function() {
var func = function() {
console.log(this.c);
}.bind(this);
func();
},
c: 'hello'
}
a.b(); // hello
console.log(a.c); // hello
// 使用bind方法二
var a = {
b: function() {
var func = function() {
console.log(this.c);
}
func.bind(this)();
},
c: 'hello'
}
a.b(); // hello
console.log(a.c); // hello
这里我们以a.b()的形式去执行a对象中的b这个函数,是经过对象a的所以当我们来执行a对象中b函数的时候找this就会先找到a对象所以在a对象中的b这个函数中的this为a对象,所以这个时候bind,绑定的this也就是为a对象了
c = 10;
var a = {
b: function () {
console.log(this);
var func = function () {
console.log(this.c);
}.bind(this);
func();
},
c: 'hello'
}
var d = a.b;
d();
这里我们以d()的形式去执行a对象中的b这个函数吗,因为d()的执行的时候由于没人应用this默认为window,所以在a对象中的b这个函数中的this为window,所以不这个时候bind,绑定的this也就是为window了
bind()的原生实现分步解析
1:通过call,吧arguments生成一个真正的数组
Array.prototype.slice.call(arguments)是用来将参数由类数组转换为真正的数组;
[].slice.call(arguments)是用来将参数由类数组转换为真正的数组;
解析:其实就是arguments他是一个类数组(对象)他没slice的方法 ,通过上面的Array.prototype.slice写法可以让他添加slice的方法,为什么可以呢?
slice()没参数默认从key0到最后一项,浅拷贝,不影响原数组
因为其实arguments也是一个对象,就相当与是在arguments中执行了slice()函数,那么不要参数的情况下就是根据key下标会返回一个完整的数组从而达到了生成了一个新的数组的效果,arguments对象中是有length属性和0.1.2.这样的属性的而slice就是会根据这些0.1.2的这些key(也叫属性值的东西)放回成为一个数组
Ⅲ、原生bing实现
Function.prototype.myBind = function () {
if (typeof this !== 'function') throw 'caller must be a function'
let self = this // 这里是关键 用来和new出来的比较原型来判断是否为new出来的 当前函数对象
let context = arguments[0]
let args = Array.prototype.slice.call(arguments, 1) // 旧:参数
let fn = function () {
let fnArgs = Array.prototype.slice.call(arguments) // 新:参数
// bind 函数的参数 + 延迟函数的参数
// 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
self.apply(this instanceof self ? this : context, args.concat(fnArgs))
}
fn.prototype = Object.create(self.prototype) // 维护原型
return fn
}
关键点
1.bing是影响不到new构造函数过程中this为新构造出来的那个对象的定义
当一个绑定函数是用来构建一个值的,原来提供的 this 就会被忽略。 ---MDN
代码:this instanceof self
this:对myBing出来的函数所用者的指向
self:使用myBing()的那个函数
instanceof:用来检查构造函数的prototype属性是否出现在某一个实例对象的原型链上
2.由于返回的是新定义出来的函数,所以原型要记得绑定
3.Object.create() 专门用来绑定__proto__的
分析
这里帮忙分析下子在用new流程的时候为什么要---(this instanceof self)
var YAxisPoint = Point.MyBind(null, 0/*x*/);
var axisPoint = new YAxisPoint(5);
我们以上面代码为例子,所以可以得出
1. self:Point 函数
我们使用关键词new的时候在执行YAxisPoint函数,那么YAxisPoint 函数中的this就是我们new出来的实例了
2..this = 新出来的实例对象
由于this等于新new出来的实例其实this instanceof self不应该为true的,因为新创建的函数和引用myBind的函数(self函数)完全无瓜葛
但是,fn.prototype = Object.create(self.prototype),导致fn构造函数new出来的实例指向的prototype和引用myBind的函数(self函数)的prototype是一样的,所以this instanceof self === true
四、 call、apply、bind三者的异同
共同点 :
-
都可以改变this指向;
-
三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window
不同点:
- call 和 apply 会调用函数, 并且改变函数内部this指向.
- call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递,且apply和call是一次性传入参数,而bind可以分为多次传入
- bind是返回绑定this之后的函数
应用场景
-
call 经常做继承
-
apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
-
bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向
以上内容搬运相关学习内容的优秀创作者文章,详解可见:
call内容: 什么是 call 函数? call 函数的用法? call 函数的实现原理(实例演示)_call函数-CSDN博客
apply内容:js apply()用法详解-CSDN博客
bind内容:js中bind()使用详情_js bind-CSDN博客