活动发起人@小虚竹 想对你说:
这是一个以写作博客为目的的创作活动,旨在鼓励大学生博主们挖掘自己的创作潜能,展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴,那么,快来参加吧!我们一起发掘写作的魅力,书写出属于我们的故事。我们诚挚邀请你参加为期14天的创作挑战赛!
提醒:在发布作品前,请将不需要的内容删除。
执行上下文
js在执行代码之前,需要经过一系列的“准备”,这被称为执行上下文,其包含词法环境和this 。所有的 js 代码在运行时都是在执行上下文中进行的,每创建一个执行上下文,就会将当前执行上下文放到一个栈顶,这就就是我们常说的执行栈。
执行上下文的创建
何时创建执行上下文
JavaScript 中有三种情形会创建新的执行上下文:
- 全局执行上下文,进入去全局代码的时候。任何不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文。
- 函数执行上下文,进入
function
函数体代码。当一个函数被调用时,就会为该函数创建一个新的执行上下文,函数的上下文可以有很多个。 - Eval 执行上下文,eval 函数参数指定的代码,执行在eval函数中的代码会有属于它自己的执行上下文。
eval函数执行上下文用的不多,我们只介绍全局执行上下文和函数执行上下文:
- 在全局执行上下文中,this 是指向 window 对象的;
- 在函数执行上下文中,默认情况下调用一个函数,其执行上下文的 this 也是指向 window 的,(理论上谁最后调用它,它指向谁)。
2. 函数的 this 指向
this 是 JavaScript 的一个关键字,多数情况下 this 指向调用它的对象。
这句话有两个意思,首先 this 指向的应该是一个对象(函数执行上下文对象)。其次,这个对象指向的是调用它的对象,如果调用它的不是对象或对象不存在,则会指向全局对象(严格模式下为 undefined)。
其实,this 是在函数被调用时确定的,它的指向取决于函数调用的地方,而不是它被声明的地方(除箭头函数外)。
当函数被调用时,会创建一个执行上下文,它包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息,this 就是这个记录的一个属性,它会在函数执行的过程中被用到。
this 在函数的指向绑定形式有四种:默认绑定、隐式绑定、显式绑定、new绑定。
(1)默认绑定(全局环境)
函数在浏览器全局环境中直接使用不带任何修饰的函数引用进行调用,非严格模式下 this
指向 window
;在 use strict
指明严格模式的情况下就是 undefined
(严格模式不允许 this 指向全局对象)。
注意: 在浏览器环境下,全局对象是window;在Node.js环境下,全局对象是global。
function func1 () {
console.log(this)
}
function func2 () {
'use strict'
console.log(this)
}
func1() // window
func2() // undefined
需要注意一种情况,来看代码:
var num = 1
var foo = {
num: 10,
func: function() {
console.log(this) // window
console.log(this.num) // 1
}
}
var func1 = foo.fn
func1()
这里会输出 Window 和 1。
这里 this
仍然指向 window
。虽然 func
函数在 foo
对象中作为方法被引用,但是在赋值给 func1
之后,func1
的执行仍然是在 window
全局环境中。因此输出 window
和 1
。因为最后是window调用的func1
,所以this指向window
它们相当于:
console.log(window)
console.log(window.num)
(2)隐式绑定(上下文对象)
如果函数在某个上下文对象中调用,那么 this 绑定的是那个上下文对象。来看一个例子:
var a = 'hello'
var obj = {
a: 'world',
fn: function() {
console.log(this.a)
}
}
obj.fn()
在上述代码中,最后会输出"world"。这里fn方法是作为对象的属性调用的,此时fn方法执行时,this会指向obj对象。也就是说,此时this指向的是调用这个方法的对象。
那如果嵌套了多层对象,this的指向又是怎样的呢?下面来看一个例子:
const obj1 = {
text: 1,
fn: function() {
return this.text
}
}
const obj2 = {
text: 2,
fn: function() {
return obj1.fn()
}
}
const obj3 = {
text: 3,
fn: function() {
var fn = obj1.fn
return fn()
}
}
console.log(obj1.fn())
console.log(obj2.fn())
console.log(obj3.fn())
对于这段代码,最终的三个输出结果:
- 第一个
console
输出1
,这时 this 指向了调用 fn 方法的对象 obj1,所以会输出obj1中的属性text
的值1
; - 第二个
console
输出1
,这里调用了obj2.fn()
,最终还是调用o1.fn()
,因此仍然会输出1
。 - 第二个
console
输出undefined
,在进行var fn = o1.fn
赋值之后,是直接调用的,因此这里的this
指向window
,答案是undefined
。
根据上面例子就可以得出结论:如果嵌套了多个对象,那么指向最后一个调用这个方法的对象。
那如何让 console.log(obj2.fn())
输出 2
呢?可以这样:
const obj1 = {
text: 1,
fn: function() {
return this.text
}
}
const obj2 = {
text: 2,
fn: o1.fn
}
console.log(obj2.fn())
还是上面的结论:this
指向最后调用它的对象,在 fn
执行时,挂到 obj2
对象上即可,就相当于提前进行了赋值操作。
(3)显示绑定(apply、call、bind)
显式绑定是指需要引用一个对象时进行强制绑定调用,显式绑定可以使用apply、call、bind
方法来绑定this
值,使其指向我们指定的对象。
call、apply 和 bind三个方法都可以改变函数 this
指向,但是 call 和 apply 是直接进行函数调用;bind
不会执行函数,而是返回一个新的函数,这个新的函数已经自动绑定了新的 this
指向,需要我们手动调用。call 和 apply 的区别: call 方法接受的是参数列表,而 apply 方法接受的是一个参数数组。
这三个方法的使用形式如下:
const target = {}
fn.call(target, 'arg1', 'arg2')
fn.apply(target, ['arg1', 'arg2'])
fn.bind(target, 'arg1', 'arg2')()
需要注意,如果把 null 或 undefined 作为 this 的绑定对象传入 call、apply、bind,这些值在调用时会被忽略,实际应用的是默认绑定规则:
var a = 'hello'
function fn() {
console.log(this.a)
}
fn.call(null)
这里会输出 hello,因为将 null 作为 this 传给了 call 方法,这时 this 会使用默认的绑定规则,this指向了全局对象 window,所以输出 window 中 a 的值 hello。
再来看一个例子:
const foo = {
name: 'hello',
logName: function() {
console.log(this.name)
}
}
const bar = {
name: 'world'
}
console.log(foo.logName.call(bar))
这里将会输出:world。
那如果对一个函数进行多次 bind
,那么上下文会是什么呢?
let a = {}
let fn = function () {
console.log(this)
}
fn.bind().bind(a)()
这里会输出 a
吗?可以把上述代码转换成另一种形式:
// fn.bind().bind(a) 等于
let fn2 = function fn1() {
return function() {
return fn.apply()
}.apply(a)
}
fn2()
可以发现,不管给函数 bind
几次,fn
中的 this
永远由第一次 bind
决定,所以结果永远是 window
。
let a = {
name: 'kobe'
}
function fn() {
console.log(this.name)
}
fn.bind(a)() // kobe
(4)new绑定(构造函数)
函数作为构造函数使用 new 调用时, this 绑定的是新创建的构造函数的实例:
function Person(name,age){
this.name = name;
this.age = age;
this.say = function(){
console.log(this.name + ":" + this.age);
}
}
var person = new Person("kobe",24);
console.log(person.name); // kobe
console.log(person.age); // 24
person.say(); // kobe:24
可以看到,在上面代码中,this 就指向了构造函数 Person 的新对象person,所以使用 this 可以获取到 person 对象的属性和方法。
实际上,在使用 new 调用构造函数时,会执行以下操作:
- 创建一个新对象;
- 构造函数的 prototype 被赋值给这个新对象的 proto;
- 将新对象赋给当前的 this;
- 执行构造函数;
如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象,如果返回的不是对象将被忽略。
总结:
- this 是在函数被调用时确定的,它的指向取决于函数调用的地方,而不是它被声明的地方(除箭头函数外)
- this 在函数的指向绑定形式有四种:默认绑定、隐式绑定、显式绑定、new绑定。
- this优先级:new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定