一、this的原理

1.1问题的由来

学懂 JavaScript 语言,一个标志就是理解下面两种写法,可能有不一样的结果

var obj = {foo: function () {}
};var foo = obj.foo;// 写法一
obj.foo()// 写法二
foo()

上面代码中,虽然obj.foofoo指向同一个函数,但是执行结果可能不一样。请看下面的例子。

var obj = {foo: function () { console.log(this.bar) },bar: 1
};var foo = obj.foo;
var bar = 2;obj.foo() // 1
foo() // 2

这种差异的原因,就在于函数体内部使用了this关键字。很多教科书会告诉你,this指的是函数运行时所在的环境。对于obj.foo()来说,foo运行在obj环境,所以this指向obj;对于foo()来说,foo运行在全局环境,所以this指向全局环境。所以,两者的运行结果不一样

函数的运行环境到底是怎么决定的?举例来说,为什么obj.foo()就是在obj环境执行,而一旦var foo = obj.foofoo()就变成在全局环境执行?

理解了这一点,你就会彻底理解this的作用。

1.2内存的数据结构

JavaScript 语言之所以有this的设计,跟内存里面的数据结构有关系。

var obj = { foo:  5 };

上面的代码将一个对象赋值给变量obj。JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },然后把这个对象的内存地址赋值给变量obj

也就是说,变量obj是一个地址(reference)。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。

原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo属性,实际上是以下面的形式保存的。

{foo: {[[value]]: 5[[writable]]: true[[enumerable]]: true[[configurable]]: true}
}

注意,foo属性的值保存在属性描述对象的value属性里面。

1.3函数

这样的结构是很清晰的,问题在于属性的值可能是一个函数。

var obj = { foo: function () {} };

这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo属性的value属性。

{foo: {[[value]]: 函数的地址...}
}

由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。

var f = function () {};
var obj = { f: f };// 单独执行
f()// obj 环境执行
obj.f()

1.4环境变量

JavaScript 允许在函数体内部,引用当前环境的其他变量。

var f = function () {console.log(x);
};

上面代码中,函数体里面使用了变量x。该变量由运行环境提供。

现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境

var f = function () {console.log(this.x);
}

上面代码中,函数体里面的this.x就是指当前运行环境的x

var f = function () {console.log(this.x);
}var x = 1;
var obj = {f: f,x: 2,
};// 单独执行
f() // 1// obj 环境执行
obj.f() // 2

上面代码中,函数f在全局环境执行,this.x指向全局环境的x

obj环境执行,this.x指向obj.x

obj.foo()是通过obj找到foo,所以就是在obj环境执行。一旦var foo = obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环境执行。

二、函数不同调用方式中this取值

JavaScript 中的 this,它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript 中函数的调用有以下几种方式:

  • 作为对象方法调用
  • 作为函数直接调用
  • 作为构造函数调用
  • 使用 apply 或 call,bind 调用

我们将按照调用方式的不同,分别讨论 this 的含义。

2.1作为对象方法调用

在 JavaScript 中,函数也是对象,因此函数可以作为一个对象的属性,此时该函数被称为该对象的方法,在使用这种调用方式时,this 被自然绑定到该对象

var test = {     a:0,     b:0,get:function(){return this.a;     }
}
console.log(test.get()) //0var prop=30
var O={prop:37}
function independent(){return this.prop
}
O.f=independentconsole.log(O.f()) //37  作为对象方法调用console.log(independent()) //30  作为函数直接调用

由此可见this是动态作用域,this的指向关键是看函数怎么调用而不是怎么创建

2.2作为函数直接调用

函数也可以直接被调用,此时 this 绑定到全局对象(在严格模式下为undefined)。在浏览器中,window 就是该全局对象。

function fn1 (){return this
}
console.log(fn1())  //windowfunction fn2 (){"use strict";//see strict modereturn this
}
console.log(fn2 ())  //undefined

2.3作为构造函数调用

  • 当函数作为构造函数调用时,this代表new出来的对象,并且对象的原型会指向构造器的prototype(构造器是Foo、foo.constructor=Foo )
  • 如果没有使用new而是直接调用函数,this===window;
function Foo (name){this.name = name ;console.log(this)
}
var foo = new Foo('Emma');  //Foo {name: "Emma"}
Foo() //window

2.4使用 apply 或 call,bind 调用

