this的概念:
this就是属性或方法当前所在的对象(重点)
例子说明
let obj = {
a:1,
logContext: function () {
console.log('obj:', this)
}
}
obj.logContext()//打印obj对象
上面代码中,this 其实就是 obj 对象,this在logContext函数中被调用,而 logContext 属于 obj 对象上的一个方法。
如果我们对代码 {4} 进行改造,改造如下。
// ...
logContext: function () {
console.log('obj:', this, this.a)
}
// ...
在执行完改造代码后会发现打印 "1", 这也说明当前的 this 指向 obj
我们再来看一个特殊的例子, 在全局作用域定义一个 logContext
function logContext () {
console.log(this)
}
logContext();//window
//浏览器环境打印window,node环境打印global对象,严格环境下打印undefined
分析:logContext 的 this 指向取决于当前所在的对象,由于目前 logContext 是定义在全局环境下,相当于是直接挂在 window 对象下的。
我们在执行代码 {5} 的时候,其实相当于隐式调用(window.logContext),所以 logContext 所在的当前对象是 window
由于对象的属性是可以赋值给另一个对象,所以属性当前的对象是可变的,即this的指向是可变的
既然知道了一个函数中的 this 就是所属于当前所在的对象,那我们再看下面这个案例,来思考一下。
我们再次定义了一个 animal 对象,并执行关键的第 {16} 行代码,那我们思考一下,执行第 {17} 行代码,
此时的 this 指向谁?而 animal.logContext 的函数引用又指向谁,让我们分析一下
let obj = {
a:1,
logContext: function () {
console.log('obj:', this)
}
}
obj.logContext()//展示'obj',
let animal = {
name: 'monkey',
logContext: function () {
console.log('animal', this);
}
}
animal.logContext = obj.logContext
animal.logContext()
- 由于将 obj.logContext 指向了 animal.logContext, 所以 animal.logContext 的函数引用也指向了 obj.logContext, 也就是说在执行 第 {17} 行代码 的时候,虽然调用的是 animal.logContext,实际执行的是 obj.logContex 方法
- this 是指向函数当前所在的对象,那我们想一下, animal.logContext 的引用虽然改变,但是它所在的对象并没有发生改变,所以 this 仍然指向 animal
this指向绑定规则(默认/隐式/new/显示绑定)
1.默认绑定:独立的函数调用,可以理解为没有被绑定到某个对对象上进行调用(非严格模式下指向window,严格模式指向undefined)
let obj = {
name:'why',
bar: function() {
console.log('bar: ',this);
}
}
let baz = obj.bar;//将bar函数赋值给baz(引用赋值,赋值地址)
baz()//指向window对象(非严格模式)
2.隐式绑定:通过某个对象进行调用
隐式绑定前提条件:
1.在调用的对象内部有一个对于函数的引用(比如一个属性)
2.没有这样的引用,在进行调用的时候会报找不到该函数的错误
3.通过这个引用,将this绑定到了这个对象上面
function foo() {
console.log('foo is handsome', this);
}
let obj = {
name:'qqq'
}
obj.aaa = foo;//将foo赋值给obj的aaa对象
obj.aaa()//this此刻绑定到obj对象
3.new绑定
使用new关键字来调用函数会执行以下的操作:
1.创建一个全新的对象
2.这个新对象会被执行prototype连接
3.这个新对象会被绑定到函数调用的this上(this绑定在这个步骤完成)
4.如果函数没有返回其他对象,表达式会返回这个新对象
步骤 1: 创建一个全新的对象。假设这个对象为 obj。
const obj = {}; // 这个操作在内部进行,不需要显式写出
步骤 2: 这个新对象会被执行 prototype 连接。也就是说,新对象的 proto 属性会被设置为构造函数的 prototype 属性。
obj.proto = Person.prototype;
步骤 3: 这个新对象会被绑定到函数调用的 this 上。
Person.call(obj, 'Alice', 30); // 将构造函数的 this 绑定到新对象上,并传入参数
步骤 4: 如果构造函数没有显式返回其他对象,表达式会返回这个新对象。
const person1 = obj; // 如果构造函数没有显式返回对象,则返回新创建的对象
4.显示绑定(通过call,apply函数进行this绑定)
this四条规则优先级
1.默认规则的优先级最低
function defaultBinding() { console.log(this) }
// 非严格模式下,this 绑定到全局对象
defaultBinding(); // 输出: 全局对象(window)
// 严格模式下,this 绑定为 undefined
'use strict';
defaultBinding(); // 输出: undefined
2.显示绑定优先级高于隐式绑定
const obj = {
value: 42,
method: function() {
console.log(this.value);
}
};
const anotherObj = { value: 100 };
// 隐式绑定:this 绑定到 obj
obj.method(); // 输出: 42
// 显示绑定:this 绑定到 anotherObj
obj.method.call(anotherObj); // 输出: 100
3.new绑定优先高于隐式绑定
function Person(name) {
this.name = name;
console.log(this);
}
const obj = {
Person: Person
};
// 隐式绑定将 this 绑定到 obj
obj.Person('Alice'); // 输出: obj
// new 绑定将 this 绑定到新创建的对象
const newPerson = new obj.Person('Bob'); // 输出: 新创建的 Person 对象
4.new绑定优先级高于bind
function Person(name) {
this.name = name;
console.log(this);
}
const anotherObj = {};
// 使用 bind 显示绑定 this 到 anotherObj
const BoundPerson = Person.bind(anotherObj);
// new 绑定优先于 bind,this 绑定到新创建的对象
const newPerson = new BoundPerson('Charlie'); // 输出: 新创建的 Person 对象
console.log(newPerson.name); // 输出: Charlie
console.log(anotherObj.name); // 输出: undefined
a.new绑定可以与bind一起使用,new绑定优先级更高
function Person(name) {
this.name = name;
console.log(this);
}
const anotherObj = {};
// 使用 bind 创建一个绑定到 anotherObj 的函数
const BoundPerson = Person.bind(anotherObj);
// 使用 new 调用 BoundPerson
const newPerson = new BoundPerson('Charlie'); // 输出: 新创建的 Person 对象
console.log(newPerson.name); // 输出: Charlie
console.log(anotherObj.name); // 输出: undefined
b.new绑定和call,apply不允许同时使用,不存在谁的优先级更高
function Person(name) {
this.name = name;
console.log(this);
}
// 使用 new 调用 Person
const newPerson = new Person('Dave'); // 输出: 新创建的 Person 对象
// 试图同时使用 new 和 call,会导致语法错误
const invalidCall = new Person.call({}, 'Eve'); // TypeError: Person.call is not a constructor
改变this指向的特殊情况
-
直接取出内层地址调用
var obj ={ foo: function () { console.log(this); } }; // 情况一 (obj.foo = obj.foo)() // window // 情况二 (false || obj.foo)() // window/ / 情况三 (1, obj.foo)() // windowjs引擎内部,obj与obj.foo储存在两个内存地址,obj.foo()是从地址一调用地址二,因此地址二的运行环境是地址一,this指向obj,上面三种情况都是直接取出地址二进行调用,此时的运行环境为window
-
如果
this所在的方法不在对象的第一层,这时this只是指向当前一层的对象,而不会继承更上面的层。var a = { p: 'Hello', b: { m: function() { console.log(this.p); } } }; a.b.m() // undefineda.b.m在对象的第二层,m此时是普通的函数,而不是对象的方法,对于普通函数,非严格模式下指向全局对象,严格模式指向undefined
### 使用this注意点:
避免多层this
默认情况下,this的指向是不确定的,所以不要再函数中包含多层this
var o = {
f1: function () {
console.log(this);
var f2 = function () {
console.log(this);
}();
}
}
o.f1()
// Object
// Window
若想要f2函数内的this指向外层即f1,可以在f2定义一个指向外层的this变量(常见做法)
var o = {
f1: function() {
console.log(this);
var that = this;
var f2 = function() {
console.log(that);
}();
}
}
o.f1()
// Object
// Object
绑定this的方法(显示绑定)
为什么需要显示绑定?
this的动态切换虽然增添了js的灵活性,但由于this的指向是不确定的,需要显示的把this固定下来(通过js提供的call,apply,bind方法)
1.Function.prototype.call()
- call方法的作用:call可以指定函数内部this的指向,然后在所指定的作用域中,调用该函数
var obj = {};
var f = function () {
return this;
};
f() === window // true,浏览器环境
f.call(obj) === obj // true
上面的代码,指定f内部的this指定对象obj,然后在obj的作用域运行函数f
- call方法的参数是一个对象,如果参数为空,null,undefined则默认传入全局对象
var n = 123;
var obj = { n: 456 };
function a() {
console.log(this.n);
}
a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456
为什么var定义的变量挂载到全局环境中了?
var定义的变量具有函数作用域和全局作用域,即便在代码块中定义,var声明的变量依然会被提升到函数或全局作用域顶部;
let、const定义的变量具有块级作用域,仅在声明所在的代码块内有效,全局作用域中,let、const声明的变量不会称为全局对象的属性
- 如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象
var f = function () {
return this;
};
f.call(5)
// Number {[[PrimitiveValue]]: 5}
上面代码中call的参数为5,不是对象,会被自动转成包装对象:Number的实例来绑定f内部的this
- call方法还可以接受多个参数
func.call(thisValue, arg1, arg2, ...)
call的第一个参数就是this所要指向的那个对象,后面的参数是函数调用时所需的参数
- call方法的一个应用是调用对象的原生方法
var obj = {};
obj.hasOwnProperty('toString') // false
// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty('toString') // true
Object.prototype.hasOwnProperty.call(obj, 'toString') // false
上面代码中,hasOwnProperty被覆盖,就无法使用obj.hasOwnProperty这种方式得到原生方法调用的结果,但是通过call,使用Object.prototype.hasOwnProperty.call(obj, 'toString'),将hasOwnProperty中的this绑定到obj上面,将在obj作用域内使用hasOwnProperty函数,这样无论obj上是否有同名方法,都不会影响结果
2.Function.prototype.apply()
- apply作用:作用与call方法类似,也是改变this指向然后调用函数,唯一的区别是接受数组作为函数执行时的参数
func.apply(thisValue, [arg1, arg2, ...])//使用格式
//使用案例
function f(x, y){
console.log(x + y);
}
f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2
3.Function.prototype.bind()
- bind作用:将函数体内的this绑定到某个对象,然后返回一个新函数
### 箭头函数
1.箭头函数不会绑定this,arguments属性
2.箭头函数不能作为构造函数来使用(不能与new一起来使用,会抛出错误)
箭头函数的优化
1.箭头函数只有一个参数,()可以省略
2.函数体中只有一行执行代码,{}可以省略
3.只有一行代码时,这行代码的表达式结果会作为函数的返回值默认返回
4.如果默认返回值是一个对象,这个对象必须加上()
大括号 {} 在 JavaScript 中有两种用途:一种是用来表示代码块,另一种是用来表示对象字面量。没有小括号,JavaScript 解析器会将 {} 解释为代码块的开始。
const getDefaultObject = () => { key: 'value' }; // 这是错误的
const getDefaultObject = () => ({ key: 'value' });//正确写法
自我提问
1.箭头函数不会绑定this,那么this从哪里来?
箭头函数使用的是词法作用域,它们从定义它们的上下文中“继承”了 this 和 arguments。
2.为什么箭头函数不能作为构造函数来使用?(不能与new一起来使用,会抛出错误)
a.箭头函数没有 [[Construct]] 方法,因此不能用 new 调用。
b.箭头函数没有 prototype 属性,构造函数必须有一个 prototype 属性,这个属性会被新创建的对象实例所引用。