this既不指向函数自身,也不指向函数的词法作用域!它指向谁完全取决于它在哪里被调用,被谁调用!

绑定规则

总体来说,this的绑定规则有:

  1. 默认绑定(严格模式/非严格模式)
  2. 隐式绑定
  3. 显式绑定
  4. new存在时绑定
  5. 箭头函数绑定

1.默认绑定:

默认绑定就是没有应用其他绑定规则时的绑定方式。

在非严格模式下,直接调用函数默认this指向全局对象,即window。这里要注意!即使是在某个函数中,如果自执行某个函数或者是使用setTimeout内部执行某个函数,它的this都指向全局变量!

        var value = 1;
var obj = {
value: 2
}
function funA() {
setTimeout(function () {
console.log(this.value)
}, 100)
} funA.call(obj) // 1,这里setTimeou相当于window调用这个函数,即默认调用。

如果想输出obj内部定义的value,可以在setTimeout外层设变量var that = this; 然后console.log(that.value)即可。将this设定指向本函数的this。

在严格模式下,并不能将全局对象用于默认绑定,this会绑定到undefined

        function foo() {
'use strict' // 严格模式下会输出undefined,非严格模式输出2 console.log(this.a);
}
var a = 2;
foo()

但是在严格模式下,调用函数不影响默认绑定!

        function foo() {
console.log(this.a);
}
var a = 2;
(function () {
'use strict' foo() // 在严格模式下调用函数并不影响默认绑定!所以输出2
})()

2.隐式绑定:

就是前面有对象引用时,this会指向这个对象,如果有多层,则只有上一层起作用!并不会一直向上寻找!!!切记!!!

        var obj = {
a: 1,
b: 2,
getA: function () {
console.log(this.a)
},
getB: {
b: 4,
subGetA: function () {
console.log(this.a)
},
subGetB: function () {
console.log(this.b)
}
}
} obj.getA() //
obj.getB.subGetA() // undefined,这里因为getB中并没有a,因此输出undefined
obj.getB.subGetB() //

有时候会有隐式丢失的情况出现,比如:

// 代码同上,加如下语句:

var a = "global"
var other = obj.getA // 将obj.getA赋值给other,other执行的时候其实是相当于 other(){ console.log(this.a) } other() // "global"

此时,other函数实际上是var other = function(){ consle.log(this.a) },相当于默认绑定this,this指向window!(自我感觉这时的other其实与obj和getA摆脱了关系,刚才那一步也就是进行了赋值操作而已~)

间接引用时(最容易发生在赋值操作!!),调用函数会应用默认绑定规则:

    function foo() {
console.log(this.a)
}; var a = 1; var obj = {
a: 3,
foo: foo
}; var fun = {
a: 2
}; (fun.foo = obj.foo)() // // 如果将上面一句拆成如下两句来写,结果又不同
fun.foo = obj.foo
fun.foo() //

(fun.foo = obj.foo)得到的结果是( function foo(){ console.log(this.a) } ),所以这个函数执行等同于自执行foo这个函数,this指向全局对象

两句拆开后,第一句是将obj的foo函数赋值给fun,此时,fun对象内部也有了一个foo函数,那fun再调用foo则是应用了this的隐式调用规则,this指向调用对象fun,因此这时输出2

函数中传入参数也属于隐式丢失的一种

        var obj = {
a: 1,
getA: function () {
console.log(this.a)
}
} var a = "global"; function getFn(fn) { // 这里的fn = obj.getA,相当于上面一例当中的other = obj.getA
fn()
} getFn(obj.getA) // global

此时,obj.getA当作参数传到getFn中,这时在getFn中调用它等同于默认调用,this指向全局对象或undefined。

