关于this这个货,常常让我感到头疼,也很难说清这货到底是什么机制,今天就详细记录一下this,了解他就跟理解闭包差不多,不理解的时候我们会感到很难受总想着避开他,当我们真正理解之后,会有种茅塞顿开的感觉,但是也不要掉以轻心,说不定哪天又给来一脚~

先看一个例子,之前的博客中也提过到的this使用:

function fn(){
console.log(this.a)
} var a = 2; var o = {a:7}; // 使用之前讲到的apply
fn.apply(o); //
fn();//

那么this那么简单好用么,当前不是,this是比较复杂的机制,有很多规则,不小心的话很会难受。

一、抛开上面的例子,对于this我们平时会有一些误解:

1.指向自身:

function fn(){
console.log(this.a);
this.a ++;
} fn.a = 0; fn(1); console.log(fn.a);//

我们预期是输出1,因为fn被调用了1次,并且那么a++ 会导致 变成1,但是最终却是0,如果下面再来一句~

var a = 0;
fn(1); console.log(a);//

this.a 实际改变的是全局作用域a,所以例子中的 this 并没有指定为所包含的这个函数当中 = =。

如果非要调用自身,可以采用具名函数的方式(不要使用arguments.callee,在上一章提到了,被废弃的方法我们还是不要接触了):

function fn(){
fn.a ++;
} fn.a = 0; fn(); fn.a;//

或者,使用apply或者call

function fn(){
this.a ++;
} fn.a = 0; fn.call(fn); fn.a;//

说一下第一个例子为毛会是0,this 在当时指向的是全局作用域,而不是函数本身,当调用fn()方法时,this.a 会在全局作用域中声明一个 a 并执行 ++,就像下面这样

function fn(){
this.a ++;
} fn(); console.log(a); // NaN (因为a只是声明,当执行RHS的时候并没有a,那么undefined+1 会是啥? NaN)

所以:this 并不是指向自身。

2.作用域

function fn(){
var a = 0;
this.fn2();
} function fn2(){
console.log(this.a) } fn(); // undefined

首先this.fn2() 确实找到了fn2,在fn2中怎么会找到a=0呢,按照之前说的,它也只是在全局作用域中创建了一个a而已(实际上都没有创建,因为此处只有LHS 没有RHS,详细了解的话到之前的文章中看一下作用域,在此调用成功就当成是个意外吧 = =)。

那么var a 把它理解为私有的属性,而在fn2中想要用fn的私有属性怎么可能呢? 这段代码实际上想通过词法作用域的概念来用来fn中的a,但this并不会查到啥,除非这样(平常使用的比较多的):

function fn(){
var a = 0;
fn2(a);
} function fn2(a){
console.log(a)
} fn();

fn2中的a通过RHS 查询在上一层找到了 0。

so、this和词法作用的查找是冲突的,不要再想着这样用了,忘了它吧~~

那么下一句你可能在很多地方都看到过:

this实际上是在函数被调用时发生的绑定,它的指向取决与函数在哪里被调用。

二、调用位置:

如何寻找位置,先告诉你,上一层或者说最后一层~ 别急着想像,看代码:

function fn(){
fn2();
} function fn2(){
console.log('fn2');
} fn();// fn2

fn的调用位置在全局作用域下,fn2的调用位置在fn下(fn2的调用栈就是 fn - > fn2)。

所以,明白了调用位置了? 如果是多层,还有一种办法通过强大的浏览器开发者界面,如下图:(多加了一个fn3,这样可能更清晰一点)

三、绑定规则:

在调用栈中找了调用位置,接下来看看绑定规则:4种

优先级为:new > 显式 > 隐式 > 默认(为啥最后说)

1.默认规则

function fn(){
console.log(this.a);
} var a =2; fn(); //

这个例子上面基本上都用到了,它用到的时候默认规则,this指向的是全局对象(并不是一定指向全局对象的哦,后面会说到)

