在阐述它们如何使用以前,咱们有必要整理清楚this
的用法,简单的说this
是JavaScript
语言的一个关键字,它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。数组
那么问题又来了,this
的值是什么呢?bash
由于this
是在函数运行时,函数内部自动生成的一个对象,那么接下来咱们经过函数来对this
进行分析。 首先JavaScript
中的函数能够分为两类:app
接下来分别分析this
在这些函数中到底是什么?函数
this
1.纯粹的函数调用学习
function test(name) {
console.log(name)
console.log(this)
}
test('Jerry') //调用函数
复制代码
以上函数调用的方式是很是常见的,然而这只是一种简写的形式,完整的写法应该以下:ui
function test(name) {
console.log(name)
console.log(this)
}
test.call(undefined, 'Tom')
复制代码
这里面便出现了咱们将要学习的call
,先不讨论它的做用,咱们继续讨论this
的用处,call
方法接受的第一个参数就是this
,可是咱们这里是undefined
,按照规定,若是你传的context
是 null
或者 undefined
,那么 window
对象就是默认的context
(严格模式下默认context
是 undefined
)。this
2.对象中函数的调用spa
const obj = {
name: 'Jerry',
greet: function() {
console.log(this.name)
}
}
obj.greet() //第一种调用方法
obj.greet.call(obj) //第二种调用方法
复制代码
从上面的例子中,咱们发现此次call
方法的第一个参数为obj
,此时说明函数greet
内部的this
指向了obj
对象,这显而易见便知call
方法的做用是改变this
的指向,又由于上面两种调用方式结果同样可知函数的this
指向能够理解为谁调用便指向谁。prototype
3.构造函数中的this
code
每一个构造函数在new
以后都会返回一个对象,这个对象就是this
,也就是context
上下文。
this
在使用箭头函数的时候,箭头函数会默认绑定外层的this
值,因此在箭头函数中this
的值和外层的this
是同样的。由于箭头函数没有this
,因此须要经过查找做用域链来肯定this
的值。
这就意味着若是箭头函数被非箭头函数包含, this
绑定的就是最近一层非箭头函数的 this
。
注意:多层对象件套里面的this
是和最外层保持一致的。
由于今天的重点是讲解call
,apply
,bind
的用法及实现,然而箭头函数是没有这些方法的,因此箭头函数的使用仅限于此。
首先说明call
,apply
是ES5中的语法,bind
是ES6新引入的,它们三者的类似之处为:
this
对象的指向this
要指向的对象不一样之处使用一个例子进行说明:
const personOne = {
name: "张三",
age: 12,
say: function () {
console.log(this.name + ',' + this.age);
}
}
const personTwo = {
name: "李四",
age: 24
}
personOne.say(); //张三,12
复制代码
对于以上的结果,咱们应该都很是清楚,那么问题来了,若是咱们想要知道personTwo
对象的信息如何实现呢?
分别使用call
,apply
以及bind
方法实现,并从中获得它们三者的区别:
personOne.say.call(personTwo); //李四,24
personOne.say.apply(personTwo); //李四,24
personOne.say.bind(personTwo); //没有输出任何东西
复制代码
修改以上代码对比可知:call
和apply
都是对函数的直接调用,而bind
方法返回的仍然是一个函数,所以咱们须要执行它才会有结果。
personOne.say.call(personTwo); //李四,24
personOne.say.apply(personTwo); //李四,24
personOne.say.bind(personTwo)(); //李四,24
复制代码
接着继续讨论其他参数
const personOne = {
name: "张三",
age: 12,
say: function (gender, phone) {
console.log(this.name + ',' + this.age + ',' + gender + ',' + phone);
}
}
const personTwo = {
name: "李四",
age: 24
}
personOne.say("女", "123");
复制代码
这个例子的区别于上面的即为say
函数须要传递参数,咱们分别使用这三种方法实现传递参数:
personOne.say.call(personTwo, "女", "123"); //李四,24,女,123
personOne.say.apply(personTwo, ["女", "123"]); //李四,24,女,123
personOne.say.bind(personTwo, "女", "123")(); //李四,24,女,123
复制代码
显而易见的区别call
和bind
除了第一个参数外,以后的参数均为一一传递,而apply
除了第一个参数外,只有一个参数即为一个数组,数组中的每一项为函数须要的参数。
说明它们的用法以及区别以后,咱们就要本身尝试着剖析它的原理,本身书写这三个方法啦~~~~~
call
实现在知道了它的使用即原理以后,想必直接看实现方法应该也能够理解的,那么先上代码:
Function.prototype.myCall = function (obj) {
const object = obj || window; //若是第一个参数为空则默认指向window对象
let args = [...arguments].slice(1); //存放参数的数组
object.func = this;
const result = object.func (...args);
delete object.func; //记住最后要删除掉临时添加的方法,不然obj就平白无故多了个fn
return result;
}
复制代码
代码很是简短,一步步进行说明解释:
由于call
方法是每个函数都拥有的,因此咱们须要在Function.prototype
上定义myCall
,传递的参数obj
即为call
方法的第一个参数,说明this
的指向,若是没有该参数,则指向默认为window
对象。
args
为一个存放除第一个参数之外的其他参数的数组(arguments
为函数中接收到的多有参数,[...arguments]
能够将arguments
类数组转换为真正的数组,详细讲解能够查看ES6语法)。
解释object.func=this
以前,咱们先使用示例使用一下本身定义的myCall
函数:
const personOne = {
name: "张三",
age: 12,
say: function (gender, phone) {
console.log(this.name + ',' + this.age + ',' + gender + ',' + phone);
}
}
const personTwo = {
name: "李四",
age: 24
}
Function.prototype.myCall = function (obj) {
const object = obj || window; //若是第一个参数为空则默认指向window对象
let args = [...arguments].slice(1); //存放参数的数组
object.func = this;
const result = object.func (...args);
delete object.func; //记住最后要删除掉临时添加的方法,不然object就平白无故多了个func
return result;
}
personOne.say.myCall(personTwo,"女",18333669807); //李四,24,女,18333669807
复制代码
根据示例,咱们进行解释,myCall
里面的this
指的是personOne.say
这个方法(由于myCall
是一个方法,上面所说的,谁调用它,它的this
便指向谁),object.func=this
至关于给object
这个对象克隆了一个personOne.say
方法,让object
在调用这个方法,至关于object.personOne.say
,达到了call
的效果。(记住最后要删除掉临时添加的方法,不然object
就平白无故多了个func
)
object.func (...args)
里面的参数即为传入的其他参数。
apply
实现经过上面的分析,想必你们应该已经基本明白了它是如何实现的了,那么接下来实现apply
就很是简单了,由于二者的区别主要就是参数的传递方式不一样,和上面同样,先直接看一下代码:
Function.prototype.myApply = function (obj) {
const object = obj || window; //若是第一个参数为空则默认指向window对象
if (arguments.length > 1) {
var args = arguments[1]; //存放参数的数组
} else {
var args = []; //存放参数的数组
}
object.func = this;
const result = object.func(...args);
delete object.func; //记住最后要删除掉临时添加的方法,不然obj就平白无故多了个fn
return result;
}
personOne.say.myApply(personTwo, ["女", 24]);
复制代码
主要区别就是获取参数不一样,由于apply
的带二个参数为数组,数组中包含函数须要的各项参数值,其他内容实现myCall
相同,此处就不在作解释。
bind
实现话很少说,依旧是先上代码
Function.prototype.myBind = function (obj) {
const object = obj || window; //若是第一个参数为空则默认指向window对象
let self = this;
let args = [...arguments].slice(1); //存放参数的数组
return function () {
let newArgs = [...arguments]
return self.apply(object, args.concat(newArgs))
}
}
personOne.say.myBind(personTwo, "女", 24)();
复制代码
前面的知识不重复说,return function
是由于bind
返回的是一个函数,而且这个函数不会执行,须要咱们再次调用,那么当咱们调用的时候,咱们依旧能够对这个函数进行传递参数,即为支持柯里化形式传参,因此须要在返回的函数中声明一个空的数组接收调用bind
函数返回的函数时传递的参数,以后对两次的参数使用concat()
方法进行链接,调用ES5中的apply
方法。