this和bind&&call&&apply
this 关键字
this是在函数运行时,函数内部自动生成的一个对象;
-
每一个函数内部都有一个关键字是
this
-
可以让我们直接使用的
-
重点: 函数内部的 this 只和函数的调用方式有关系,和函数的定义方式没有关系
-
函数内部的 this 指向谁,取决于函数的调用方式
-
全局定义的函数直接调用,
this => window
-
构造函数中的this,每个构造函数在new之后都会返回一个对象,这个对象就是this,也就是context上下文。
function fn() { console.log(this) } fn() // 此时 this 指向 window
-
对象内部的方法调用,
this => 调用者
var obj = { fn: function () { console.log(this) } } obj.fn() // 此时 this 指向 obj
-
定时器的处理函数,
this => window
setTimeout(function () { console.log(this) }, 0) // 此时定时器处理函数里面的 this 指向 window
-
事件处理函数,
this => 事件源
div.onclick = function () { console.log(this) } // 当你点击 div 的时候,this 指向 div
-
自调用函数,
this => window
(function () { console.log(this) })() // 此时 this 指向 window
问题: 箭头函数中的this是如何查找的了?
答案: 向外层作用域中, 一层层查找this, 直到有this的定义
const obj = {
a() {
setTimeout(function () {
setTimeout(function () {
console.log(this); // window
})
setTimeout(() => {
console.log(this); // window
})
})
setTimeout(() => {
setTimeout(function () {
console.log(this); // window
})
setTimeout(() => {
console.log(this); // obj
})
})
}
}
obj.a()
那继续更新this的指向问题
const o1 = {
text: 'o1',
fn: function () {
return this.text
}
}
const o2 = {
text: 'o2',
fn: function () {
return o1.fn()
}
}
const o3 = {
text: 'o3',
fn: function () {
var fn = o1.fn
return fn()
}
}
console.log(o1.fn()); // o1
console.log(o2.fn()); // o1
console.log(o3.fn()); // undefined
// 答案是 ol ol undefined 下面来分析一下代码:
// 第一个 console 最简单,输出 o1 不难理解。 难点在第二个和第三个 console 上,关键还是看调用 this 的那个函数;
// 第二个 console 中的 o2.fn 最终调用的还是 o1.fn() ,因此运行结果仍然是 o1;
// 第三个 console 中的 o3.fn 通过 var fu = o1.fn() 的赋值进行了“裸奔“调用,因此这里的 this 指向 window, 运行结果当然是 undefined
问: 如果需要让 console.log(o2.fn())语句, 输出 o2 该怎么做?
const o1 = {
text: 'o1',
fn: function () {
return this.text
}
}
// 修改 o2 让 console.log(o2.fn())语句, 输出o2
const o2 = {
text: 'o2',
fn: o1.fn
}
const o3 = {
text: 'o3',
fn: function () {
var fn = o1.fn
return fn()
}
}
console.log(o1.fn()); // o1
console.log(o2.fn()); // o2
console.log(o3.fn()); // undefined
// 问:如果我们需要让 console.log(o2.fn())语句, 输出 o2 该怎么做?
// 一般会想到用call,apply,bind,当然这里不用的话怎么处理
// 以上方法同样应用了那个重要的结论 this 指向最后调用它的对象。
// 在上面的代码中,如果提前进行了赋值操作,将函数 fu 挂载到 o2 对象上,和最终作为 o2 对象的方法被调用
call 和 apply 和 bind
- 刚才我们说过的都是函数的基本调用方式里面的 this 指向
- 我们还有三个可以忽略函数本身的 this 指向转而指向别的地方
- 这三个方法就是 call / apply / bind
- 是强行改变 this 指向的方法
说明call,apply是ES5中的语法,bind是ES6新引入的:
- 都是用来改变函数的this对象的指向
- 第一个参数都是this要指向的对象
- 都可以利用后续参数进行传参
call
-
call
方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向 -
语法:
函数名.call(要改变的 this 指向,要给函数传递的参数1,要给函数传递的参数2, ...)
var obj = { name: 'fqniu' }
function fn(a, b) {
console.log(this)
console.log(a)
console.log(b)
}
fn(1, 2)
fn.call(obj, 1, 2)
fn()
的时候,函数内部的 this 指向 windowfn.call(obj, 1, 2)
的时候,函数内部的 this 就指向了 obj 这个对象- 使用 call 方法的时候
- 会立即执行函数
- 第一个参数是你要改变的函数内部的 this 指向
- 第二个参数开始,依次是向函数传递参数
apply
-
apply
方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向 -
语法:
函数名.apply(要改变的 this 指向,[要给函数传递的参数1, 要给函数传递的参数2, ...])
var obj = { name: 'fqniu' }
function fn(a, b) {
console.log(this)
console.log(a)
console.log(b)
}
fn(1, 2)
fn.apply(obj, [1, 2])
fn()
的时候,函数内部的 this 指向 windowfn.apply(obj, [1, 2])
的时候,函数内部的 this 就指向了 obj 这个对象- 使用 apply 方法的时候
- 会立即执行函数
- 第一个参数是你要改变的函数内部的 this 指向
- 第二个参数是一个 数组,数组里面的每一项依次是向函数传递的参数
bind
-
bind
方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向 -
和 call / apply 有一些不一样,就是不会立即执行函数,而是返回一个已经改变了 this 指向的函数
-
语法:
var newFn = 函数名.bind(要改变的 this 指向); newFn(传递参数)
var obj = { name: 'fqniu' }
function fn(a, b) {
console.log(this)
console.log(a)
console.log(b)
}
fn(1, 2)
var newFn = fn.bind(obj)
newFn(1, 2)
- bind 调用的时候,不会执行 fn 这个函数,而是返回一个新的函数
- 这个新的函数就是一个改变了 this 指向以后的 fn 函数
fn(1, 2)
的时候 this 指向 windownewFn(1, 2)
的时候执行的是一个和 fn 一模一样的函数,只不过里面的 this 指向改成了 obj
加强理解这三者关系
const apple = {
name: "苹果",
color: "红色",
type: function () {
console.log(this.name + ', ' + this.color);
}
}
const orange = {
name: "橘子",
color: "yellow",
}
// apple.type(); // 苹果, 红色
// 使用call, apply以及bind方法实现, 并从中得到它们三者的区别:
apple.type.call(orange); // 橘子, yellow
apple.type.apply(orange); // 橘子, yellow
apple.type.bind(orange); // 没有输出内容
// bind 改为如下写法:
let abind = apple.type.bind(orange);
abind() // // 橘子, yellow
const apple = {
name: "苹果",
color: "红色",
type: function (agr0, arg1) {
console.log(this.name + ', ' + this.color + ','+ agr0 + ',' + arg1);
}
}
const orange = {
name: "橘子",
color: "yellow",
}
// apple.type(); // 苹果, 红色
// 使用call, apply以及bind方法实现, 并从中得到它们三者的区别:
apple.type.call(orange, 1,2); // 橘子, yellow,1,2
apple.type.apply(orange, [1,2]); // 橘子, yellow,1,2
apple.type.bind(orange, 1,2); // 没有输出内容
// bind 改为如下写法:
let abind = apple.type.bind(orange);
abind() // 橘子, yellow,undefined,undefined
手写apply和call和bind
// 手写apply
Function.prototype.myApply = function (context) {
var context = context || window
context.fn = this
var result
// 需要判断是否存储第二个参数
// 如果存在,就将第二个参数展开
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
/*
主要区别就是获取参数不同,因为apply的带二个参数为数组,数组中包含函数需要的各项参数值,其余内容实现myCall相同, 如下call
*/
//手写call
Function.prototype.myCall = function (context) {
var context = context || window //如果第一个参数为空则默认指向window对象
// 给 context 添加一个属性
// getValue.call(a, 'b', '24') => a.fn = getValue
context.fn = this
// 将 context 后面的参数取出来
var args = [...arguments].slice(1) //存放参数的数组
// getValue.call(a, 'b', '24') => a.fn('b', '24')
var result = context.fn(...args)
// 删除 fn 删除掉临时添加的方法,否则就无缘无故多了个fn
delete context.fn
return result
}
/*
因为call方法是每一个函数都拥有的,所以需要在Function.prototype上定义myCall,
传递的参数obj即为call方法的第一个参数,说明this的指向,如果没有该参数,则指向默认为window对象。
args为一个存放除第一个参数以外的其余参数的数组(arguments为函数中接收到的多有参数,
[...arguments]可以将arguments类数组转换为真正的数组,详细讲解可以查看ES6语法)。
*/
// 手写bind
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var _this = this
var args = [...arguments].slice(1)
// 返回一个函数
return function Fun() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof Fun) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
/*
return function是因为bind返回的是一个函数,并且这个函数不会执行,需要再次调用,
那么当调用的时候,依旧可以对这个函数进行传递参数,即为支持柯里化形式传参,
所以需要在返回的函数中声明一个空的数组接收调用bind函数返回的函数时传递的参数,
之后对两次的参数使用concat()方法进行连接,调用ES5中的apply方法。
*/
context.say = this这里没看懂,怎么改变this指向的?
举个例子
obj.call(obj1)
mycall 就是例子中的 call;
context 就是 obj1;
this 指向的是 obj;
意思就是:在obj1上模拟一个say方法和obj一样的东西 完了去执行 做到改变this;
// 所有函数对象都有两个方法:apply和call,这两个方法可以让我们构建一个参数数组传递给调用函数,也允许改变this值。
// 使sayName中的this指向b, 改变this的指向
var name = 'window';
var obj = {
name: 'obj',
sayName: function () {
console.log(this.name);
}
}
var b = {
name: 'abcd'
};
//改变this指向
var newobj = obj.sayName;
newobj(); // window 注意:this指向全局
obj.sayName(); // obj 注意:this 指向 obj
newobj.call(b); // abcd 注意:this指向 b 改变this的指向并且执行调用函数
// 这样就是改变了obj.sayName的this,打印出来的是b中的name
再补充理解 bind— call — apply 这三者的关系
// 用一句话总结:它们都是用来改变相关函数this指向的
// 但 call apply 是直接进行相关函数调用的, bind 不会执行相关函数, 而是返回一个新 函数,这个新的函数已经自动绑定了新的指向
// 开发者可以手动调用它 再说具体点,就是 call app 之间的区别主要体现在参数设定上;
const foo = {
name: 'fqniu',
fun: function () {
console.log(this.name)
}
}
const bar = {
name: 'niuniu'
}
foo.fun.call(bar) // niuniu
理解new 操作符到底具体做了什么?
// 一、理解 new 操作符到底具体做了什么呢?
/*
1、创建一个新的对象
2、将构造函数的this指向这个新的对象
3、为这个对象添加属性和方法
4、最终返回新的对象
*/
// 二、代码理解如下:
var obj = {}
obj.__proto__ = Foo.prototype
Foo.call(obj)
如果在构造函数中出现了显式 return 的情况,那么需要注意,其可以细分为两种场景
// 注意点
// 需要指出的是,如果在构造函数中出现了显式 return 的情况,那么需要注意,其可以细分为两种场景
// 场景1
function Foo1(){
this.name = "fqniu"
const o = {}
return o
}
const instance1 = new Foo1()
console.log(instance1.name); // undefined 这里的 instance1 返回的是空对象 o
// 场景1
function Foo2(){
this.name = "fqniu"
return 1
}
const instance2 = new Foo2()
console.log(instance2.name); // fqniu 这里的 instance2 返回的是目标对象实例 this
// 所以,如果构造函数中显式饭回一个值
// 1、如果返回的是一个对象(返回复杂类型),那么 this 就指向这个返回的对象
// 2、如果返回的不是一个对象(返回基本类型),那么 this 仍然指向实例
箭头函数中的 this
const foo1 ={
fun:function(){
setTimeout(function(){
console.log(this);
});
}
}
console.log(foo1.fun()); // window
const foo2 ={
fun:function(){
setTimeout(() => {
console.log(this);
});
}
}
console.log(foo2.fun()); // foo2 的 fun
显式绑定和隐式绑定谁的优先级更高
我们常常把通过 call 、apply 、bind、 new 对this进行绑定的情况称为显式绑定,而把根据调用关系确定this指向的情况称为隐式绑定;
那么显式绑定和隐式绑定谁的优先级更高呢? 关于这个问题的答案,那么会在接下来的例题中为大家揭晓
function foo(a) {
this.a = a
}
const obj1 = {}
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a); // 2
// 上述代码 通过bind将bar函数中的this绑定为obj1对象。
// 执行bar(2)后, obj1.a值为2, 即执行bar(2)后, obj1对象为 {a: 2}
// 当再使用bar作为构造函数时,例如执行以下代码,则会输出3
var bat = new bar(3)
console.log(bat.a); // 3
// bar 函数本身是通过 bind 法构造的函数,其内部已经将 this 绑定为 obj1,
// 当它再次作为构造函数通过 new 调用时,返回的实例就已经与 obj1 解绑了。
// 也就是说,new 绑定修改了 bind 绑定中this指向
// 因此 new 绑定的优先级比显式 bind 定的更高。
function foo(){
return a => {
console.log(this.a);
}
}
const obj1 = {
a: 1
}
const obj2 = {
a: 2
}
const bar = foo.call(obj1)
bar.call(obj2) // 打印的为 1
// 以上代码的输出结果为1, 由于foo中的 this 绑定到了obj1上,所以 bar(引用箭头函数)中的this 会绑定到 obj1 上,箭头函数的绑定无法被修改
// 如果将foo完全写成如下的箭头函数的形式,则会输入123
var a = 123
const foo = () => a => {
console.log(this.a);
}
const obj1 = {
a: 1
}
const obj2 = {
a: 2
}
const bar = foo.call(obj1)
bar.call(obj2) // 打印的为 123
// 如果将 a 的声明修改一下呢
const a = 123
const foo = () => a => {
console.log(this.a);
}
const obj1 = {
a: 1
}
const obj2 = {
a: 2
}
const bar = foo.call(obj1)
bar.call(obj2) // 打印的为 undefined
// 答案为 undefined, 原因是使用 const 声明的变量不会挂载到 window 全局对象上。
// 因此,this指向 window 时,自然也找不到 a变量了 关于 const let 等后续会进行介绍