因为fn直接调用fn(),我们或许也可以这样理解 window.fn() ,this.a 指向window.a ,输出2~  是不是很好理解(有点像隐式绑定这样说~)

但是有一点是需要注意的是,严格模式下不适用

function fn(){
"use strict";
console.log(this.a);
} var a = 2; fn(); // TypeError: Cannot read property 'a' of undefined

this不会默认绑定到window上,除非~:

window.fn();//

真正意义上用到了隐式绑定~~(别急,马上就说隐式绑定)

还有很重要的一点,严格模式下默认绑定只会关注函数体内部,不会关注被谁调用,像下面这样,是可以使用的:

function fn(){
console.log(this.a);
} var a = 2; function fn2(){
"use strict";
fn();
} fn2();//

2.隐式绑定:

是否调用位置有上下文对象或者是上下文对象,或者说被某个对象包含或拥有:

function fn(){
console.log(this.a);
} var o = {
a:2,
fn: fn
}
// o.fn = fn;
o.fn();//

不管是先定义或者是后引用,在此隐式绑定的规则会把函数调用中的this 绑定到这个上下文对象。(回顾一下,在这里o的fn拥有所在作用域o的闭包或者说行使权、使用权,把它看成 o = {a:2,fn:function(){ console.log(this.a) }})

在声明一次:对象引用链只有上一层或则最后一层在调用位置起作用。

function fn(){
console.log(this.a) } var o = {
a:0,
fn:fn
} var o2 = {
a:2,
o:o,
fn:o.fn
} o2.o.fn(); // 0 (绑定的是o)
o.fn();//2 (这里o2的fn为函数本身,与o没有直接关系,所以this绑定的是o2这个对象,或者说叫隐式丢失)

tip:隐式丢失

// 跟上面的例子一个意思
function fn(){
console.log(this.a)
} var o = {
a:2,
fn : fn
} var x = o.fn; x();//undefined

o.fn 引用的是函数本身,并没有执行fn函数,所以x知识引用了fn函数,当执行x函数时,应用了到了上面说到的默认绑定,(在全局作用域声明了a,但是没有赋值),好吧,怕忘记了,如果像下面这样就更清晰了

// 在上面例子的基础上加2句
var a =7;
x();//

再来一个,参考书《你不知道的javascript》:

function fn(){
console.log(this.a)
} function fn2(f){ f();
} var o = {
a:2,
fn:fn
} fn2(o.fn); // undefined

fn2执行的f  是fn函数本身,跟o没有毛关系,所以最终也是使用了默认绑定。

还有一种就是window内置对象,跟上面结果一样,在此就不写例子了。

3.显示绑定

这个可能最好理解,就是指定this要绑定的上下文对象,主要用到的就是 apply、call、bind,关于这3个货,想看的可以看看之前的文章 JS 关于 bind ,call,apply 和arguments p8

这里主要说一个概念:如果你传入一个原始值比如:“”、1、true,当作this 的绑定对象,这个值会转为它的对象形式(new String()、new Number()、new Boolean()),称为装箱。

function fn(){
console.log(this.a);
} fn.call({a:2});// fn();// undefined

如上面看到的显示绑定也不会解决丢失绑定的问题。

但是我们可以通过硬绑定来解决这个问题:

function fn(){
console.log(this.a)
} var o = {
a:2
} function fn2(){
fn.call(o);
} fn2();//2

这样调用fn2的时候都会默认显示绑定。

它的典型行为是:创建一个包裹函数,负责接收参数并返回值。

看这个例子:

function fn(f){
console.log(this.a);
console.log(f);
} var o = {
a:2
} function fn2(){
fn.apply(o,arguments)
} fn2(6);
//
//

跟上一个例子差不多,这里利用了arguments内置对象来传递参数。

还有一种是创建辅助函数:

function fn(f){
console.log(this.a);
console.log(f);
} var o = { a:3} function fn2(){ return function (){
fn.apply(o,arguments);
}
} var fn3 = fn2(); fn3(8);
//
//