与此情况类似的还有使用定时器调用函数,setTimeout(function(){ ... //在函数中的this也存在隐式丢失 }, time),原理同参数传递!

3.显示绑定:使用call()/apply()

call()和applay()的区别:前者接受的是若干参数的列表,而后者接受的是一个包含多个参数的数组

fn.call(obj, arg1, arg2,...)

fn.apply(obj, [arg1, arg2,...])

延伸一下:

fn.bind(obj, [arg1, [arg2, [, ...]]])

bind的特性:

  1. 指定this
  2. 返回一个函数
  3. 可以指定参数
  4. 柯里化

call() applay()和bind的区别:call和apply是直接执行了前面的fn函数,而bind()返回的是一个新函数,当再次调用它时,它的this值会绑定到obj上。

call()和apply()中的第一个参数为一个对象,使用call()或applay()会默认将this绑定到这个对象上。

    function foo() {
console.log(this.a)
} var obj = {
a: 3
} var fun = function () {
foo.call(obj) // 强制把foo的this绑定到了obj上
} fun() //
setTimeout(fun, 1000) //
fun.call(window) // 3 fun函数内部已经存在硬绑定,不可能再修改它的this

显示绑定无法解决丢失绑定的问题。?????啥意思????

有时候把null或者undefined作为this的绑定对象传入call()、apply()的时候,实际应用的是默认规则!即相当于没有应用显示绑定!!

但是,有以下两种情况可以利用null:

  1. 使用apply()展开一个数组,因为apply的语法是:apply(obj, [para1, para2, para3...]),参数放在一个数组当中传入到函数中
  2. 利用bind预先设置一些参数
    function foo(a, b) {
console.log("a:" + a + ",b:" + b);
} // 1. 利用null把数组“展开”成参数
foo.apply(null, [2, 3]); // a:2,b:3 // 2. 利用bind(..)进行柯里化(即预先设置一些参数)
var bar = foo.bind(null, 2);
bar(3); // a:2,b:3

但是,传入null会带来一些副作用:比如某个函数确实使用了this,那默认绑定规则会把this绑定到全局对象中

所以,可以用var empty = Object.create(null);生成一个空对象,用它代替null,此时的empty不会创建Object.prototype这个委托,比{}更空!

4.new绑定:

构造函数和普通函数的差别就在于:构造函数是被new操作符调用的函数,会生成一个实例对象。该实例对象有两个特性:1. 能访问到构造函数内部的属性;2. 能访问到原型里的属性

使用new调用函数,会自动执行如下操作:

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(因此,this指向了这个新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象
        function create() {
// 创建一个对象
var obj = new Object(); // 将arguments的第一个参数,即作为要传入的构造函数
var Constructor = [].shift.call(arguments); // 将obj的原型指向构造函数
obj.__proto__ = Constructor.prototype; // 改变Constructor的this指向,指到obj对象,并给obj添加新属性arguments
Constructor.apply(obj, arguments);
// 返回obj
return obj;
}

解析:

  1. [].shift的作用是将arguments这个伪数组对象转成数组,等价于:Array.prototype.shift;因为arguments只能获取索引值和length,没有数组的一系列方法,因此加call是通过显示绑定让arguments变相有shift这个方法
  2. var Constructor = [].shift.call(arguments);这句其实得到的就是create这个函数,并将其赋给Constructor
  3. 但是还有一个问题是:需要判断return的值是不是对象!!!

如果函数没有return返回值 or return的是String/Number/null,那么new表达式中的函数调用会自动返回这个新对象;

如果函数return了一个Object类型,则this指向return返回的对象。

所以create()得到如下优化代码:

        function create() {
var obj = new Object();
var Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var result = Constructor.apply(obj, arguments);
return typeof result == 'object' ? result : obj;
}

__proto__是访问器属性,通过它可以访问到对象的内部属性[[prototyoe]],

但是在使用时并不推荐使用__proto__,原因有两点:

  1. __proto__在ES6时才被标准化,存在浏览器兼容问题
  2. 通过改变一个对象的[[prototype]]去改变和继承对象的属性会造成严重的性能问题,所以应该尽量避免去改变一个对象的[[prototype]]属性

因此,如果要创建一个对象并且继承这个对象的[[prototype]],这里推荐使用Object.create(),因此代码优化如下:

        function create() {
var Constructor = [].shift.call(arguments);
var obj = Object.create(Constructor.prototype);
var result = Constructor.apply(obj, arguments); return result instanceof Object ? result : obj;
}

5.箭头函数(不按照以上四种绑定规则,而是由外层(函数或者全局)作用域来决定this)

以上四种绑定规则实际上总结为:this总是指向调用该函数的对象!

箭头函数的this总结:

  1. 箭头函数不绑定this
  2. 其this寻值行为与普通变量相同,是在作用域中逐级寻找
  3. 无法通过bind、call、apply来直接修改(可以间接修改)
  4. 改变作用域中的this指向可以改变箭头函数的this
function foo() {
return (a) => {
console.log( this.a ); // this指向的是foo(),因为箭头函数在foo函数这个作用域中
};
} var obj1 = {
a: 2
}; var obj2 = {
a: 3
} var bar = foo.call( obj1 );
bar.call( obj2 ); // 2,不是3!这里实际是foo.call(obj1).(obj2)

foo的this已经绑定到了obj1上,foo()的内部创建了一个箭头函数会捕捉foo()的this,箭头函数的绑定无法被修改!!即使使用new也不会修改!

并且我们知道通过硬绑定后不能再次修改它的绑定,所以这里是把foo里的this指向obj1而不是obj2

几种绑定方式的优先级:

new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

对this的理解与总结的更多相关文章

  1. 理解CSS视觉格式化

    前面的话   CSS视觉格式化这个词可能比较陌生,但说起盒模型可能就恍然大悟了.实际上,盒模型只是CSS视觉格式化的一部分.视觉格式化分为块级和行内两种处理方式.理解视觉格式化,可以确定得到的效果是应 ...

  2. 彻底理解AC多模式匹配算法

    (本文尤其适合遍览网上的讲解而仍百思不得姐的同学) 一.原理 AC自动机首先将模式组记录为Trie字典树的形式,以节点表示不同状态,边上标以字母表中的字符,表示状态的转移.根节点状态记为0状态,表示起 ...

  3. 理解加密算法(三)——创建CA机构,签发证书并开始TLS通信

    接理解加密算法(一)--加密算法分类.理解加密算法(二)--TLS/SSL 1 不安全的TCP通信 普通的TCP通信数据是明文传输的,所以存在数据泄露和被篡改的风险,我们可以写一段测试代码试验一下. ...

  4. node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理

    一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...

  5. 如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念

    一.前言     DDD(领域驱动设计)的一些介绍网上资料很多,这里就不继续描述了.自己使用领域驱动设计摸滚打爬也有2年多的时间,出于对知识的总结和分享,也是对自我理解的一个公开检验,介于博客园这个平 ...

  6. 学习AOP之透过Spring的Ioc理解Advisor

    花了几天时间来学习Spring,突然明白一个问题,就是看书不能让人理解Spring,一方面要结合使用场景,另一方面要阅读源代码,这种方式理解起来事半功倍.那看书有什么用呢?主要还是扩展视野,毕竟书是别 ...

  7. ThreadLocal简单理解

    在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...

  8. JS核心系列:理解 new 的运行机制

    和其他高级语言一样 javascript 中也有 new 运算符,我们知道 new 运算符是用来实例化一个类,从而在内存中分配一个实例对象. 但在 javascript 中,万物皆对象,为什么还要通过 ...

  9. 深入理解JS 执行细节

    javascript从定义到执行,JS引擎在实现层做了很多初始化工作,因此在学习JS引擎工作机制之前,我们需要引入几个相关的概念:执行环境栈.全局对象.执行环境.变量对象.活动对象.作用域和作用域链等 ...

  10. 浅谈我对DDD领域驱动设计的理解

    从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...

随机推荐

  1. inobounce.js : 禁止IOS H5的滑动回弹

    IOS的移动端/H5/webapp 页面如果滚动到底部或者在页面顶部再往上拉,都会出现一个回弹的效果. 想取消这个效果可以引入一个简单的库就行,不用再写繁琐的样式. github地址 直接引入就行 & ...

  2. [kuangbin带你飞]专题一 简单搜索 - N - Find a way

    正确代码: #include<iostream> #include<queue> #define N 210 #define inf 0xffffff using namesp ...

  3. selenium+python 绕过登录进行测试

    多个Py文件进行多线程测试时,每次登录是很麻烦的事情,所以通过cookie登录,可以绕过登录操作      但是至少需要正常登录一次才能获取到cookie 然后使用cookies进行登录

  4. spark自定义函数之——UDF使用详解及代码示例

    前言 本文介绍如何在Spark Sql和DataFrame中使用UDF,如何利用UDF给一个表或者一个DataFrame根据需求添加几列,并给出了旧版(Spark1.x)和新版(Spark2.x)完整 ...

  5. Apache Spark 2.2.0 中文文档 - Spark SQL, DataFrames and Datasets

    Spark SQL, DataFrames and Datasets Guide Overview SQL Datasets and DataFrames 开始入门 起始点: SparkSession ...

  6. "_CMTimeGetSeconds", referenced from:

    CMTime is defined in the CoreMedia.framework. Add that framework to your project.

  7. CSIC_716_20191106【列表、元组、字典、集合】

    python的数据类型及其内置方法 一.列表(续) list.count( ).list.index( ) list = ['1', '2', '3', '2', 'a', 'b', 'c', 'a' ...

  8. leetcode-分治

    题目169: 分治:O(nlgn) class Solution: def majorityElement(self, nums: List[int]) -> int: def majorE(l ...

  9. leetcode-63-不同路径②

    题目描述: 第一次提交: class Solution: def uniquePathsWithObstacles(self, obstacleGrid) : m = len(obstacleGrid ...

  10. The Preliminary Contest for ICPC Asia Nanjing 2019 C. Tsy's number 5

    https://nanti.jisuanke.com/t/41300 题意:求\(\sum_{i=1}^n\phi(i)\phi(j)2^{\phi(i)\phi(j)}\) \(f_i=\sum_{ ...