apply 和 call允许切换函数执行的上下文环境(context),即 this 绑定的对象。this取得的是apply() 、call()、 bind()调用时传入的第一个参数(bind是ES5提供的方法,所以只有IE9及以上才支持)

2.4.1 apply()、call()

function add(c,d){return this.a + this.b +c + d;
}var O = {a:1,b:3}add.call(O,5,7); //1+3+5+7add.apply(O,[10,20]) //1+3+10+20function bar(){console.log(Object.prototype.toString.call(this))
}bar.call(7) // "[object Number]"

2.4.2 bind()改变this取值

function f(){return this.a
}var g = f.bind({a:"test"})
console.log(g()); //testvar O = {a:37,f:f,g:g};
console.log(O.f(),O.g()) //37,test

bind与new(用new调用bind改变不了this取值)

function foo(){this.b=100;return this.a
}
var func = foo.bind({a:1})
func(); //1
new func(); //{b:100}

直接调用func(),this会指向bind中的参数,所以返回值为1,由于执行this.b=100,所以this对象会有个b属性,最终this为{a: 1, b: 100}。用new的话return如果不是对象会把this作为返回值,并且this会被初始化为一个默认的空对象,对象的原型是构造器的prototype,所以这里虽然用了bind的方法,但是this仍然会指向没有bind的情况。this指向空对象,空对象的原型指向foo.prototype,空对象的b属性会设置为100,整个对象会作用返回值(return this.a 被忽视)所以结果为{b:100}.

bind与currying(函数柯里化:把一个函数拆成多个单元)

function add(a,b,c){return a + b + c
}var func = add.bind(undefined,100);
func(1,2) //103 (100 + 1 +2)var func2 = func.bind(undefined,200);
func2(10) //310 (100+ 200 + 10)

有时候不需要一次性把函数都调用完,而是调用一次把前几个参数传完了以后得到了一个函数再去调用,并且每次都传入自己所需要传入的值。

add.bind不需要改变this,那么就传入undefined(null也可),但是我们提供了额外参数100,那么bind方法就会把100赋值给a,再调用的时候1会给b,2就会给c,所以最后答案是103。再func.bind一次a上次绑定了100,b就会绑定为200,再调用func2传入值为10,10赋值为c,所以func2调用结果为310。

function getConfig(color,size,otherOptions){console.log(color,size,otherOptions)
}var defaultConfig=getConfig.bind(null,"#cc0000","1024*768");defaultConfig("123") //#cc0000","1024*768,123
defaultConfig("456") //#cc0000","1024*768,456

三、不同环境this的取值

3.1全局作用域中this(浏览器)

全局作用域中的this一般指向全局对象,在浏览器中全局对象就是window

console.log(window.document===document) //true
console.log(this===window) //true
this.a=37
console.log(window.a) //37

3.2对象原型链上的this

var o={ f:function(){ this.a + this.b } }
var p=Object.create(o)
p.a=1
p.b=4
console.log(p.f()) //5