对比上一个例子,一个是立即执行,另一个是返回绑定后的函数本身再进行调用。或者使用bind也行

function fn(f){
console.log(this.a);
console.log(f);
} var o = { a:3} function fn2(){
return fn.bind(o);
} var fn3 = fn2(); fn3(5);
//
//

另外关于API 调用上下文在实际应用中有需要函数上就是通过call 与apply 实现了显示绑定,比如[].forEach();

4.new 绑定

首先要说的是,new不会实例化某个类(和我之前的说法有些冲突,但是实例我们会比较好理解),因为他们是被new操作符调用的普通函数。

类似这种 new String() ,正确的说法叫做“函数调用”,因为实际上js中并不存在构造函数之说,只存在函数调用。

上面是官方一点的语言,其实我们只需要知道这几点暂时:

new 会创建一个全新的对象,并且这个对象会绑定到函数调用的this,如果这个函数没有return 那么就返回这个函数本身的新对象~

function fn(){this.a = 3;
} var fn2 = new fn(); fn2.a;//

如上,new出来的新对象fn2 绑定到了fn的this上,是一个全新的对象(这跟之前的文自定义创建对象中说到的一样,如果为私有变量,则不会拥有它,或者它看不到,但是可以使用它比如下面这种:)

function fn(){
this.a = 3;
var b = 4; this.fn2 = function(){
console.log(b) ;
}
} var fn3 = fn(); console.log(fn3);// {a: 3, fn2: ƒ}
fn3.fn2();//

这里除了this,还有闭包的相关概念,在此就不多说了。大家只要知道this绑定到了新对象上(全新的)。

好了,4种绑定说完了,接下来说下优先级:

function fn(){
console.log(this.a)
} var o = {a:2,fn:fn}; var o2 = {a:5,fn:fn} o.fn();//
o.fn.call(o1);//

那么看显示绑定应该是优先于隐式绑定的,通过最后一行可以看出来(这里我感觉有点不好理解,或者我们可以这样理解,o.fn() 是显示绑定this所以从调用位置来看,上下文o的a为2所以this.a输出的为2;o.fn 为函数本身,所以在对函数本身进行显示绑定,所以this绑定到了o2上面)

并且显示绑定和隐式绑定都会丢失this(上面提到的)。

看下一个new 绑定和隐式绑定:

function fn(f){
this.a = f
} var o = {
fn:fn
} var o2 ={} o.fn(0);
console.log(o.a);// o.fn.call(o2,3);
console.log(o2.a);// var fn2 = new o.fn(5);
console.log(o.a);//
console.log(o2.a);//
console.log(fn2.a);//

o.fn(0) 为隐式绑定,this绑定到o 上,o.a 为0 这点不用多说。

o.fn.call(o2,3); fn函数本身中this被显示绑定带o2上,o2对象获得a并为3;

最后fn2为一个new出来的新对象,this绑定到这个新对象上(上下文),它的a为5。(因为函数就是对象)

不是很明显,下面来一个(比较new 和显示绑定):

function fn(f){
this.a = f
} var o = {} var x = fn.bind(o) x(1); o.a;// var y = new x(3) y.a;//

个人感觉在判断优先级时,不能只是记住哪种规则优先级高,而是需要仔细分析,还是理解最重要

比如:

o.fn.call()    ,首先o是一个对象,o对象中包涵的函数fn 在这里并没有调用它,按照之前所说,它只是表示函数本身,那么在调用call的显示绑定并执行了该函数,那么this肯定会绑定到显示绑定的第一个参数上,所以是显示优先

var fn2 = new o.fn();  记住最关键的那句,new会创建一个新对象并绑定到this上,这样就不会迷糊了,o.fn 是函数本身,并且创建一个新对象,那么fn2 是一个全新的对象,所以这里就是new 优先

(this与call无法同时使用但是可以用bind)

var fn2 = fn.bind(o);

var bar = new fn2();

