关于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. <转>PHP中正则表达式函数

    PHP中的正则表达式函数 在PHP中有两套正则表达式函数库.一套是由PCRE(Perl Compatible Regular Expression)库提供的,基于传统型NFA.PCRE库使用和Perl ...

  2. 设计模式《JAVA与模式》之访问者模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述访问者(Visitor)模式的: 访问者模式是对象的行为模式.访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要 ...

  3. zabbix 监控安装

    注意:此篇是在安装好lnmp环境后才能部署的操作,所以,做之前准备好lnmp环境,或者可以参考我做的lnmp环境,之后接着此篇开始安装 监控系统Zabbix-3.2.1的安装 zabbix-serve ...

  4. vue+iview实现一行平均五列布局

    iview 的栅格布局是以 html代码部分: <Row :gutter="20"> <Col style="float: left;width: 20 ...

  5. python3模块: requests

    Python标准库中提供了:urllib等模块以供Http请求,但是,它的 API 太渣了.它是为另一个时代.另一个互联网所创建的.它需要巨量的工作,甚至包括各种方法覆盖,来完成最简单的任务. 发送G ...

  6. Apache JMeter的基本使用

    安装 安装地址:http://jmeter.apache.org/download_jmeter.cgi 解压后运行jmeter.bat的批处理文件就可以了 JMeter测试脚本编写: 1,创建线程组 ...

  7. 课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 3、Python Basics with numpy (optional)

    Python Basics with numpy (optional)Welcome to your first (Optional) programming exercise of the deep ...

  8. C++ STL基本容器的使用(vector、list、deque、map、stack、queue)

    1.关联容器和顺序容器 C++中有两种类型的容器:顺序容器和关联容器,顺序容器主要有:vector.list.deque等.关联容器主要有map和set.如下图: 1.vector基本使用 #incl ...

  9. 全网最详细的Sublime Text 3的激活(图文详解)

    不多说,直接上干货! 前期博客 全网最详细的Windows里下载与安装Sublime Text *(图文详解) ZYNGA INC. User License EA7E- 927BA117 84C93 ...

  10. CSS3无前缀脚本prefixfree.js与Animatable使用介绍

    要求 必备知识 本文要求基本了解 JAVASCRIPT 和 和 CSS3 基本知识. 运行环境 桌面端:IE9 +,Opera 10+,火狐3.5 +,Safari 4+和Chrome浏览器;移动端: ...