this
就是js
里的关键字,有特殊意义,代表函数执行主体。
一、定义
- 函数执行的主体(不是上下文):意思是谁把函数执行的,那么执行主体就是谁
二、使用情况
- 1、全局作用域里的
this
是window
,全局作用域下相当于是window.fn()
执行只是把window.
省略了(严格模式下是undefined
)。
console.log(this === window) // true
window.a = 13;
console.log(this.a) // 13
复制代码
- 2、函数里的
this
,看执行主体前有没有点
,如果有点
,那点
前面是谁,函数里的this
就是谁,如果没有点
,那函数里的this
就是window
,严格模式下是undefined
。
function fn(){
console.log(this)
}
fn();//window
let obj = {
fn:function(){
console.log(this)
}
}
obj.fn();//obj
var f = obj.fn;
f();//window
复制代码
- 3、自执行函数里的
this
是window
或undefined
(严格模式下)
(function(){
console.log(this);//==>window
})();
~function(){}();//==>window
+function(){}();//==>window
-function(){}();//==>window
!function(){}();//==>window
复制代码
- 4、回调函数里的
this
一般情况下是window
let ary = [1,2,3];
ary.forEach(function(item,index){
console.log(this)
})
//================================//
setTimeout(function(num){
console.log(num)
console.log(this)
},1000,1)
//================================//
function fn(m){
m()
}
fn(function(){
console.log(this)
})
复制代码
- 5、箭头函数没有
this
:
但是要是在箭头函数里使用
this
,他就会往上一级作用域查找,如果上一级作用域也没有,那就继续往上找,直到找到全局的window
为止
let obj = {
num: 2,
fn: function () {
// this-->obj
let m = () => {
// this-->obj
console.log(this.num) // obj(向上一级作用域查找)
}
m()
}
}
obj.fn()
复制代码
- 6、构造函数里的
this
是当前实例 - 7、实例原型上的公有方法里的
this
一般是当前实例(下面改变this
的方法中有体现) - 8、给元素绑定事件行为,那事件里的
this
就是当前被绑定的元素本身
btn.onclick = function(){
console.log(this) // btn
}
复制代码
三、面向对象中有关私有/公有方法中的THIS问题
总结下来
this
在面向对象中,主要还是看是谁执行的,也就是执行函数点前面是谁
- 1、方法执行,看前面是否有点,点前面是谁
THIS
就是谁- 2、把方法总的
THIS
进行替换- 3、再基于原型链查找的方法确定结果即可
四、改变this
指向:call
/apply
/bind
每一个函数(普通函数/构造函数/内置类)都是
Function
这个内置类的实例,所以:函数.__proto__===Function.prototype
,函数
可以直接调取Function
原型上的方法call / apply / bind
- 原型上提供的三个公有属性方法
- 每一个函数都可以调用这个方法执行
- 这些方法都是用来改变函数中的
THIS
指向的
1、call
语法:
- 函数.call(context,params1,params2….)
定义:
- 函数基于原型链找到
Function.prototype.cal
l这个方法,并且把它执行,在call
方法执行的时候改变里面的this关键字
作用:
- 让当前函数执行
- 把函数中的
THIS
指向改为第一个传递给CALL
的实参 - 把传递给
CALL
其余的实参,当做参数信息传递给当前函数
注意:
如果执行
CALL
一个实参都没有传递,非严格模式下是让函数中的THIS
指向WINDOW
,严格模式下指向的是UNDEFINED
fn.call(null)
;- //=>
this:window
- 严格下是
null
(第一个参数传递的是null
/undefined
/不传
,非严格模式下this
指向window
,严格模式下传递的是谁this
就是谁,不传this
是undefined
)
- //=>
使用方法:
function fn(){}
fn.call(); //=>fn函数基于原型链找到Function.prototype上的call方法,并且让其执行(执行的是call方法:方法中的this是fn)
fn.call.call(); //=>fn.call就是Function.prototype上的call方法,也是一个函数,只要是函数就能用原型上的方法,所以可以继续调用call来执行
Function.prototype.call = function $1(){
//...
}
fn.call => $1
fn.call() => $1() this:fn
fn.call.call() => $1.call() => 继续让call执行,this:$1
实例.方法():都是找到原型上的内置方法,让内置方法先执行(只不过执行的时候做了一些事情会对实例产生改变,而这也是这些内置方法的作用),内置方法中的THIS一般都是当前操作的实例
复制代码
call简单实现原理
```javascript
//=>我们的需求是想让FN执行的时候,方法中的THIS指向OBJ
obj.fn(); //=>Uncaught TypeError: obj.fn is not a function
//因为此时obj中并没有fn这个属性
-------解决办法---------
obj.fn = fn;
obj.fn(); //=>this:obj //=>'OBJ'
delete obj.fn;//=>对象中原本没有,所以使用后要删掉
复制代码
<a name="b2c8c983"></a>
### 自己基于原生JS简单的实现内置的call方法
- 详细梳理:
~ function () { /*
* myCall:改变函数中的THIS指向
* @params
* context 可以不传递,传递必须是引用类型值(因为后面要给它加$fn的属性)
*/
function myCall(context) {
//this:sum 也就是当前要操作的这个函数实例
context = context || window;
let args = [], //=>除第一个参数外剩余传递的信息值
result;
for (let i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
context.$fn = this;
result = context.$fn(...args); //=>args=[10,20] ...是ES6中的展开运算符,把数组中的每一项分别的展开传递给函数 //=>context.$fn(10,20)
delete context.$fn;
return result;
}
/* 扩展到内置类的原型上 */
Function.prototype.myCall = myCall;
}();
复制代码
- 可简写为
function myCall(context,…arg){ //context—>obj this—>fn context = context || window; // 如果你不传参、或者传null、传undefined,那context的值都是window let res = null; // 创建一个变量,准备接收函数执行结果 context.fn = this; // 把fn增加到obj里 res = context.fn(…arg); // 让fn指向 delete context.fn; // 把fn从obj里删除 return res; // 把this的返回值return出去 } Function.prototype.myCall = myCall; console.log(fn.myCall(obj,1,2)) // ‘ss’ 复制代码
- 以题为例分析
![](https://cdn.nlark.com/yuque/0/2020/webp/1307905/1598074406026-d9baa8f6-3819-41fc-bcb4-a5c4bd67e153.webp#alt=image)
> 最终我们可以得出:
> - 一个`CALL`是让左边函数执行(`this`是传递的参数)
> - 多个`CALL`是让最后传参的函数执行(`this`是`window`/`undefined`)
<a name="b913c65b"></a>
## 2、apply
> 和`call`方法一样,都是把函数执行,并且改变里面的`this`关键字的,唯一的区别就是传递给函数参数的方式不同
> - `call`是一个个传参
> - `apply`是按照数组传参
let obj={name:’OBJ’}; let fn=function(n,m){ console.log(this.name); } //=>让fn方法执行,让方法中的this变为obj,并且传递10/20 fn.call(obj,10,20); fn.apply(obj,[10,20]); 复制代码
<a name="5425fee1"></a>
## 3、bind
> 和`call`/`apply`一样,也是用来改变函数中的`this`关键字的,只不过基于`bind`改变`this`,当前方法并没有被执行,类似于预先改变`this`
<a name="5da210a2-1"></a>
### 语法:
- 函数.bind(context,params1,....)
<a name="9cdb3b71"></a>
### 区别:
- `bind`是预处理`this`,他并不会让函数执行
- `bind`方法的返回值是一个改变`this`之后的新函数
<a name="8076e5d8-1"></a>
### 作用:
- 把函数中的`THIS`指向通过预处理的方式改为第一个传递给`BIND`的实参
- 一般使用在绑定点击事件,不让函数立即执行时
let obj={name:’OBJ’}; function fn(){ console.log(this.name); } document.body.onclick=fn; //=>当事件触发,fn中的this:BODY //=>点击BODY,让FN中的THIS指向OBJ //document.body.onclick=fn.call(obj); //=>基于call/apply这样处理,不是把fn绑定给事件,而是把fn执行后的结果绑定给事件 document.body.onclick=function(){ //this:BODY fn.call(obj); } document.body.onclick=fn.bind(obj); 复制代码 ```
注意:
- 在
IE6~8
中不支持bind
方法 - 预先做啥事情的思想被称为“柯理化函数”
优点:
bind
的好处是:通过bind
方法只是预先把fn
中的this
修改为obj
,此时fn
并没有执行呢,当点击事件触发才会执行fn
(call
/apply
都是改变this
的同时立即把方法执行)