纵使怎么变,fn2是显示绑定没错,如果像上面例子 fn是这样的  function(f){ this.a = f },那么fn2.a 肯定是o的a,

但是bar 声明使用new 绑定,那么会创建一个新对象~新对象~新对象,所以,fn2里如果加上一个参数比如 new fn2(3) ,那么新声明的bar.a 肯定就是3~

不要被所谓的优先级弄晕了,记住这几条重要的规则,管它怎么变,相信都能找到最终的那个this。

这里有一个概念:第一个参数用于绑定this,剩余的参数用于传递给下层函数的这种行为被称为“部分应用”、或者“柯里化”。(bind、apply、call)

那么这里对于判断this绑定的是什么就很好查了:

1.先看new

2.再看call、apply、bind

3.看隐式调用 o.fn()

4.啥都没,那就是默认绑定(官方的语言是,如果在严格模式下,就绑定到undefined,否则绑定到全局对象

但是

如果把null 或则undefined作为this的绑定对象传给apply的话,嘿嘿~

调用的时候会被忽略~

function fn(){
console.log(this.a)
} fn.apply(null);// undefined

其实就等于直接调用 fn()罢了。

当然我们可以另类的用这种机制:

function fn(){
for(let i = 0 ;i<arguments.length;i++){
console.log(arguments[i]);
}
} fn.apply(null,[1,2]);
//
//

是的,可以用来做展开数组(但是这里看着没必要)(在es6里可以通过...来解决展开数组的问题,像这样fn(...[1,2]))

如果使用null 作为柯里化的这种操作很危险,为啥,看下面:

function fn(a){
this.a = a;
} fn.call(null,2); console.log(a);//

默认绑定使全局作用域的a 赋值了2(成功进行了RHS 查询)。

如果非要使用的话:可以使用空对象,如下

function fn(a){
this.a = a;
console.log(this.a)
} var n = Object.create(null); fn.call(n,2); console.log(a);// a is not defined // 或者使用严格模式也未尝不可,但是代码中混用严格模式与懒惰模式真的会很不好维护~~
function fn(a){
'use strict';
this.a = a;
} fn.call(null,2) ; // Uncaught TypeError: Cannot set property 'a' of null

拓展一下Object.create()

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 (请打开浏览器控制台以查看运行结果。

另外:间接引用也会使用默认绑定

function fn(){
console.log(this.a)
} var o = {a:2,fn:fn} var o2 = {} var x = o.fn; x();// undefined (o2.fn = o.fn)();//undefined

这里其实很简单,按照之前说的,o.fn 是函数本身,并没有进行绑定~

还有一种绑定叫做“软绑定”,可以给默认绑定指定一个全局对象和undefined的值,同事保留隐式绑定或者显示绑定this的能力~

说实话,平时我们不太会用到,了解一下就好,软绑定在内置方法中并不存在,如果想要使用,必须自己实现,下面给出官方的例子:

if(!Function.prototype.softBind){
Function.prototype.softBind = function (obj){
var fn=this;
var curried = [].slice.call(arguments,1);
var bound = function(){
return fn.apply((!this||this===(window||global))?obj:this,curried.concat.apply(curried,arguments));
};
bound.prototype = Object.create(fn.prototype);
return bound;
}; }

检查调用对象this绑定的对象到底是谁?如果是window或者undefined、null之类的那么this绑定就交给参数对象obj去处理,相反

如果不是,则交给this本身去处理,方法的最后一步把fn 也就是this的原型保留并传给新声明的还说bound并返回~

JS 关于this p9的更多相关文章

  1. Vue.js学习笔记(1)

    数据的双向绑定(ES6写法) 效果: 没有改变 input 框里面的值时

  2. js语言精粹读书笔记一

    一.语法 1.

  3. 用node-webkit(NW.js)创建桌面程序

    以往写windows桌面程序需要用MFC.C#之类的技术,那么如果你只会web开发技术呢?或者说你有一个网站,但是你想把你的网站打包成一个桌面应用程序,该如何做呢? 答案就是用node-webkit这 ...

  4. 二、JavaScript语言--JS基础--JavaScript进阶篇--浏览器对象

    1.window对象 window对象是BOM的核心,window对象指当前的浏览器窗口. window对象方法:

  5. JS原生方法实现瀑布流布局

    html部分(图片都是本地,自己需要改动图片) p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 30.0px Consolas; color: #2b7ec ...

  6. 手机浏览器JS识别

    识别方法:采用Fiddler 抓包工具 侦测手机http链接,抓取http头 查看 工具:Fiddler 1:Fiddler配置 允许远程设备连接,配置端口为默认8888(确保8888端口没有被其他进 ...

  7. js之引用类型

    一.摘要: <javascript高级程序设计第三版>一书中单独有一章对js的引用类型(Object.Array.RegExp.Function:基本包装类型:Boolean.Number ...

  8. PhotoSwipe.js 相册展示插件学习

    PhotoSwipe.js官网:http://photoswipe.com/,在这个网站上可以下载到PhotoSwipe的文件以及相关的例子. 这个组件主要是用来展示图片.相册用的,还是很实用的. 一 ...

  9. js根据条件json生成随机json:randomjson

    前端开发中,在做前后端分离的时候,经常需要手写json数据,有3个问题特别揪心: 1,数据是写死的,不能按一定的条件随机生成长度不一,内容不一的数据 2,写数组的时候,如果有很多条,需要一条一条地写, ...

随机推荐

  1. 浅谈getResource方法

    项目经常会读取一些配置文件, 因此getResource方法便能够起到重要作用 使用时主要是两种方法, 一个是字节码文件Class类, 另一个是ClassLoader类加载器 使用Class类时有两种 ...

  2. 【hyperscan】hyperscan开源了!

    hyperscan开源了! 官网:https://01.org/zh/hyperscan 1. 新闻背景 当地时间10月19日,intel将它的高速正则表达式匹配引擎hyperscan开源了,版本4. ...

  3. vue项目axios请求接口,后端代理请求接口404,问题出现在哪?

    在vue项目中,列表数据需要用到qq音乐接口中的数据,但是直接请求不行,有host及referer限制,需要采用后端代理的方式.借助axios及node的express,在dev-server.js中 ...

  4. 装饰器中的@functools.wraps的作用

    def login_required(view_func): @functools.wraps(view_func) def wrapper(*args, **kwargs): ...... retu ...

  5. Linux下删除某些非法字符文件名的文件

    1.首先利用 ls -i 查找ID 2.find ./ -inum 20718697 -exec rm '{}' \;

  6. Git for Windows之日志查看与版本切换

    1.查看本地版本库的修改日志 (1).通过log指令查看完整日志 (2).通过 log --pretty=oneline查看简易版日志 2.版本切换 (1).切换到本地版本库最新的版本,通过reset ...

  7. Shell 流程控制 if while for

    if else if if 语句语法格式: if condition then command1 command2 ... commandN fi 写成一行(适用于终端命令提示符): if [ $(p ...

  8. 全网最详细的再次或多次格式化导致namenode的ClusterID和datanode的ClusterID之间不一致的问题解决办法(图文详解)

    不多说,直接上干货! java.io.IOException: Incompatible clusterIDs in /opt/modules/hadoop-2.6.0/data/tmp/dfs/da ...

  9. 浅尝Vue.js组件(一)

    本篇目录: 组件名 组件注册 全局注册 基础组件的自动化全局注册 局部注册 在模块系统中局部注册 Prop 单向数据流 Prop验证 类型检查 非Prop特性 替换/合并已有的特性 禁用特性继承 自定 ...

  10. Solidity字符串类型

    字符串可以通过""或者''来表示字符串的值,Solidity中的string字符串不像C语言一样以\0结束,比如abcd这个字符串的长度就为我们所看见的字母的个数,它的长度为4. ...