p是空对象并且它的原型会指向o(p.__proto__===o),调用原型上的方法(对象本身没有此方法会沿着原型链向上查找,原型链上的this也可以指向Object.create创建的对象,此this的指向与new创建实例对象的指向类似)this.a和this.b仍然能取到对象p上的a和b。即原型链上的this可以指向所在的对象(p对象

注:p的构造器不为o,为Object(p.constructor===Object),这是用Object.create创建对象与new实例创建对象的不同,如果是new创建的实例对象构造器就是o。

3.3get/set方法与this

此时this的指向与原型链上的this的指向类似

function modules(){return Math.sqrt(this.re*this.re + this.im*this.im)
}var o={re:1,im:-1,get phase(){return Math.atan2(this.im,this.re)}
}Object.defineProperty(o,"modules",{get:modules,enumerable:true,configurable:true
})console.log(o.phase,o.modules) //logs -0.781.4142

get、set方法中的this,一般情况下也是会指向get,set方法所在的对象(与一般对象属性作为函数对象类似)。

参考:

JavaScript 的 this 原理

https://blog.csdn.net/qq_40646986/article/details/82657909

object取值_this的原理、函数的不同调用方式this取值、以及不同环境下this的取值、函数四种调用方法...相关推荐

  1. 函数的四种调用模式.上下文调用.call.apply

    闭包:函数就是一个闭包,一个封闭的作用域; 返回函数,要返回多个函数就用一个对象封装一下, 立即执行函数+return 回调函数 JS动态创建的DOM,不会被搜索引擎抓取,对SEO不友好. /*win ...

  2. JavaScript高级第2天:定义函数的三种方式、函数的原型链结构、完整原型链、作用域以及作用域链、函数的四种调用模式、闭包、计数器、斐波那契数列优化、三种继承方式

    JavaScript高级第二天 01-定义函数的三种方式 1.函数声明 function:可以先调用再声明,因为预解析(把函数声明.变量声明进行提升) function fn() {//函数体conl ...

  3. 项目总结10:通过反射解决springboot环境下从redis取缓存进行转换时出现ClassCastException异常问题...

    通过反射解决springboot环境下从redis取缓存进行转换时出现ClassCastException异常问题 关键字 springboot热部署  ClassCastException异常 反射 ...

  4. JavaScript中函数四种调用模式

    目录 JS中函数的四种调用模式 函数调用模式 方法调用模式 构造器调用模式 上下文调用模式 JS中函数的四种调用模式 在函数的调用模式中感觉最大的区别就是: this指向 函数调用模式 即通过函数名直 ...

  5. 函数的四种调用模式以及call()apply()和bind()

    函数的四种调用模式 根据函数内部this的指向不同,可以将函数的调用模式分成4种 函数调用模式 方法调用模式 构造函数调用模式 上下文调用模式(借用方法模式) 函数:当一个函数不是一个对象的属性时,我 ...

  6. 浅谈js函数三种定义方式 四种调用方式 调用顺序

    在Javascript定义一个函数一般有如下三种方式: 函数关键字(function)语句: function fnMethodName(x){alert(x);} 函数字面量(Function Li ...

  7. JavaScript中函数的四种调用方式(若有错误之处请斧正)

    函数的几种调用方式 一.一般形式函数的直接调用 fun(); 二.作为对象的方法调用 var obj = {name:"123",sayMyage:function(age){al ...

  8. C语言中返回字符串函数的四种实现方法

    有四种方式: 1.使用堆空间,返回申请的堆地址,注意释放 2.函数参数传递指针,返回该指针 3.返回函数内定义的静态变量(共享) 4.返回全局变量 其实就是要返回一个有效的指针,尾部变量退出后就无效了 ...

  9. C语言:跨平台环境下使用snprintf,vsnprintf系列函数要注意返回值的问题

    标准C语言函数snprintf,vsnprintf系列函数可以向指定的缓冲区输出格式化打印的字符串. 如果指定的缓存区足够大,那么调用正常,返回值就是写入缓存区的字节长度(不含结尾'\0') 那么缓存 ...

最新文章

  1. JavaScript Document 对象
  2. 【MySQL】MySQL的索引
  3. Collection集合概述
  4. [五]RabbitMQ-客户端源码之AMQChannel
  5. 教你从零搭建Web漏洞靶场OWASP Benchmark
  6. Luogu 2296 寻找道路
  7. extjs 方法执行顺序_(软件工程)非结构化程序变为结构化程序的三种方法
  8. 【bzoj2199/Usaco2011 Jan】奶牛议会——2-sat
  9. 韩语在线翻译图片识别_在线翻译有道小程序
  10. python中的减号_我的python把减号和破折号混淆了
  11. DTOI 10.25 测试 T3 雪人
  12. 互联网系统故障应急处理流程
  13. Cadence仿真笔记(二):传统noise仿真—共源极的噪声
  14. 也谈SAP系统优缺点
  15. windows11-USB禁用
  16. 求生之路2服务器消息,求生之路2服务器公告设置
  17. 云服务器训练神经网络
  18. 记我的第一次腾讯游戏策划面试
  19. 【leetcode刷题】70.最少操作使数组递增——Java版
  20. 交直轴电感matlab_永磁电机交直轴电感Lq Ld仿真计算ANSOFT实例详解.pdf

热门文章

  1. django启动时同时使用Schedule启动其他程序
  2. 交流充电桩电路图_直流充电桩和交流充电桩给电动汽车充电过程中是如何工作的?...
  3. 【Jmeter篇】jmeter Ant Jenkins接口自动化测试集成之半路逆转(二)
  4. 【JMeter】Threads(users)3种类型
  5. 【Python爬虫】微信公众号历史文章和文章评论API分析
  6. Vue项目中自动将px转换为rem
  7. msp430项目编程14
  8. ZK tree使用mold
  9. node.js 针对不同的请求路径(url) 做出不同的响应
  10. 程序员常用字体(vs2008字体修改方案)