看有和没有this的区别
var obj = {
name: "John",
eating: function () {
console.log(this.name + "吃东西")
},
running: function () {
console.log(this.name + "在跑步")
},
studying: function () {
console.log(this.name + "在学习")
}
}
obj.eating();
obj.running();
obj.studying();
var info = {
name: "John",
eating: function () {
console.log(info.name + "吃东西")
},
running: function () {
console.log(info.name + "在跑步")
},
studying: function () {
console.log(info.name + "在学习")
}
}
info.eating();
info.running();
info.studying();
运行结果
John吃东西 John在跑步 John在学习 John吃东西 John在跑步 John在学习
不用this有什么劣势
- 当修改对象的名字时,使用里面的方法,可能达不到想要的效果
- 当对象赋值给其他变量时,其他变量使用,可能达不到想要的结果
var foo = {
name: 'WEI'
}
foo.running = info.running;
foo.running(); //John在跑步
this在全局环境下的指向
-
大多数情况下,this都是出现在函数中
-
全局作用下:严格模式 undefined,非严格模式 window
console.log(this) //window
"use strict";
console.log(this); //undefined
- node环境下为 {}
console.log(this); //{}
同一个函数中this不同的调用,导致不同的指向
function foo(){
console.log(this)
}
//1. 直接调用这函数
foo()
// 2 创建一个对象, 对象中的函数指向foo
var obj = {
name: 'wei',
foo: foo
}
obj.foo()
//3 apply 调用
foo.apply('abc')
this中有四种绑定规则:
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
默认绑定
什么是默认绑定?
默认绑定是 this 绑定规则中优先级最低的一种。它适用于独立函数调用的场景,也就是一个函数被直接调用,而不是作为对象的方法、不是通过 new 关键字、也不是通过 call(), apply(), 或 bind() 来调用的情况。
this 指向什么?
在默认绑定规则下,this 的指向取决于函数所在的执行环境是严格模式(Strict Mode)还是非严格模式(Non-strict Mode) :
-
非严格模式 (Non-strict Mode):
- 在这种模式下,默认绑定的 this 会指向全局对象。
- 在浏览器环境中,全局对象通常是 window。
- 在 Node.js 环境中,全局对象是 global。
- 潜在风险: 这可能导致意外地修改全局对象的属性,因为函数内部对 this 的操作会直接作用于全局对象。
-
严格模式 (Strict Mode):
- 在这种模式下(通过在脚本或函数开头使用 'use strict'; 声明),默认绑定的 this 会是 undefined。
- 优点: 这是一种更安全、更可预测的行为。它防止了函数在无意中污染全局命名空间。如果你在严格模式下尝试访问 this 上的属性(而 this 是 undefined),会立即抛出一个 TypeError,帮助你更快地发现错误。
代码演示
案例 1
// 非严格模式 (默认)
var a = 10; // 在全局作用域声明变量,相当于 window.a = 10
function foo() {
console.log(this); // 输出: Window {...} (浏览器) 或 global {...} (Node.js)
console.log(this.a); // 输出: 10
this.b = 20; // 在全局对象上创建属性 b
}
foo(); // <-- 独立函数调用,应用默认绑定
console.log(window.b); // 输出: 20 (浏览器环境)
// console.log(global.b); // 输出: 20 (Node.js 环境)
console.log(b); // 输出: 20 (因为 b 现在是全局变量)
案例 2
'use strict'; // 启用严格模式
var a = 10; // 全局变量
function bar() {
console.log(this); // 输出: undefined
// console.log(this.a); // TypeError: Cannot read properties of undefined (reading 'a')
// this.c = 30; // TypeError: Cannot set properties of undefined (setting 'c')
}
bar(); // <-- 独立函数调用,应用默认绑定
// 尝试访问全局 c 会报错 (因为 bar 函数内部的赋值失败了)
// console.log(c); // ReferenceError: c is not defined
案例 3
var x = 5;
function baz() {
'use strict'; // 只在函数内部启用严格模式
console.log(this); // 输出: undefined
// console.log(this.x); // TypeError
}
function qux() {
// 非严格模式
console.log(this); // 输出: Window / global
console.log(this.x); // 输出: 5
}
baz();
qux();
案例 4
var obj = {
name: 'wei',
foo: function () {
console.log(this)
}
}
obj.foo(); //{ name: 'wei', foo: [Function: foo] }
var bar = obj.foo;
bar() //window
案例 4
function foo() {
console.log(this)
}
var obj = {
name: 'wei',
foo: foo
}
var bar = obj.foo
bar() //window
案例 5
function foo() {
function bar() {
console.log(this);
}
return bar;
}
var fun = foo()
fun() //window
隐式绑定
什么是隐式绑定?
隐式绑定是 this 绑定规则中最常见的一种。它发生在函数作为对象的方法被调用时。换句话说,当函数调用前面有一个“点” (.) 或方括号 ([]) 来访问对象的属性(而这个属性恰好是一个函数)时,隐式绑定规则就会生效。
this 指向什么?
在隐式绑定规则下,this 会被绑定到调用该方法的那个对象。也就是“点”或方括号左边的那个对象。这个对象被称为调用的上下文对象(Context Object) 。
触发条件:
- 函数是通过对象的属性访问(. 或 [])来调用的。
- 关键: 必须是直接调用。如果先把方法赋给一个变量,然后再调用这个变量,就不再是隐式绑定了(这会导致“隐式丢失”,通常会退回到默认绑定)。
示例:
1. 基本示例
const person = {
name: 'Alice',
age: 30,
greet: function() {
// 在 greet 方法内部,this 指向调用它的对象 (person)
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
console.log(this === person); // true
}
};
person.greet(); // <-- 调用 greet 方法,点左边是 person 对象,应用隐式绑定
// 输出:
// Hello, my name is Alice and I am 30 years old.
// true
function foo() {
console.log(this);
}
var obj = {
name: 'wei',
foo: foo
}
obj.foo() //obj对象: {name: 'wei', foo: ƒ}
2. 嵌套对象示例
this 指向的是直接调用该方法的对象,即使对象是嵌套的。
const company = {
name: 'TechCorp',
address: '123 Main St',
department: {
name: 'Engineering',
manager: {
name: 'Bob',
report: function() {
// 在 report 方法内部,this 指向调用它的对象 (manager)
console.log(`Report from ${this.name} in the ${this.departmentName} department.`); // 注意:这里访问 this.departmentName 会失败
// 要访问上层,需要明确引用
console.log(`Correct: Report from ${this.name} in the ${company.department.name} department.`);
console.log(this === company.department.manager); // true
}
}
}
};
company.department.manager.report(); // <-- 调用 report 方法,点左边是 manager 对象,应用隐式绑定
// 输出:
// Report from Bob in the undefined department.
// Correct: Report from Bob in the Engineering department.
// true
var obj1 = {
name: 'obj1',
foo: function() {
console.log(this);
}
}
var obj2 = {
name: 'obj2',
bar: obj1.foo
}
obj2.bar() //{name: 'obj2', bar: ƒ}
隐式丢失 (Implicitly Lost) - 最需要注意的情况!
当隐式绑定的函数(对象的方法)丢失了它的上下文对象时,this 的绑定就会改变,通常会退回到默认绑定(指向全局对象或 undefined)。
常见导致隐式丢失的场景:
-
将方法赋给变量:
const user = { name: 'Charlie', sayHi: function() { console.log(`Hi, I'm ${this.name}`); // 期望 this 是 user console.log(this); } }; user.sayHi(); // 正常隐式绑定,输出: Hi, I'm Charlie, { name: 'Charlie', sayHi: [Function: sayHi] } console.log("--- Separator ---"); const greetingFunc = user.sayHi; // 将方法赋给一个新变量,丢失了 user 这个上下文 greetingFunc(); // <-- 独立函数调用,应用默认绑定 // 非严格模式下: // 输出: Hi, I'm undefined (或全局对象的 name 属性值) // 输出: Window {...} 或 global {...} // 严格模式下 ('use strict'): // TypeError: Cannot read properties of undefined (reading 'name') // 因为 this 是 undefined -
将方法作为回调函数传递:
const counter = { count: 0, increment: function() { // 'use strict'; // 在严格模式下更容易发现问题 this.count++; console.log(this.count); console.log(this); } }; // 直接调用,隐式绑定,正常工作 // counter.increment(); // 输出 1, { count: 1, increment: f } // 作为回调函数传递给 setTimeout // setTimeout(counter.increment, 100); // 100ms 后执行 // 当 setTimeout 执行回调时,它执行的是 "increment" 这个函数本身, // 而不是作为 counter 的方法来执行。上下文丢失! // 非严格模式下: // 100ms 后输出: NaN (因为 this 指向 window/global, this.count 是 undefined, undefined++ 是 NaN) // 100ms 后输出: Window {...} 或 global {...} (全局对象上多了一个 NaN 属性 count) // 严格模式下 ('use strict'): // 100ms 后抛出: TypeError: Cannot read properties of undefined (reading 'count')
如何解决隐式丢失?
通常使用 bind(), call(), apply() 或箭头函数来显式地绑定 this。
// 解决方法示例 (针对回调)
const counter = {
count: 0,
increment: function() {
this.count++;
console.log(this.count);
}
};
// 1. 使用 bind()
const boundIncrement = counter.increment.bind(counter); // 创建一个新函数,this 永久绑定到 counter
setTimeout(boundIncrement, 100); // 100ms 后输出 1
// 2. 使用箭头函数 (常用)
setTimeout(() => {
counter.increment(); // 在箭头函数内部调用,此时 increment 是作为 counter 的方法调用的,应用隐式绑定
}, 200); // 200ms 后输出 2 (假设之前 bind 的也执行了)
// 3. 在一些接受上下文参数的函数中使用 (例如数组方法)
// [1, 2].forEach(counter.increment, counter); // 不适用于 setTimeout
显示绑定
与隐式绑定(根据调用位置的对象自动确定 this)和默认绑定(独立调用时指向全局对象或 undefined)不同,显式绑定允许你明确地、强制地指定一个函数在调用时其内部 this 关键字应该指向哪个对象。
如何实现显式绑定?
JavaScript 提供了三个内置的函数方法来实现显式绑定:
- call()
- apply()
- bind()
这三个方法都存在于所有函数的 prototype 上(Function.prototype),因此任何函数都可以调用它们。
1. Function.prototype.call(thisArg, arg1, arg2, ...)
-
作用: 立即调用该函数,并将函数内部的 this 绑定到你指定的第一个参数 thisArg 上。从第二个参数开始,可以依次传入函数执行所需的参数列表。
-
thisArg: 你希望函数执行时 this 指向的对象。
- 如果传入 null 或 undefined,在非严格模式下 this 会指向全局对象 (window/global),在严格模式下 this 会是 undefined。
- 如果传入原始值(数字、字符串、布尔值),this 会指向该原始值的包装对象 (Number, String, Boolean)。
-
arg1, arg2, ...: 传递给被调用函数的参数,需要逐个列出。
示例:
function greet(punctuation, suffix) {
console.log(`Hello, ${this.name}${punctuation} ${suffix || ''}`);
console.log('this inside greet:', this);
}
const person1 = { name: 'Alice' };
const person2 = { name: 'Bob' };
// 使用 call() 将 greet 函数的 this 绑定到 person1
greet.call(person1, '!', 'Nice to meet you.');
// 输出:
// Hello, Alice! Nice to meet you.
// this inside greet: { name: 'Alice' }
// 使用 call() 将 greet 函数的 this 绑定到 person2
greet.call(person2, '?');
// 输出:
// Hello, Bob?
// this inside greet: { name: 'Bob' }
// 演示 thisArg 为 null (非严格模式)
function showGlobalName() {
console.log(this.name); // 假设全局有 var name = 'Global';
}
// var name = 'Global'; // 在浏览器或 Node 全局定义
// showGlobalName.call(null); // 在非严格模式下会输出 "Global"
// 演示 thisArg 为原始值
function showType() {
console.log(typeof this, this instanceof String, this.valueOf());
}
showType.call("hello"); // 输出: object true hello
2. Function.prototype.apply(thisArg, [argsArray])
- 作用: 与 call() 非常相似,也是立即调用函数并将 this 绑定到 thisArg。
- 主要区别: apply() 接受一个数组(或类数组对象)作为第二个参数,数组中的元素会作为参数传递给被调用的函数。
- thisArg: 同 call()。
- [argsArray]: 一个数组或类数组对象,其元素将作为参数传递给函数。如果不需要传递参数,可以传入 null 或 undefined。
示例:
function introduce(city, country) {
console.log(`I am ${this.name} from ${city}, ${country}.`);
console.log('this inside introduce:', this);
}
const person3 = { name: 'Charlie' };
const locationArgs = ['London', 'UK'];
// 使用 apply() 将 introduce 函数的 this 绑定到 person3,并传递参数数组
introduce.apply(person3, locationArgs);
// 输出:
// I am Charlie from London, UK.
// this inside introduce: { name: 'Charlie' }
// apply() 的常见用途:调用需要参数列表的函数,但你只有一个数组
const numbers = [5, 2, 8, 1, 9];
// Math.max 需要多个数字参数,而不是数组
// const maxNum = Math.max(numbers); // 错误!会返回 NaN
const maxNum = Math.max.apply(null, numbers); // 使用 apply 将数组元素展开作为参数
console.log(maxNum); // 输出: 9
// ES6 Spread 操作符提供了更简洁的方式: Math.max(...numbers)
3. Function.prototype.bind(thisArg, arg1, arg2, ...)
- 作用: bind() 与 call() 和 apply() 不同,它不会立即执行函数。相反,它会创建一个新的函数(称为绑定函数 (Bound Function) ),这个新函数的 this 值被永久地绑定到 bind() 的第一个参数 thisArg。
- 永久绑定 (Hard Binding): 一旦使用 bind() 创建了绑定函数,之后无论如何调用这个绑定函数(即使使用 call() 或 apply() 再次尝试改变它的 this),其内部的 this 始终指向 bind() 时设定的 thisArg。
- 参数预设 (Partial Application): bind() 从第二个参数开始,还可以预先设定函数调用时所需的部分或全部参数。当调用绑定函数时,传递给绑定函数的参数会追加到预设参数之后。
- 返回值: 一个新的、this 和部分参数已绑定的函数。
示例:
function foo() {
console.log(this);
}
// foo.call('aaa')
// foo.call('aaa')
// foo.call('aaa')
// foo.call('aaa')
//都是String {'aaa'},但是太麻烦了
//默认当地和显示绑定bind冲突,显示绑定优先级大
var newFoo = foo.bind('aaa')
newFoo()
function logDetails(level, message) {
console.log(`[${level.toUpperCase()}] ${this.source}: ${message}`);
}
const loggerConfig = { source: 'WebServer' };
const networkConfig = { source: 'NetworkMonitor' };
// 创建一个 this 绑定到 loggerConfig 的新函数
const webLogger = logDetails.bind(loggerConfig);
// 创建一个 this 绑定到 networkConfig 且第一个参数预设为 'INFO' 的新函数
const networkInfoLogger = logDetails.bind(networkConfig, 'info');
// 调用绑定函数
webLogger('ERROR', 'Failed to process request.');
// 输出: [ERROR] WebServer: Failed to process request.
networkInfoLogger('Connection established.'); // 只需要传递剩余的 message 参数
// 输出: [INFO] NetworkMonitor: Connection established.
// 演示 bind 的永久性
const anotherContext = { source: 'AnotherSource' };
webLogger.call(anotherContext, 'WARN', 'Trying to change context?');
// 输出仍然是: [WARN] WebServer: Trying to change context?
// 'this' 仍然指向 loggerConfig,而不是 anotherContext
为什么要使用显式绑定?
-
解决 this 丢失问题: 特别是在回调函数或将方法赋值给变量时,使用 bind() 可以确保函数在稍后执行时 this 指向正确的对象。
-
借用方法: 可以让一个对象“借用”另一个对象的方法,并在自己的上下文(this 指向自己)中执行。例如,类数组对象(如 arguments 或 DOM NodeList)借用 Array.prototype 的方法。
function exampleFunc() { // arguments 不是真数组,没有 slice 方法 // const argsArray = arguments.slice(1); // TypeError // 使用 call() 借用 Array.prototype.slice const argsArray = Array.prototype.slice.call(arguments, 1); console.log(argsArray); } exampleFunc('a', 'b', 'c'); // 输出: [ 'b', 'c' ] -
创建柯里化 (Currying) 或部分应用 (Partial Application) 的函数: bind() 可以方便地创建预设了部分参数的新函数。
优先级:
显式绑定 (call, apply, bind) 的优先级高于隐式绑定和默认绑定。如果一个函数同时满足多种绑定条件,显式绑定会胜出。(注意:new 绑定的优先级又高于显式绑定)。
总结:
- call() 和 apply() 立即执行函数,允许你一次性地指定 this 和参数。call 接收单个参数列表,apply 接收参数数组。
- bind() 不立即执行,而是返回一个新函数,这个新函数的 this 被永久绑定,并且可以预设参数。
- 显式绑定是控制函数执行上下文 (this) 的强大工具,特别适用于回调、方法借用和函数式编程模式。
new绑定
。
这是 this 绑定规则中非常特殊且优先级很高的一种。它发生在使用 new 操作符来调用一个函数时。这个被 new 调用的函数通常被称为构造函数(Constructor Function) 。
new 操作符做了什么?
当你使用 new SomeFunction(...) 时,JavaScript 引擎会执行以下四个步骤:
-
创建新对象: 一个全新的、空的原生 JavaScript 对象被创建。
-
设置原型链接: 这个新创建的对象的内部 [[Prototype]](即 proto)属性被设置为指向构造函数(SomeFunction)的 prototype 对象。这步建立了原型链,使得新对象可以继承构造函数原型上的属性和方法。
-
绑定 this 并执行函数: 构造函数(SomeFunction)被调用,并且其内部的 this 关键字被绑定(指向)到第一步创建的那个新对象上。 这就是 new 绑定的核心。构造函数内部的代码通常会使用 this 来给新对象添加属性和方法。
-
返回新对象(通常情况下):
- 如果构造函数没有显式地使用 return 语句返回一个对象(或者返回的是 null, undefined 或其他原始类型值),那么 new 表达式的结果就是第一步创建并经过第三步初始化的那个新对象。
- 如果构造函数显式地 return 了另一个对象,那么 new 表达式的结果就是这个被 return 的对象,而不是第一步创建的新对象。
this 指向什么?
在 new 绑定规则下,构造函数内部的 this 总是指向在第一步中新创建的那个对象实例。
示例:
function Car(make, model, year) {
// 在 new 调用时,这里的 'this' 指向新创建的 Car 对象实例
console.log('Inside constructor, this:', this);
// 使用 this 给新对象添加属性
this.make = make;
this.model = model;
this.year = year;
this.isRunning = false;
// 不推荐在构造函数中直接添加方法(最好放 prototype)
// this.start = function() { this.isRunning = true; }
// 默认情况下,隐式返回 this (新创建的对象)
// 如果 return { custom: 'object' }; 则 new 的结果是这个 custom object
// 如果 return 5; (原始值),则 return 语句被忽略,仍然返回 this
}
// 使用 new 调用 Car 构造函数
const myCar = new Car('Toyota', 'Camry', 2023);
// myCar 是由 new Car() 创建的对象实例
console.log('myCar instance:', myCar);
console.log(myCar.make); // 输出: Toyota
console.log(myCar.year); // 输出: 2023
// 验证原型链是否设置正确
console.log(Object.getPrototypeOf(myCar) === Car.prototype); // 输出: true
与非 new 调用的对比:
如果同一个函数不使用 new 调用,this 的指向将遵循其他规则(通常是默认绑定):
function Gadget(name) {
// 'use strict'; // 在严格模式下,下面的调用会报错,因为 this 是 undefined
this.name = name;
console.log('Inside Gadget, this:', this);
}
// 使用 new 调用 (new 绑定)
const gadget1 = new Gadget('Phone');
console.log('Gadget 1:', gadget1); // 输出: Gadget 1: Gadget { name: 'Phone' }
console.log("--- Separator ---");
// 不使用 new 调用 (默认绑定)
const gadget2Result = Gadget('Watch');
// 在非严格模式下:
// Inside Gadget, this: Window {...} 或 global {...}
// 全局对象上被添加了 name 属性: window.name = 'Watch'
console.log('Gadget 2 Result:', gadget2Result); // 输出: Gadget 2 Result: undefined (因为函数默认返回 undefined)
console.log(window.name); // 输出: Watch (浏览器环境)
优先级:
new 绑定的优先级非常高,仅次于箭头函数(箭头函数没有自己的 this 绑定)。
- new 绑定 > 显式绑定 (bind, call, apply) > 隐式绑定 (方法调用) > 默认绑定
这意味着,即使你尝试对一个函数使用 bind, call 或 apply 来设置 this,然后再使用 new 来调用它,new 绑定仍然会生效,this 还是会指向新创建的对象。
function Creature(type) {
this.type = type;
console.log(`Creating a ${this.type}, this is:`, this);
}
const predefinedContext = { type: 'SHOULD BE IGNORED' };
// 尝试用 bind 预设 this,但随后用 new 调用
const BoundCreature = Creature.bind(predefinedContext);
const creatureInstance = new BoundCreature('Dragon');
// 输出: Creating a Dragon, this is: Creature { type: 'Dragon' }
// 'this' 指向新创建的 creatureInstance,而不是 predefinedContext
console.log(creatureInstance.type); // 输出: Dragon
关键点总结:
- 触发条件: 使用 new 操作符调用函数。
- this 指向: 函数内部的 this 指向新创建的对象实例。
- 目的: 主要用于面向对象编程中的对象实例化和初始化。
- 高优先级: new 绑定的优先级高于显式绑定、隐式绑定和默认绑定。
- 箭头函数例外: 不能对箭头函数使用 new 操作符,因为箭头函数没有自己的 this,也缺少构造函数所需的内部机制。尝试这样做会抛出 TypeError。