JavaScript中的this, 究竟指向什么?
在JavaScript代码的不同位置中,this所指向的数据是不一样的。比如大部分同学都知道,在对象的函数属性方法中,this指向对象本身;在构造函数中,this指向要生成的新对象。事实上,this指向的逻辑不止这几种,this也不只是与原型链有关。在这里我们研究一下,在不同的场景下,JavaScript中的this, 究竟指向什么。
globalThis
在观察各类this之前,先来了解一下globalThis的概念。globalThis是从不同的JavaScript环境中获取全局对象的方式。
由于在部分环境或者上下文中,使用this是无法直接获取到全局对象的,例如一些模块化的JS代码内,以及在部分上下文的严格模式下(具体场景和区别后面会描述)。因此globalThis提供了一个标准的方式来获取不同环境下的全局this对象。这个对象在不同的JavaScript环境中是不一样的。
// 浏览器环境
console.log(globalThis)
console.log(globalThis === window)
/* 输出
Window {window: Window, self: Window, document: document, ...省略 }
true
*/
// 严格模式下表现一致// Node.js环境
console.log(globalThis)
console.log(globalThis === global)
/* 输出
<ref *1> Object [global] { ...省略 }
true
*/
// 严格模式下表现一致
可以看到,在浏览器中globalThis就是window对象,而在Node.js中,globalThis是global对象。我们直接在命令行中使用var定义的全局变量,实际上会被作为globalThis的属性(但let和const不会)。这里我们不过多介绍全局对象,感兴趣的同学可以自行了解更多。
命令行全局上下文
我们先试一下,直接在命令行的全局上下文中使用this,所指向的值是什么。
浏览器命令行
浏览器命令行,即是在浏览器调试工具的Console中使用this。
// 浏览器命令行
console.log(this)
console.log(this === globalThis)
console.log(this === window)
/* 输出
Window {window: Window, self: Window, document: document, ...省略 }
true
true
*/
// 严格模式下表现一致
可以看到,在浏览器命令行的全局上下文中直接使用this,实际指向的是globalThis,也就是window对象。
Node.js命令行
Node.js命令行,即使用node命令,不带其他参数,进入交互式shell。
// Node.js命令行
console.log(this)
console.log(this === globalThis)
console.log(this === global)
/* 输出
<ref *1> Object [global] { ...省略 }
true
true
*/
// 严格模式下表现一致
在浏览器命令行的全局上下文中直接使用this,实际指向的是globalThis,也就是global对象。
浏览器HTML中的全局上下文
在浏览器的HTML的全局上下文中的this,是否和命令行中不一样呢?我们来实验一下。
<html><body><script>console.log(1, this);console.log(1, this === globalThis);console.log(1, this === window);</script><script src="1.js"></script></body>
</html>
引用的1.js内容:
console.log(2, this);
console.log(2, this === globalThis);
console.log(2, this === window);
这里尝试了两种情况,一种是内部脚本语句,第二种是外部脚本文件。两种情况下,this都指向window。输出结果:
1 Window {window: Window, self: Window, document: document, ...省略 }
1 true
1 true
2 Window {window: Window, self: Window, document: document, ...省略 }
2 true
2 true
// 严格模式下表现一致
CommonJS中的模块上下文
由于JavaScript发展历史的原因,JavaScript有很多模块化开发规范,比如:AMD,CMD,UMD,CommonJS等等。后来ECMAScript标准官方定义了ESModule模块化规范,现在大部分环境都支持这个规范。我们对目前主流使用的ESModule和CommonJS规范进行说明。首先看一下CommonJS,这种规范最常用在Node.js环境。
单个文件
假设我们有一个js文件,里面没有任何模块化规范相关的代码。我们使用命令行直接执行这个文件node 1.js
,这时模块上下文中this的值指向什么呢?是否和命令行直接执行代码一致呢?这里举个例子看下:
console.log(this)
console.log(this === globalThis);
console.log(this === global);
/* 输出
{}
false
false
*/
// 严格模式下表现一致
注意我们不能在带package.json的项目里面执行,否则项目配置会干扰我们的判断。这时查看结果,看到并不是global,而是一个空对象。这个空对象是什么呢?我们继续实验下:
console.log(this)
console.log(module.exports)
console.log(this === module.exports)
/* 输出
{}
{}
true
*/
// 严格模式下表现一致
原来这时候的this是module.exports!这是CommonJS规范中的模块导出内容。也就是说,在我们没有指定规范,且代码内容也没有任何规范相关指示时,Node.js命令行执行的文件会包裹在CommonJS模块中运行。(后面部分会说明如何使文件在ESModule规范下运行)
这时候this的指向与直接命令行执行代码不同,实际指向的是module.exports。我们再看一个例子:
console.log(1, this)
console.log(1, module.exports)
console.log(1, this === module.exports)this.a = 1;
exports.b = 2;console.log(2, this)
console.log(2, module.exports)
console.log(2, exports)
console.log(2, this === module.exports)module.exports.c = 3;console.log(3, this)
console.log(3, module.exports)
console.log(3, this === module.exports)module.exports = {d: 4};console.log(4, this)
console.log(4, module.exports)
console.log(4, exports)
console.log(4, this === module.exports)/* 输出
1 {}
1 {}
1 true
2 { a: 1, b: 2 }
2 { a: 1, b: 2 }
2 { a: 1, b: 2 }
2 true
3 { a: 1, b: 2, c: 3 }
3 { a: 1, b: 2, c: 3 }
3 true
4 { a: 1, b: 2, c: 3 }
4 { d: 4 }
4 { a: 1, b: 2, c: 3 }
5 false
*/
// 严格模式下表现一致
这个例子比较长。最上面我们输出了this和module.exports,都是空对象。然后我们将this和exports都添加了不同的属性,发现this和CommonJS的导出对象都增加了,也证明了exports和module.exports实际是同一个对象。然后在module.exports添加了属性,this中也同时被添加了。
然后看最后一步,我们将module.exports整个替换为其它对象,这时候this和module.exports就再不是一个对象了。而exports依旧是旧对象不变。这里this和exports被覆盖的逻辑是一样的,导出的内容会被新的module.exports覆盖。
实际上,Node.js的CommonJS的模块被包装在一个函数中,并且this值设置为module.exports。
CommonJS模块文件
这里新建两个CommonJS模块文件,看看this的指向问题。首先是入口文件a.js内容:
const b = require("./b");
console.log(b);console.log("a1", this);
console.log("a1", module.exports);
console.log("a1", this === module.exports);exports.a = 1;console.log("a2", this);
console.log("a2", module.exports);
console.log("a2", this === module.exports);
然后是被引用的b.js内容:
console.log("b1", this);
console.log("b1", module.exports);
console.log("b1", this === module.exports);this.b = 2;
module.exports.c = 3;console.log("b2", this);
console.log("b2", module.exports);
console.log("b2", this === module.exports);
命令行执行node a.js
,然后我们看一下输出结果:
b1 {}
b1 {}
b1 true
b2 { b: 2, c: 3 }
b2 { b: 2, c: 3 }
b2 true
{ b: 2, c: 3 }
a1 {}
a1 {}
a1 true
a2 { a: 1 }
a2 { a: 1 }
a2 true
// 严格模式下表现一致
因为文件a中先引用了文件b,所以文件b先输出。首先可以看到,在文件b中,我们使用this和module.exports本身对导出对象添加了属性,可以看到这并不影响this的指向,this依旧指向导出对象,而且我们添加的属性在文件a中成功的输出了。而文件a中this指向的是该文件独立的导出对象,与文件b的导出对象无关。
this是不是模块内的"全局对象"
前面了解到,我们直接在命令行中使用var定义的全局变量,实际上会被作为globalThis的属性。上面我们也清楚了,在CommonJS模块内的this,并不是全局对象,而是该模块的初始导出对象。那么这里的this,是否可以作为这个模块局部的“全局对象”呢?也就是说,在模块中使用var定义的变量,会不会也挂在this上呢?我们来尝试一下。
console.log(this);
var a = 1;
this.b = 2;
module.exports.c = 3;
console.log(this);
console.log(b);
/* 输出
{}
{ b: 2, c: 3 }
ReferenceError: b is not defined
*/
// 严格模式下表现一致
首先使用var定义了变量a,但是后面输出this时,里面并没有a。然后对this添加了属性b,并尝试直接输出变量b,可以看到变量b找不到,引发了异常。可以得出结论,CommonJS中的this,用法并不像globalThis一样,并不是一个模块内的"全局对象"。
小总结
可以看到,当我们在CommonJS模块中使用this时,this指向的是该模块初始的导出对象。此时我们给this添加属性,属性值也会被导出。但如果我们覆盖了导出对象,此时导出对象就和this无关了。另外,模块中的this并不能类似像全局globalThis一样,不能把模块内var变量作为自身的属性。这个也容易理解,如果真的有这种特性,那模块内的变量统统被导出,模块导出机制会变得非常混乱。
ESModule中的模块上下文
ESModule模块化规范是ECMAScript标准官方定义的,目前大部分环境都支持这个规范。这里列举Node.js和浏览器环境,看一下在模块上下文中,this究竟指向什么。
ESModule和浏览器
我们来看下在浏览器中的表现。首先是index.html:
<html><body><script>console.log(1, this);</script><script type="module">import a from "./a.js";console.log(a);console.log(2, this);console.log(2, globalThis);</script></body>
</html>
然后是index.html中引用的a.js:
console.log(3, this)
const a = 3;
export default a;
我们直接在浏览器中打开,却发现报错:
这是因为直接用Chrome浏览器打开的本地文件,协议为file://
。在这个协议下使用ESModule中的import会被认为是跨域。因此我们在本地启动一个Node服务来提供HTTP协议,用来支持import。
// main.js
const http = require('http');
const fs = require('fs');http.createServer((req, res) => {let data = '';console.log(`request url: ${req.url}`);if(req.url === '/') {data = fs.readFileSync('./index.html');res.end(data);} else if(req.url === '/a.js') {data = fs.readFileSync('./a.js');// Chrome浏览器要求必须设置Content-type才能使用importres.setHeader('Content-type', 'text/javascript');res.end(data);}
}).listen(8000, () => {console.log('server start!');
});
然后在命令行执行node main.js
启动服务,再到浏览器输入localhost:8000
即可访问页面。查看浏览器Console输出:
1 Window {window: Window, self: Window, document: document, ...省略 }
3 undefined
3
2 undefined
2 Window {window: Window, self: Window, document: document, ...省略 }
// 严格模式下表现一致
可以看到,在浏览器中非ESModule,this指向window,而在ESModule内,this却是undefined,而globalThis依然指向window不变。
ESModule和Node.js
虽然在Node.js下默认使用CommonJS规范,但Node.js也是支持ESModule的,但需要手动开启,方式主要有两种:
- 文件后缀名为.mjs。
- 所在项目的package.json文件中包含
type: "module"
。
我们在Node.js中开启ESModule,看看this的指向问题。首先是入口文件a.mjs:
import a from "./a.mjs";
console.log(a);
console.log(2, this);
console.log(2, globalThis);
然后是被引入的文件b.mjs:
console.log(3, this)
const a = 3;
export default a;
最后命令行执行node a.mjs
,结果如下:
3 undefined
3
2 undefined
2 <ref *1> Object [global] { ...省略 }
// 严格模式下表现一致
我们构造的示例与浏览器中ESModule的示例基本一致,结果也是一致的,除了在Node.js中,globalThis依然指向global对象。
因此,不管是Node.js还是浏览器环境,在ESModule的模块上下文中,this的指向都是undefined。
场景小总结
通过上面对于全局上下文/模块上下文的实验结果,我们总结出了六种场景:
- 浏览器命令行
- 浏览器HTML中
- Node.js命令行
- CommonJS和Node.js
- ESModule和浏览器
- ESModule和Node.js
后续的实验都会考虑这六种场景,以及对应的严格模式。
普通函数上下文
在普通函数上下文,以及普通函数的嵌套函数中,this指向什么?在不同的环境和模块化规范下,this指向有什么区别呢?我们在不同的场景执行同一段代码,看看结果区别如何。
function fun1() {console.log(1, this);function fun2() {console.log(2, this);}fun2();return fun2;
}
const fun2 = fun1();
fun2();
浏览器命令行
首先看看在浏览器命令行执行的结果:
// 非严格模式
1 Window {window: Window, self: Window, document: document, ...省略 }
2 Window {window: Window, self: Window, document: document, ...省略 }
2 Window {window: Window, self: Window, document: document, ...省略 }
可以看到,浏览器命令行的不管是单层函数,还是嵌套函数,都是指向window,也就是globalThis。但是在严格模式下表现并不一致:
// 严格模式
1 undefined
2 undefined
2 undefined
在严格模式下,this值为undefined。这里先不解释,在专门的严格模式总结中描述。
浏览器HTML中
看一下在浏览器HTML中执行的结果:
// 非严格模式
1 Window {window: Window, self: Window, document: document, ...省略 }
2 Window {window: Window, self: Window, document: document, ...省略 }
2 Window {window: Window, self: Window, document: document, ...省略 }// 严格模式
1 undefined
2 undefined
2 undefined
浏览器HTML中与浏览器命令行的效果完全一致,而且是否严格模式的表现也不一致。
Node.js命令行
看一下在Node.js命令行中执行的结果:
// 非严格模式
1 <ref *1> Object [global] { ...省略 }
2 <ref *1> Object [global] { ...省略 }
2 <ref *1> Object [global] { ...省略 }// 严格模式
1 undefined
2 undefined
2 undefined
结果与在浏览器中类似,非严格模式指向globalThis,严格模式为undefined。
CommonJS和Node.js
看一下在Node.js中,使用CommonJS规范中执行的结果:
// 非严格模式
1 <ref *1> Object [global] { ...省略 }
2 <ref *1> Object [global] { ...省略 }
2 <ref *1> Object [global] { ...省略 }// 严格模式
1 undefined
2 undefined
2 undefined
结果还是与前面类似,非严格模式指向globalThis,严格模式为undefined。
ESModule和浏览器
看一下在浏览器中,使用ESModule规范中执行的结果:
// 非严格模式
1 undefined
2 undefined
2 undefined// 严格模式
1 undefined
2 undefined
2 undefined
ESModule规范中,this的值全都是undefined。
ESModule和Node.js
看一下在Node.js中,使用ESModule规范中执行的结果:
// 非严格模式
1 undefined
2 undefined
2 undefined// 严格模式
1 undefined
2 undefined
2 undefined
ESModule规范中,this的值全都是undefined。
小总结
通过上面不同环境下的实验,可以看到在普通函数上下文中,this指向globalThis;而在严格模式中,this值为undefined。至于ESModule规范,它是默认开启严格模式的,因此全是undefined。
构造函数上下文
构造函数是JavaScript原型链和类的重要概念,是生成实例对象的方法,构造函数中的this,指向的就是我们要生成的实例对象的this。这里我们来执行一段代码,试验一下。
let global1 = null;
function C1() {global1 = this;console.log(1, this);this.a = 1;console.log(1, this);
}
const c1 = new C1();
console.log(1, global1, c1, global1 === c1);let global2 = null;
class C2 {a = 2;constructor() {global2 = this;console.log(2, this);this.b = 2;console.log(2, this);}
}
const c2 = new C2();
console.log(2, global2, c2, global2 === c2);let global3 = null;
function C3() {global3 = this;this.a = 1;console.log(3, this);return {};
}
const c3 = new C3();
console.log(3, global3, c3, global3 === c3);
代码中分别尝试了三种情形,分别是传统构造函数,Class类的构造函数,以及在构造函数中返回另一个对象。来看一下输出:
1 C1 {}
1 C1 { a: 1 }
1 C1 { a: 1 } C1 { a: 1 } true
2 C2 { a: 2 }
2 C2 { a: 2, b: 2 }
2 C2 { a: 2, b: 2 } C2 { a: 2, b: 2 } true
3 C3 { a: 1 }
3 C3 { a: 1 } {} false
由于构造函数中的this是JavaScript语法规定的特性,因此不同的环境和是否严格模式表现都是一致的。可以看到,构造函数上下文中的this,确实是指向创建的实例对象,不管是传统构造函数还是Class类的构造函数。
如果构造函数返回另一个对象,那么这个返回的对象并不属于这个构造函数的实例对象;构造函数中的this也不指向返回对象,而是指向真正的实例对象。
对象或实例属性的函数上下文
函数属性
如果一个函数是某个对象或者实例的属性,那么这个函数的内部的this,指向的应该是这个对象/实例本身。这里我们来执行一段代码,试验一下。
function fun() {console.log(this);
}const a = {};
a.fun = fun;
a.fun();function C1() {this.fun = fun;
}
const b = new C1();
b.fun();
对于函数fun,我们尝试了两种情况,一种先创建对象,再作为对象属性赋值,另一种是在构造函数中作为属性赋值。我们来看一下输出:
{ fun: [Function: fun] }
C1 { fun: [Function: fun] }
由于对象或实例属性函数中的this是JavaScript语法规定的特性,因此不同的环境和是否严格模式表现都是一致的。可以看到分别输出了普通对象和C1的实例。
结合上面关于普通函数上下文的实验,可以看到其实函数本身是“不拥有自己的this的”,函数内部的this,完全看调用这个函数所在的环境和调用方式。如果是以对象属性的形式调用,this就指向对象本身;如果是直接调用,则指向globalThis或者严格模式下为undefined。
get和set
一个对象的取值函数getter和存值函数setter也都是函数,在其中也能获取到this。在这里面this指向什么呢?
const c1 = {get g1() {console.log("1 get", this);return 1;},set g1(val) {console.log("1 set", this);},
};
c1.g1 = c1.g1;const c2 = {};
Object.defineProperty(c2, "g2", {enumerable: true, // 对象可以枚举get() {console.log("2 get", this);return 1;},set(val) {console.log("2 set", this);},
});
c2.g2 = c2.g2;
这里又尝试了两种情况,一种是定义对象时直接提供getter和setter,一种是先定义对象,后面使用Object.defineProperty添加。我们看一下输出。
1 get { g1: [Getter/Setter] }
1 set { g1: [Getter/Setter] }
2 get { g2: [Getter/Setter] }
2 set { g2: [Getter/Setter] }
可以看到,分别输出了getter和setter所属的对象。由于getter和getter函数中的this是JavaScript语法规定的特性,因此不同的环境和是否严格模式表现都是一致的。
原型函数属性上下文
上面介绍了实例属性的函数中,this的指向问题。那我们再看一下如果这个函数属性是挂在原型上的,或者是原型上的set和get,this指向如何。
原型函数属性
首先来看看原型上的函数属性。
class C1 {a = 1;fun() {console.log(1, this);}
}
const c1 = new C1();
c1.fun();function C2() {this.a = 2;
}
C2.prototype = {fun() {console.log(2, this);},
};
const c2 = new C2();
c2.fun();function C3() {this.a = 3;
}
const c3 = new C3();
const c3proto = {fun() {console.log(3, this);},
};
c3.__proto__ = c3proto;
c3.fun();
c3proto.fun();
上面代码尝试了三种情况,分别是class关键字直接创建原型;在构造函数上提供原型;以及在实例上直接赋值原型。其中最后一种我们还尝试了在原型上直接调用函数,这相当于上一节对象的函数属性上下文。看一下输出结果:
// 浏览器输出
1 C1 { a: 1 }
2 C2 { a: 2 }
3 C3 { a: 3 }
3 { fun: [Function: fun] }// Node.js输出
1 C1 { a: 1 }
2 { a: 2 }
3 { a: 3 }
3 { fun: [Function: fun] }// 严格模式下表现一致
可以看到,在浏览器和Node.js中的输出有区别,是情况2和3的输出不同。浏览器中明确指出了这是C2和C3的实例,但是Node.js并没有。那是不是就说明Node.js中this指向的不是实例呢?
并不是的,查看代码发现所有的a都是实例属性,而不是原型属性。因此实际上无论浏览器或者Node.js,这三种场景this指向的都是实例对象,而不是原型对象。只不过原型更改后,Node.js对于console.log的输出处理不同。
再看最后一句输出:原型对象调用fun函数,函数中的this指向的是原型对象;实例调用fun函数,函数中的this指向的是实例对象。因此函数中this的指向和函数本身无关,而是和函数的“调用形式”有关。
原型的get和set
再来看看原型上的取值函数getter和存值函数setter。仿照上一节给出了代码:
class C1 {a = 1;get g1() {console.log("1 get", this);return 1;};set g1(val) {console.log("1 set", this);};
}
const c1 = new C1();
c1.g1 = c1.g1;function C2() {this.a = 2;
}
C2.prototype = {get g2() {console.log("2 get", this);return 1;},set g2(val) {console.log("2 set", this);},
};
const c2 = new C2();
c2.g2 = c2.g2;function C3() {this.a = 3;
}
const c3proto = {get g3() {console.log("3 get", this);return 1;},set g3(val) {console.log("3 set", this);},
};
const c3 = new C3();
c3.__proto__ = c3proto;
c3.g3 = c3.g3;
c3proto.g3 = c3proto.g3;
来看一下各个环境的输出结果:
// 浏览器输出
1 get C1 { a: 1 }
1 set C1 { a: 1 }
2 get C2 { a: 2 }
2 set C2 { a: 2 }
3 get C3 { a: 3 }
3 set C3 { a: 3 }
3 get { g3: [Getter/Setter] }
3 set { g3: [Getter/Setter] }// Node.js输出
1 get C1 { a: 1 }
1 set C1 { a: 1 }
2 get { a: 2 }
2 set { a: 2 }
3 get { a: 3 }
3 set { a: 3 }
3 get { g3: [Getter/Setter] }
3 set { g3: [Getter/Setter] }// 严格模式下表现一致
可以看到,浏览器和Node.js的输出是不同的,但不同点依然是Node.js对于console.log的输出处理不同,本质上指向的还是同一个对象。
然后我们看下输出结果,发现原型上的getter和setter与在原型上的函数属性一致,其中的this都指向调用它的对象。如果是实例调用就指向实例,原型直接调用就指向原型。
类的静态方法上下文
类的静态方法与getter,setter
与实例属性或者原型属性一样,类本身也有自己的静态属性。那么在类的静态方法中,this的指向如何呢?
class C1 {static a = 1;static fun() {console.log(this, this === C1);}static get g1() {console.log('get', this, this === C1);return 1;}static set g1(val) {console.log('set', this, this === C1);}
}
C1.fun();
C1.g1 = C1.g1;
我们尝试了类的静态属性方法,以及类的静态getter,setter。来看下输出结果:
[class C1] { a: 1 } true
get [class C1] { a: 1 } true
set [class C1] { a: 1 } true
可以看到,指向的都是类本身。没有指向实例(甚至这个例子都没有创建实例),也没有指向原型。注意这里没有包含传统的构造函数形式的类静态方法的例子。因为那种场景与直接对一个函数赋值一个属性没有任何区别。如果没有对这个函数使用new,甚至都看不出它是一个构造函数。因此,类的静态方法实际上就是类这个对象的方法而已。由于类的静态方法的this是JavaScript语法规定的特性,因此不同的环境和是否严格模式表现都是一致的。
类的静态块
类的静态属性中有一个特殊一点的叫做静态块,它是为了类静态属性的初始化逻辑而设置的。我们来看一下,在类的静态块中,this指向什么。
class C1 {static a = 1;static {console.log(this);}
}// 输出
// [class C1] { a: 1 }
可以看到,类的静态块中的this指向的就是类本身。由于类的静态块中的this是JavaScript语法规定的特性,因此不同的环境和是否严格模式表现都是一致的。
继承-构造函数上下文
了解实例和原型的上下文之后,我们再来了解一下与继承有关的场景中this的指向问题。首先来看一下继承中的构造函数上下文。
class C1 {constructor() {console.log(1, this);}a = 1;fun1() {}
}class C2 extends C1 {constructor() {// console.log(this) 这里会报错super();console.log(2, this);}b = 2;fun2() {}
}const c1 = new C1();
const c2 = new C2();/* 输出
1 C1 { a: 1 }
1 C2 { a: 1 }
2 C2 { a: 1, b: 2 }
*/
首先我们创建了一个父类C1的实例,此时this指向父类的实例。子类C2继承了C1,在创建实例的时候,调用super(),即执行父类的构造函数。在super()执行前,不可以使用this,会报错的。
可以看到父类的构造函数输出this中类名是子类C2,而不是父类本身C1。这是由继承的机制决定的,我们创建的是子类C2的实例,而不是父类C1的。但是由于父类构造函数需要先执行,此时子类的实例属性还没挂载到实例上,因此没有b: 2
。在子类的构造函数中,此时子类的实例属性就已经被挂载了。
由于继承构造函数中的this是JavaScript语法规定的特性,因此不同的环境和是否严格模式表现都是一致的。
继承-实例函数属性上下文
再来看看继承中的函数属性上下文,首先来看下继承中的实例属性上下文。我们构造下例子:
function fun() {console.log(this);
}
class C1 {constructor() {this.fun1 = fun;}a = 1;
}
class C2 extends C1 {constructor() {super();this.fun2 = fun;}b = 2;
}const c1 = new C1();
c1.fun1();
const c2 = new C2();
c2.fun1();
c2.fun2();/* 输出
C1 { a: 1, fun1: [Function: fun] }
C2 { a: 1, fun1: [Function: fun], b: 2, fun2: [Function: fun] }
C2 { a: 1, fun1: [Function: fun], b: 2, fun2: [Function: fun] }
*/
首先我们创建了类C1的实例,没有用到继承,输出也是类C1的实例。然后我们创建了类C2的实例,继承C1,调用C1中的实例属性,发现此时this是C2的实例,与类C2自己绑定的fun2函数输出一致。至于函数中的super.xx用法指向的是原型,并不是实例属性,因此这个场景无法使用。
由于继承实例函数属性上下文中的this是JavaScript语法规定的特性,因此不同的环境和是否严格模式表现都是一致的。
继承-原型函数属性上下文
看完了继承的实例函数属性,再看下继承的原型函数属性上下文。
函数属性
首先看一下普通的函数属性,我们构造下例子:
class C1 {a = 1;fun1() {console.log(1, this);}
}
class C2 extends C1 {b = 2;fun2() {console.log(2, this);}fun3() {super.fun1();}
}const c1 = new C1();
c1.fun1();
const c2 = new C2();
c2.fun1();
c2.fun2();
c2.fun3();/* 输出
1 C1 { a: 1 }
1 C2 { a: 1, b: 2 }
2 C2 { a: 1, b: 2 }
1 C2 { a: 1, b: 2 }
*/
可以看到与实例属性一致,在类C2的实例上调用的方法,其中继承的this指向的都是类C2的实例。注意最后一个输出,我们是使用super.xxx调用父类的原型方法,结果也是一致的。
由于继承原型函数属性上下文中的this是JavaScript语法规定的特性,因此不同的环境和是否严格模式表现都是一致的。
get和set
再来看看继承原型上的取值函数getter和存值函数setter。我们构造下例子:
class C1 {a = 1;get g1() {console.log("1 get", this);return 1;}set g1(val) {console.log("1 set", this);}
}
class C2 extends C1 {b = 2;get g2() {console.log("2 get", this);return 1;}set g2(val) {console.log("2 set", this);}fun() {super.g1 = super.g1;}
}const c1 = new C1();
c1.g1 = c1.g1;
const c2 = new C2();
c2.g1 = c2.g1;
c2.g2 = c2.g2;
c2.fun();/* 输出
1 get C1 { a: 1 }
1 set C1 { a: 1 }
1 get C2 { a: 1, b: 2 }
1 set C2 { a: 1, b: 2 }
2 get C2 { a: 1, b: 2 }
2 set C2 { a: 1, b: 2 }
1 get C2 { a: 1, b: 2 }
1 set C2 { a: 1, b: 2 }
*/
与普通函数属性一致,我们在在类C2的实例上使用getter和setter,其中继承的this指向也是类C2的实例。包括我们使用super直接调用类C1原型上的方法。由于继承原型函数属性上下文中的this是JavaScript语法规定的特性,因此不同的环境和是否严格模式表现都是一致的。
继承-类的静态方法上下文
看完了继承的实例属性和原型属性,再来看看它的静态方法上下文中,this的指向。
外部和静态方法中调用
class C1 {static a = 1;static fun1() {console.log(1, this);}
}class C2 extends C1 {static b = 1;static fun2() {console.log(2, this);}static fun3() {super.fun1();}
}C1.fun1();
C2.fun1();
C2.fun2();
C2.fun3();/* 输出
1 [class C1] { a: 1 }
1 [class C2 extends C1] { b: 1 }
2 [class C2 extends C1] { b: 1 }
1 [class C2 extends C1] { b: 1 }
*/
可以看到在子类中调用父类的静态方法,其中的this指向的是子类。这与在子类实例中调用父类(实例或原型)方法是一样的逻辑。最后我们尝试了以super的形式调用父类方法,this指向的也是子类。由于继承类的静态方法上下文中的this是JavaScript语法规定的特性,因此不同的环境和是否严格模式表现都是一致的。
静态块中调用
在子类中调用父类方法还有一个场景,就是静态块。我们举例试一下:
class C1 {static a = 1;static fun() {console.log('1 fun', this);}static {console.log(1, this);}
}class C2 extends C1 {static b = 1;static {console.log(2, this);this.fun();super.fun();}
}/* 输出
1 [class C1] { a: 1 }
2 [class C2 extends C1] { b: 1 }
1 fun [class C2 extends C1] { b: 1 }
1 fun [class C2 extends C1] { b: 1 }
*/
这里在静态块中尝试了使用this和super调用父类的静态方法,结果其中的this指向的都是子类。由于继承类的静态方法在静态块中调用上下文中的this是JavaScript语法规定的特性,因此不同的环境和是否严格模式表现都是一致的。
类的字段初始化器
字段初始化器指的是直接把this作为字段的值。我们分别看一下静态字段和实例字段的情况,以及在类继承中的情况。
实例和静态字段初始化器
虽然代码看起来“实例和静态字段初始化器在同一个作用域下”,但实际上并不是。
class C1 {static a = this;c = this;
}console.log(C1.a);
const c1 = new C1();
console.log(c1.c);/* 输出
<ref *1> [class C1] { a: [Circular *1] }
<ref *1> C1 { c: [Circular *1] }
*/
静态字段初始化器的this指向类本身,实例字段初始化器的this指向实例本身。
初始化器继承
class C1 {static a1 = this;c1 = this;
}
class C2 extends C1 {static a2 = this;c2 = this;
}console.log(C2.a1);
const c2 = new C2();
console.log(c2.c1);/* 输出
<ref *1> [class C1] { a1: [Circular *1] }
<ref *1> C2 { c1: [Circular *1], c2: [Circular *1] }
*/
在输出中可以看到,实例字段初始化器中的this指向的是继承后的实例(也只有这一个实例)。但是静态字段初始化器指向的确实父类,并不是子类。这是因为静态字段初始化器在创建类的时候就创建好了,继承只是继承了这个字段,并不会修改它的值。而实例字段初始化器是在创建实例的时候赋值的,因此可以指向继承后的实例。
call方法
上面我们介绍了很多函数场景下,this的指向问题,其中无论是普通对象,类还是原型,修改函数中this指向的方式实际上都是obj.fun()。那么有没有方法,不需要将函数附加到对象上即可绑定this?下面要介绍的三个方法,call,bind和aplly,都可以做到。首先介绍call方法。
使用 call()可以在调用函数时让this指向指定的值。我们看一下例子:
function fun(val) {console.log(val, this);
}
const obj = { a: 1 };
fun.call(obj, 2);/* 输出
2 { a: 1 }
*/
可以看到,使用call方法的第一个入参为要指向的this,后面的入参为函数本身原有的入参。call方法可以做到不修改对象绑定this,同时执行函数。
如果call方法的第一个入参为null和undefined:在非严格模式下,此时它的this指向和不使用call方法一致,即为globalThis,可以参考上面普通函数场景下的输出。如果为严格模式,那么还是指向call方法的第一个入参。
function fun() {console.log(this);
}
fun.call();
fun.call(undefined);
fun.call(null);/* 输出
// 非严格模式 Node.js
<ref *1> Object [global] { ...省略 }
<ref *1> Object [global] { ...省略 }
<ref *1> Object [global] { ...省略 }
// 非严格模式 浏览器
Window {window: Window, self: Window, document: document, ...省略 }
Window {window: Window, self: Window, document: document, ...省略 }
Window {window: Window, self: Window, document: document, ...省略 }
// 严格模式
undefined
undefined
null
*/
apply方法
apply方法与上面介绍的call方法非常类似,也是不需要将函数附加到对象上即可绑定this,执行函数。他俩的区别在于函数传参方式不同,apply是用数组的形式传参。我们看一下示例:
function fun(a, b) {console.log(a, b, this);
}const obj = { a: 1 };
fun.call(obj, 1, 2);
fun.apply(obj, [1, 2]);/* 输出
1 2 { a: 1 }
1 2 { a: 1 }
*/
可以看到,call方法会将除了第一个参数外的所有参数都传给原函数,而apply的第二个方法是一个数组,数组的内容就是第二个函数的参数列表。
如果apply方法的第一个入参为null和undefined,它的表现与call方法一致:在非严格模式下,此时它的this指向和不使用call方法一致,即为globalThis,可以参考上面普通函数场景下的输出。如果为严格模式,那么还是指向call方法的第一个入参。
function fun() {console.log(this);
}
fun.apply();
fun.apply(undefined);
fun.apply(null);/* 输出
// 非严格模式 Node.js
<ref *1> Object [global] { ...省略 }
<ref *1> Object [global] { ...省略 }
<ref *1> Object [global] { ...省略 }
// 非严格模式 浏览器
Window {window: Window, self: Window, document: document, ...省略 }
Window {window: Window, self: Window, document: document, ...省略 }
Window {window: Window, self: Window, document: document, ...省略 }
// 严格模式
undefined
undefined
null
*/
bind方法
call和apply方法虽然可以绑定this,但都是立即执行该函数。那么有没有方法可以绑定this,但是并不会立即执行该函数呢?这就是bind方法的效果。
bind方法说明
我们来举个例子看一下:
// 示例1
function fun1() {console.log(this);
}
const obj = { a: 1 };
const funa = fun1.bind(obj);
funa();
// 示例2
function fun2(a, b, c, d) {console.log(this, a, b, c, d);
}
const funb = fun2.bind(obj, 1,2);
funb(3,4);
const func = funb.bind(null, 5);
func(6);
/* 输出
{ a: 1 }
{ a: 1 } 1 2 3 4
{ a: 1 } 1 2 5 6
*/
示例1可以看到,使用bind方法绑定了obj作为函数的this,但是并没有直接执行,而是返回了新函数,可以延迟执行。而且比call和apply方法更高级的是,bind方法可以暂存入参,且可以多次调用,多次暂存。看示例2,funb绑定了this,且传了两个参数;在执行新函数的时候前两个就不需要传了,相当于起到暂存参数的作用。而且可以重复调用bind方法,多次暂存参数,例如func就二次调用了bind方法。不过多次调用时,绑定this就无效了,以第一次为准。
bind方法特殊场景
首先是常见的第一个入参为null和undefined,它的表现与call,apply方法一致:在非严格模式下,此时它的this指向和不使用call方法一致,即为globalThis,可以参考上面普通函数场景下的输出。如果为严格模式,那么还是指向bind方法的第一个入参。
function fun() {console.log(this);
}
fun.bind()();
fun.bind(undefined)();
fun.bind(null)();/* 输出
// 非严格模式 Node.js
<ref *1> Object [global] { ...省略 }
<ref *1> Object [global] { ...省略 }
<ref *1> Object [global] { ...省略 }
// 非严格模式 浏览器
Window {window: Window, self: Window, document: document, ...省略 }
Window {window: Window, self: Window, document: document, ...省略 }
Window {window: Window, self: Window, document: document, ...省略 }
// 严格模式
undefined
undefined
null
*/
然后是bind方法创建的函数作为构造函数,此时我们绑定的this是无效的:
function Fun1() {console.log(this, new.target === Fun1);
}
const obj = { a: 1 };
const fun1 = Fun1.bind(obj);
console.log(new fun1());function Fun2(a, b, c, d) {console.log(a, b, c, d);
}
const fun2 = Fun2.bind(obj, 1, 2);
new fun2(3, 4);/* 输出
Fun1 {} true
Fun1 {}
1 2 3 4
*/
可以看到,Fun1在作为构造函数使用时,绑定的obj是无效的,this此时还是构造函数生成的实例。虽然此时绑定this无效,但是暂存参数的功能还是有效的。例如fun2就暂存了Fun2的两个参数,然后作为构造函数使用时,成功读入了暂存的参数。
bind方法创建的函数虽然能作为构造函数,但不能作为父类被其他子类继承。bind方法可以绑定类,此时静态方法会失效,但继承的静态方法依旧生效。bind还有一些其它特性,不过并不是this的新情形,因此这里就不多介绍了。
原始值原型函数属性上下文
在JavaScript中,原始值(原始数据类型)是一种既非对象也无方法或属性的数据。所有原始值都是不可变的,即它们的值不能被修改。但是当在原始值上访问属性时,JavaScript自动将值装入包装对象中,并访问该对象上的属性。这里我们尝试执行原始值的原型函数属性,看看其中this的指向。
function fun() {console.log(this);
}
Number.prototype.fun = fun;
(1).fun();
console.log(new Number(1), 1);String.prototype.fun = fun;
('a').fun();
console.log(new String('a'), 'a');Boolean.prototype.fun = fun;
(false).fun();
console.log(new Boolean(false), false);/* 输出
// 非严格模式
[Number: 1]
[Number: 1] 1
[String: 'a']
[String: 'a'] a
[Boolean: false]
[Boolean: false] false
// 严格模式
1
[Number: 1] 1
a
[String: 'a'] a
false
[Boolean: false] false
*/
可以看到,我们先在原始值对应的原型上增加了一个函数属性,然后再在原始值上调用。结果在严格模式和非严格模式是不同的。严格模式下,this指向原始值;非严格模式下,this指向包装对象。
回调函数上下文
当一个函数作为回调函数传递时,this的值取决于如何调用回调。
自定义回调场景
如果是我们自己写的调用回调代码,那么this的值就由我们的调用方式决定。这里举例看下:
let globThis = null;
function funStore() {globThis = this;
}
funStore();function fun() {console.log(globThis === this);
}
fun();function call1(call) {call();
}
call1(fun);const obj = {call2: function (call) {call();}
};
obj.call2(fun);/* 输出
true
true
true
*/
首先我们执行了一个普通函数,记录了this,然后在两种回调中尝试this值与普通函数直接执行的区别。输出发现没有任何区别,this的指向实际是一样的,可以参考上面普通函数场景下的输出,且不同的环境和是否严格模式表现都普通函数场景下一致。
JavaScript提供的回调场景
JavaScript本身提供了很多回调函数的调用场景,比如迭代数组方法。其中大部分回调的this指向都与普通函数执行时一致,而且也可以传入可选的this值。我们看一下例子:
let globThis = null;
function funStore() {globThis = this;
}
funStore();function fun() {console.log(globThis === this);
}
fun();[1].forEach(fun);
[1].map(fun);function fun2() {console.log(this);
}
const obj = {a:1};
[1].forEach(fun2, obj);
[1].map(fun2, obj);/* 输出
true
true
true
{ a: 1 }
{ a: 1 }
*/
这里我们举例了数组的两个原型方法:forEach和map。在执行回调函数时,this的值与普通函数直接执行没有区别。但是这些接受回调的方法允许我们多传一个参数作为this指向,传了之后,回调中的this值就是我们指定的了。
部分特殊情形
还有一些特殊的可以接收回调的函数,此时回调函数中this指向是有意义的。我们举例看下:
function fun() {console.log(this);
}
JSON.parse("true", fun);
JSON.stringify({ a: 1 }, fun);/* 输出
{ '': true }
{ '': { a: 1 } }
*/
这里尝试了JSON.parse和JSON.stringify。可以看到,虽然我们没有传额外的参数,但是回调函数中的this却与普通函数直接执行是有区别的。这里的this指向的是当前解析的对象,具体可以查看相关文档了解。
箭头函数
箭头函数是一种简洁的函数表达式,且除了简洁之外,与普通函数相比在用法上有一些差异,比如这里要说的this。普通函数中this的指向是看调用函数的对象,但箭头函数的this指向是创建箭头函数时,作用域中this的指向。
箭头函数的this指向,在不同的情形下与普通函数的效果有较大区别。
不同的环境和模块化规范
首先我们试一下,在不同的环境和模块化规范下,直接执行函数。
function fun1() {console.log(1, this);
}
const fun2 = () => {console.log(2, this);
}
console.log(0, this);
fun1();
fun2();/* 输出
// 浏览器命令行 非严格模式
0 Window {window: Window, self: Window, document: document, ...省略 }
1 Window {window: Window, self: Window, document: document, ...省略 }
2 Window {window: Window, self: Window, document: document, ...省略 }// 浏览器命令行 严格模式
0 Window {window: Window, self: Window, document: document, ...省略 }
1 undefined
2 Window {window: Window, self: Window, document: document, ...省略 }// 浏览器HTML中 非严格模式
0 Window {window: Window, self: Window, document: document, ...省略 }
1 Window {window: Window, self: Window, document: document, ...省略 }
2 Window {window: Window, self: Window, document: document, ...省略 }// 浏览器HTML中 严格模式
0 Window {window: Window, self: Window, document: document, ...省略 }
1 undefined
2 Window {window: Window, self: Window, document: document, ...省略 }// Node.js命令行 非严格模式
0 <ref *1> Object [global] { ...省略 }
1 <ref *1> Object [global] { ...省略 }
2 <ref *1> Object [global] { ...省略 }// Node.js命令行 严格模式
0 <ref *1> Object [global] { ...省略 }
1 undefined
2 <ref *1> Object [global] { ...省略 }// CommonJS和Node.js 非严格模式
0 {}
1 <ref *1> Object [global] { ...省略 }
2 {}// CommonJS和Node.js 严格模式
0 {}
1 undefined
2 {}// ESModule和浏览器
0 undefined
1 undefined
2 undefined// ESModule和Node.js
0 undefined
1 undefined
2 undefined
*/
我们尝试了六种场景,以及对应的严格模式和非严格模式。可以看到不同的场景下,箭头函数的指向的this值是有区别的,而且很多场景下普通函数和箭头函数的this指向也不一样。
但是可以看到,箭头函数中this的指向和箭头函数外,直接输出this的指向是一样的。因此证明了箭头函数中的this指向,即是作用域中this的指向。
外部的箭头函数在对象和类中作为属性
如果一个在对象和类外面的箭头函数,被作为对象属性,类的静态属性或者实例属性来执行,其中的this指向又是如何呢?我们来看一下例子:
function fun1() {console.log(1, this);
}
const outerThis = this;
const fun2 = () => {console.log(this === outerThis);
};const obj = {fun1: fun1,fun2: fun2,
};
obj.fun1();
obj.fun2();class C1 {static sf1 = fun1;static sf2 = fun2;f1 = fun1;f2 = fun2;
}
C1.sf1();
C1.sf2();const c1 = new C1();
c1.f1();
c1.f2();/* 输出
1 { fun1: [Function: fun1], fun2: [Function: fun2] }
true
1 [class C1] { sf1: [Function: fun1], sf2: [Function: fun2] }
true
1 C1 { f1: [Function: fun1], f2: [Function: fun2] }
true
*/
在这里我们同时对比了普通函数和箭头函数的情况。可以看到普通函数在对象属性,类的静态属性或者实例属性执行时,this指向的值就是“拥有这个属性的对象”,我们在上面的章节讨论过。但是箭头函数的this却始终与外面直接输出的this指向是一致的,不管箭头函数被哪个类或者对象拥有。这也说明了箭头函数的this指向是创建箭头函数时,作用域中this的指向。
为什么这里要输出this === outerThis
而不是直接输出this的值呢?因为不同的环境和模块化规范,以及是否严格模式之下,这里的this值的情况太多了,这里重复输出太罗嗦了,因此统一用这个表达式代替。
类或对象内部的箭头函数
我们再来看一下,如果箭头函数定义在对象或者类的内部,此时箭头函数指向什么。
const outerThis = this;
const obj = {fun1() {console.log(this);},fun2: () => {console.log(this === outerThis);},
};
obj.fun1();
obj.fun2();class C1 {static staticThis = this;static sf1 = function () {console.log('sf1', this);};static sf2 = () => {console.log('sf2', this);};cthis = this;f1 = function () {console.log('f1', this);};f2 = () => {console.log('f2', this);};
}
console.log('staticThis', C1.staticThis);
C1.sf1();
C1.sf2();const c1 = new C1();
console.log('cthis', c1.cthis);
c1.f1();
c1.f2();/* 输出
{ fun1: [Function: fun1], fun2: [Function: fun2] }
true
staticThis [class C1] { staticThis: [Circular *1], sf1: [Function: sf1], sf2: [Function: sf2] }
sf1 [class C1] { staticThis: [Circular *1], sf1: [Function: sf1], sf2: [Function: sf2] }
sf2 [class C1] { staticThis: [Circular *1], sf1: [Function: sf1], sf2: [Function: sf2] }
cthis C1 { cthis: [Circular *1], f1: [Function: f1], f2: [Function: f2] }
f1 C1 { cthis: [Circular *1], f1: [Function: f1], f2: [Function: f2] }
f2 C1 { cthis: [Circular *1], f1: [Function: f1], f2: [Function: f2] }
*/
首先我们看一下对象属性,虽然箭头函数是在对象内部定义的,但this的指向还是与外部的this指向一致,因此可以认为对象没有形成自己的作用域。
再来看看类。类这里分为静态属性和实例属性,这里的作用域实际上就是字段初始化器的作用域。静态字段初始化器中this指向类C1,因此作用域中的this指向就是类C1,这也是静态字段的箭头函数的指向。实例属性是创建实例时被初始化的,初始化时的作用域与构造函数中一致,即是对象实例。因此这里箭头函数中this指向的也是实例。
类继承
我们再来看一下继承情形下,箭头函数中this的指向。
class C1 {static staticThis = this;static sf1 = function () {console.log("sf1", this);};static sf2 = () => {console.log("sf2", this);};cthis = this;f1 = function () {console.log("f1", this);};f2 = () => {console.log("f2", this);};
}class C2 extends C1 {a = 1;static b = 2;
}
console.log("staticThis", C2.staticThis);
C2.sf1();
C2.sf2();const c2 = new C2();
console.log("cthis", c2.cthis);
c2.f1();
c2.f2();/* 输出
staticThis [class C1] { staticThis: [Circular *1], sf1: [Function: sf1], sf2: [Function: sf2] }
sf1 [class C2 extends C1] { b: 2 }
sf2 [class C1] { staticThis: [Circular *1], sf1: [Function: sf1], sf2: [Function: sf2] }
cthis C1 { cthis: [Circular *1], f1: [Function: f1], f2: [Function: f2] }
f1 C1 { cthis: [Circular *1], f1: [Function: f1], f2: [Function: f2] }
f2 C1 { cthis: [Circular *1], f1: [Function: f1], f2: [Function: f2] }
*/
可以看到,这一节的例子实际上就是上一节类实例的例子增加了继承。首先看下静态字段初始化器,子类中输出是父类。可以认为父类中静态属性的作用域是父类,箭头函数中this的指向也是如此。但在普通函数的情形下,是子类调用的函数,因此普通函数中this的指向还是子类,符合普通函数this指向调用方的规则。
再看实例属性的继承。可以看到输出中不管是实例字段初始化器,普通函数和箭头函数,this指向的都是子类。因为在创建实例属性时,只有一个实例存在,这个实例就是子类的实例,因此这里的指向是一致的。
作用域中this变化的情形
假设箭头函数所处的作用域中的this在不同情形是变化的,那么箭头函数中this的指向会不会跟着变化呢?我们看一个例子。
function fun1() {console.log(this);const fun2 = () => {console.log(this);};fun2();
}
// fun1();const obj = {fun: fun1,
};
obj.fun();class C1 {static fun1 = fun1;fun = fun1;
}
C1.fun1();
const c1 = new C1();
c1.fun();/* 输出
{ fun: [Function: fun1] }
{ fun: [Function: fun1] }
[class C1] { fun1: [Function: fun1] }
[class C1] { fun1: [Function: fun1] }
C1 { fun: [Function: fun1] }
C1 { fun: [Function: fun1] }
*/
可以看到,我们的箭头函数是定义在一个普通函数内部的,而这个普通函数我们使用了各种方式执行,因此函数内部的this指向各不相同。而且每次是新创建的箭头函数。箭头函数来说,作用域中的this一直在变化,因此内部this指向也一直在变化。
第一个在最外层执行的场景我们注释了,因为输出随着不同的场景变化,前面已经介绍过可能的值,因此这里省略了。
eval函数中的this
eval函数会将传入的字符串当做JavaScript代码执行。如果传入的“代码”中包含this,那么这时的this指向如何呢?eval函数有两种模式,一种是直接调用,一种是间接调用。两者的指向并不一致。
直接调用
在直接调用时,eval内部的作用域上下文就是eval执行时所处的上下文。
eval("console.log(1, this)");
const obj = {fun() {eval("console.log(2, this)");}
}
obj.fun();eval(`class C1 { static a = this; b = this; }const c = new C1();console.log(3, C1.a);console.log(4, c.b);
`);/* 输出
// 第一行会随着外部作用域this的指向而变化,这里列举几种,不全列举了
1 {} // Node.js + Commonjs 非严格模式
1 undefined // Node.js + ESModule
1 <ref *1> Object [global] { ...省略 } // Node.js + 命令行 非严格模式
2 { fun: [Function: fun] }
3 <ref *1> [class C1] { a: [Circular *1] }
4 <ref *1> C1 { b: [Circular *1] }
*/
首先我们尝试了在最外层中调用eval输出this,发现确实会随着外部作用域this的指向而变化。然后尝试了在对象方法中执行eval,此时this指向为调用的对象。然后我们尝试了在eval内部创建作用域,输出内部的this,发现指向的规则与在外面一致。
间接调用
间接调用时,eval内部的作用域却是全局作用域,此时this的指向为globalThis。
const e = eval;
e("console.log(1, this)");const obj = {fun() {const ef = eval;ef("console.log(2, this)");},
};
obj.fun();/* 输出
// Node.js
1 <ref *1> Object [global] { ...省略 }
2 <ref *1> Object [global] { ...省略 }
// 浏览器
1 Window {window: Window, self: Window, document: document, ...省略 }
2 Window {window: Window, self: Window, document: document, ...省略 }
*/
我们把eval赋值给一个变量,然后间接调用它,发现无论eval执行时所处的作用域是什么,this的指向都是globalThis,即使是严格模式也不会变化。
事件处理器
JavaScript中的事件处理器可以绑定事件处理函数,这个处理函数是个回调函数,那么其中this的指向如何呢?
<html><div onclick="console.log(1, this)" onwheel="console.log(2, this)">点击1</div><div id="click2">点击2</div><body><script>const click2 = document.getElementById("click2");click2.addEventListener("click", function () {console.log(3, this);});click2.addEventListener("click", () => {console.log(4, this);});</script></body>
</html>/* 输出
1 <div οnclick="console.log(1, this)" οnwheel="console.log(2, this)">点击1</div>
2 <div οnclick="console.log(1, this)" οnwheel="console.log(2, this)">点击1</div>
3 <div id="click2">点击2</div>
// 不同环境输出不一致
4 Window {window: Window, self: Window, document: document, ...省略 } // 浏览器 非ESmodule
4 undefined // 浏览器 + ESmodule
*/
实际上,如果事件处理函数是一个普通函数,那么其中的this指向的是引发事件的对象。如果是一个箭头函数,那么this指向符合箭头函数的自己的规则,即外部作用域中this的指向。
严格模式总结
严格模式是JavaScript的一种模式,相对于非严格模式限制了部分语法和使用方式。严格模式与非严格模式对于this的处理是有区别的,我们上面描述不同场景下this的指向时,已经碰到过一些了。
严格模式的条件
这里先整理下使用严格模式的条件:
- 在ESModule中是自动使用严格模式的。
- 文件开头设置
"use strict";
,整个文件内会使用严格模式。 - 函数内开头设置
"use strict";
,整个函数内会使用严格模式。 - class中是自动使用严格模式的。
严格模式与非严格模式this的区别
事实上在非严格模式下,传递给函数的this会被强制转换为一个对象。例如在普通函数上下文中,因为this没有指向(undefined)所以将this替换成了globalThis。或者原始值作为this时,会被替换为这个值的包装对象。我们看一下具体的例子(这些例子都是前面讲过的):
function fun() {console.log(this);
}fun.call();
fun.call(undefined);
fun.call(null);fun.apply();
fun.apply(undefined);
fun.apply(null);fun.bind()();
fun.bind(undefined)();
fun.bind(null)();Number.prototype.fun = fun;
(1).fun();
String.prototype.fun = fun;
('a').fun();
Boolean.prototype.fun = fun;
(false).fun();
上面列举了普通函数,以及使用call, apply, bind执行函数,同时指定this是undefined或null的场景。在非严格模式下,this都指向globalThis。而严格模式下,输出则变成了this原始指向的值。另外还有原始值作为this访问属性方法的场景。在非严格模式下this指向值的包装对象;严格模式下this指向值本身。我们看下输出:
// 非严格模式输出
globalThis // 具体值由环境确定
globalThis // 具体值由环境确定
globalThis // 具体值由环境确定
globalThis // 具体值由环境确定
globalThis // 具体值由环境确定
globalThis // 具体值由环境确定
globalThis // 具体值由环境确定
globalThis // 具体值由环境确定
globalThis // 具体值由环境确定
globalThis // 具体值由环境确定
[Number: 1]
[String: 'a']
[Boolean: false]// 严格模式输出
undefined
undefined
undefined
null
undefined
undefined
null
undefined
undefined
null
1
a
false
严格模式内代码在外部调用
严格模式除了整个文件生效之外,也可以只在class中生效,或者只在某个函数内生效。那么严格模式外执行严格模式之内的代码,是否也具有严格模式的性质么? 我们来试一下。
function fun() {console.log(0, this);
}
fun();class C1 {fun1() {console.log(1, this);}fun2() {return function () {console.log(2, this);};}
}
const c1 = new C1();
const { fun1 } = c1;
fun1();const fun2 = c1.fun2();
fun2();let fun3;
function funStrict() {"use strict";fun3 = function () {console.log(3, this);};
}
funStrict();
fun3();/* 输出
0 globalThis // 具体值由环境确定
1 undefined
2 undefined
3 undefined
*/
首先我们在外面输出了一个普通函数内部的this,结果是globalThis。然后我们将类和严格模式函数中的代码(实际为嵌套的函数)放到严格模式外执行,发现输出的this都是undefined,这说明严格模式依然是生效的。
特殊或组合场景讨论
这里我们讨论下部分容易搞错的特殊场景,又或者上面介绍的一些场景的组合情形下,this指向的值。
对象字面量不创建作用域
对象字面量不创建作用域,因此更不会更改外部的this。
const obj = {a: this,
}
这里的this和对象字面量外部作用域中的this实际是一致的。
箭头函数使用call/bind/apply方法
上面描述过,箭头函数的this指向是创建箭头函数时,外部作用域中this的指向。虽然call/bind/apply方法可以改变函数内的this指向,但无法改变箭头函数内的this指向。我们看一下例子:
const obj = {a:1};
const fun1 = function () {console.log(1, this);
};
const fun2 = () => {console.log(2, this);
}fun1();
fun2();fun1.call(obj);
fun2.call(obj);fun1.apply(obj);
fun2.apply(obj);fun1.bind(obj)();
fun2.bind(obj)();/* 输出 以 Node.js + CommonJs + 非严格模式为例
1 <ref *1> Object [global] { ...省略 }
2 {}
1 { a: 1 }
2 {}
1 { a: 1 }
2 {}
1 { a: 1 }
2 {}
*/
这里以 Node.js + CommonJs + 非严格模式 为例,此时外部作用域中的this指向为{}。可以看到普通函数的this指向受到了call/bind/apply方法的影响,但是箭头函数始终指向外部作用域中的this。因此箭头函数的this指向不会受到call/bind/apply方法的影响。
嵌套普通函数中的this并不会继承
嵌套普通函数中的this指向并不会继承外部作用域。我们看下例子:
const obj = {f1: function () {console.log(1, this);function f2() {console.log(2, this);}f2();},
};
obj.f1();class C1 {static f3() {console.log(3, this);function f4() {console.log(4, this);}f4();}
}
C1.f3();/* 输出 以 Node.js + CommonJs + 非严格模式为例
1 { f1: [Function: f1] }
2 <ref *1> Object [global] { ...省略 }
3 [class C1]
4 undefined
*/
我们尝试了普通对象方法和类的静态方法两种作用域,再其中创建了普通函数并执行,发现指向的this和当前作用域都无关,其中普通对象方法this指向了globalThis,class因为自动使用严格模式,所以输出undefined。
总结
在整理和撰写这篇文章之前,我一直认为this是简单的几条规则就可以概括的内容,但写完之后我发现,this的规则比想象的要复杂一些。并不是说难,而是情况很多。包含类与对象,箭头函数,JS运行环境,模块化,构造函数,继承,回调,箭头函数,call/bind/apply,eval,严格模式等很多主题。可以说贯穿了半个JavaScript语法的领域。
阮一峰写过一篇JavaScript的this原理里面介绍了为什么JavaScript语言有this的设计,实际上这和内存中存储的数据结构有关系。但是JavaScript中的this搞出了如此多的场景和情形,实际上并不好用,也不容易理解。(只是容易面试出题了)
参考
- MDN globalThis
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/globalThis - Javascript 中 let 声明的全局变量不在 window 上
https://juejin.cn/post/7064043813534171149 - MDN 严格模式
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode - MDN this
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this - JS 中在严格模式下 this 的指向 超详细
https://www.cnblogs.com/cyy22321-blog/p/16672057.html - 「万字进阶」深入浅出 Commonjs 和 Es Module
https://juejin.cn/post/6994224541312483336 - 《ECMAScript6入门教程》Class 的基本语法
https://es6.ruanyifeng.com/#docs/class - 《ECMAScript6入门教程》Class 的继承
https://es6.ruanyifeng.com/#docs/class-extends - MDN Function.prototype.call()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call - MDN Function.prototype.bind()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind - MDN Function.prototype.apply()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply - MDN JSON.parse()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse - MDN JSON.stringify()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify - MDN 原始值
https://developer.mozilla.org/zh-CN/docs/Glossary/Primitive - MDN 箭头函数表达式
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions - MDN eval()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/eval - JavaScript的this原理 阮一峰
https://www.ruanyifeng.com/blog/2018/06/javascript-this.html
相关文章:
JavaScript中的this, 究竟指向什么?
在JavaScript代码的不同位置中,this所指向的数据是不一样的。比如大部分同学都知道,在对象的函数属性方法中,this指向对象本身;在构造函数中,this指向要生成的新对象。事实上,this指向的逻辑不止这几种&…...
JavaWeb学习(3)(Servlet详细、Servlet的三种实现方式(面试)、Servlet的生命周期、传统web.xml配置Servlet(了解))
目录 一、Servlet详细。 (1)基本介绍。 (2)基本作用。 1、接收客户端请求数据。 2、处理请求。 3、完成响应结果。 二、Servlet的三种实现方式。 (1)实现javax.servlet.Servlet接口。 1、基本介绍。 2、代码…...
【图像去雾数据集】URHI数据集介绍
URHI数据集对应论文:RESIDE: A Benchmark for Single Image Dehazing(2017) URHI数据集下载链接:https://sites.google.com/site/boyilics/website-builder/reside 为便于下载,将上述官方提供的链接中百度云链接粘贴如…...
Playwright中Page类的方法
导航和页面操作 goto(url: str, **kwargs: Any): 导航到一个URL。 reload(**kwargs: Any): 重新加载当前页面。 go_back(**kwargs: Any): 导航到会话历史记录中的前一个页面。 go_forward(**kwargs: Any): 导航到会话历史记录中的下一个页面。 set_default_navigation_tim…...
算力介绍与解析
算力(Computing Power)是指计算机系统在单位时间内处理数据和执行计算任务的能力。算力是衡量计算机性能的重要指标,直接影响计算任务的速度和效率。 算力的分类和单位 a. 基础算力:以CPU的计算能力为主。适用于各个领域的计算。…...
CentOS 上如何查看 SSH 服务使用的端口号?
我们知道,linux操作系统中的SSH默认情况下,端口是使用22,但是有些线上服务器并不是使用的默认端口,那么这个时候,我们应该如何快速知道SSH使用的哪个端口呢? 1、通过配置文件查看 cat /etc/ssh/sshd_confi…...
每日算法Day03
1.19.删除链表的倒数第N个节点 算法链接: 19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode) 类型: 链表 难度: 中等 思路:采用双指针法,控制两个指针之间的距离为n个节点 易错点:返回节点的确定和头节点的处理&…...
【漏洞复现】Apache Solr 身份认证绕过导致任意文件读取漏洞复现(CVE-2024-45216)
🏘️个人主页: 点燃银河尽头的篝火(●’◡’●) 如果文章有帮到你的话记得点赞👍+收藏💗支持一下哦 【漏洞复现】Apache Solr 身份认证绕过导致任意文件读取漏洞复现(CVE-2024-45216) 一、漏洞概述1.1漏洞简介1.2组件描述1.3漏洞描述二、漏洞复现2.1 应用协议2.2 环境…...
若依将数据库更改为SQLite
文章目录 1. 添加依赖项2. 更新配置文件 application-druid.yml2.1. 配置数据源2.2. 配置连接验证 3. 更新 MybatisPlusConfig4. 解决 mapper 中使用 sysdate() 的问题4.1. 修改 BaseEntity4.2. 修改 Mapper 5. 更新 YML 配置 正文开始: 前提条件:在您的…...
ubuntu远程桌面开启opengl渲染权限
背景 最近用windows的【远程桌面连接】登录ubuntu后(xrdp协议),发现gl环境是集显的,但是本地登录ubuntu桌面后是独显(英伟达),想要在远程桌面上也用独显渲染环境。 一、查看是独显还是集显环境…...
Scala的泛型
需求:定义一个名为getMiddleEle 的方法用它来获取当前的列表的中间位置的值中间位置的下标 长度/2目标:getMiddleEle(List(1,2,3,4,5)) > 5/2 2 > 下标为2的元素是:3 getMiddleEle(List(1,2,3,4)) > 4/2 2 > 下标为2的元素是:3格式如下: 定义一个函数的格式:def…...
每隔一秒单片机向电脑发送一个16进制递增数据
SCON0x50 SM00 SM11(工作方式为方式一) REN1允许单片机从电脑接收数据 TB8 RB8 SM2是方式2和方式3直接配置为0 TI为发送中断请求标志位 由硬件配置为1 必须由 软件复位为0,RI为接收中断请求标志位,同理TI UART.c #include &l…...
轻量级日志管理平台:Grafana Loki搭建及应用(详细篇)
前言 Grafana Loki是Grafana Lab团队提供的一个水平可扩展、高可用性、多租户的日志聚合系统,与其他日志系统不同的是,Loki最初设计的理念是为了为日志建立标签索引,而非将原日志内容进行索引。 现在目前成熟的方案基本上都是:L…...
React和Vue.js的相似性和差异性是什么?
React和Vue.js都是现代前端开发中广泛使用的JavaScript框架,它们都旨在提高开发效率和组件化开发。以下是他们的一些相似性和差异性: 相似性 组件化:两者都支持组件化开发,允许开发者将UI拆分成独立的、可复用的组件。虚拟DOM&a…...
跨域 Cookie 共享
跨域请求经常遇到需要携带 cookie 的场景,为了确保跨域请求能够携带用户的认证信息或其他状态,浏览器提供了 withCredentials 这个属性。 如何在 Axios 中使用 withCredentials 为了在跨域请求中携带 cookie,需要在 Axios 配置中设置 withCr…...
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之计数器与累加器(一)
学习背景: 在现实生活中一些需要计数的场景下我们会用到计数器,如空姐手里记录乘客的计数器,跳绳手柄上的计数器等。累加器是累加器求和,以得到最后的结果。计数器和累加器它们虽然是基础知识,但是应用广泛࿰…...
红黑树(Red-Black Tree)
一、概念 红黑树(Red Black Tree)是一种自平衡的二叉搜索树,通过添加颜色信息来确保在进行插入和删除操作时,树的高度保持在对数级别,从而保证了查找、插入和删除操作的时间复杂度为 O(log n)。这种树可以很好地解决普…...
火电厂可视化助力提升运维效率
图扑智慧火电厂综合管理平台实现对火电厂关键设备和系统的实时监控和数据分析。图扑可视化不仅优化了运维流程,还增强了安全管理,有效提升了电厂整体运营效率。...
application.yml 和 bootstrap.yml
在 Spring Boot 中,application.yml 和 bootstrap.yml 都是用来配置应用程序的属性文件,通常用于环境配置、服务配置等。但是,它们有一些不同的用途和加载顺序。以下是它们之间的主要区别: 1. application.yml: 主要…...
电子应用设计方案-49:智能拖把系统方案设计
智能拖把系统方案设计 一、引言 随着人们生活水平的提高和对清洁效率的追求,智能拖把作为一种创新的清洁工具应运而生。本方案旨在设计一款功能强大、操作便捷、清洁效果出色的智能拖把系统。 二、系统概述 1. 系统目标 - 实现自动清洁地面,减轻用户劳…...
Model Context Protocol 精选资源列表
Model Context Protocol 精选资源列表 Model Context Protocol 精选资源列表什么是MCP?教程社区说明服务器实现📂 浏览器自动化☁️ 云平台💬 社交👤 数据平台🗄️ 数据库💻 开发者工具🧮 数据科…...
Windows 11 12 月补丁星期二修复了 72 个漏洞和一个零日漏洞
微软于 2024 年 12 月为 Windows 11 发布的补丁星期二修复了其产品生态系统中的 72 个漏洞,包括 Windows 通用日志文件系统驱动程序中一个被积极利用的零日漏洞。 这个严重漏洞可以通过基于堆的缓冲区溢出授予攻击者系统权限,使其成为此版本中优先级最高…...
Python毕业设计选题:基于Hadoop 的国产电影数据分析与可视化_django+spider
开发语言:Python框架:djangoPython版本:python3.7.7数据库:mysql 5.7数据库工具:Navicat11开发软件:PyCharm 系统展示 管理员登录 管理员功能界面 用户管理 免费电影管理 在线论坛 留言反馈 看板展示 系统…...
蓝桥杯我来了
最近蓝桥杯报名快要截止了,我们学校开始收费了,我们学校没有校赛,一旦报名缴费就是省赛,虽然一早就在官网上报名了,但是一直在纠结,和家人沟通,和朋友交流,其实只是想寻求外界的支持…...
大模型qiming面试内容整理-编码能力评估
编码能力评估是大模型相关岗位面试中非常关键的一环,面试官通常希望通过这个环节了解候选人对编程语言、算法与数据结构的掌握情况,以及其在实践中解决实际问题的能力。以下是编码能力评估的常见内容和类型,特别是针对机器学习、大模型和深度学习方向: 编程语言熟练度 ● P…...
Vivado ILA数据导出MATLAB分析
目录 ILA数据导出 分析方式一 分析方式二 有时候在系统调试时,数据在VIVADO窗口获取的信息有限,可结合MATLAB对已捕获的数据进行分析处理 ILA数据导出 选择信号,单击右键后,会有export ILA DATA选项,将其保存成CS…...
Linux内核 -- 字符设备之read write poll基本实现
Linux字符设备:read、write和poll函数实现及完整代码 1. read函数 原型 ssize_t read(struct file *file, char __user *buf, size_t count, loff_t *pos);实现步骤 检查用户缓冲区:使用copy_to_user将数据从内核空间复制到用户空间。返回已读取的字…...
linux部署ansible自动化运维
ansible自动化运维 1,编写ansible的仓库(比赛已经安装,无需关注) 1、虚拟机右击---设置---添加---CD/DVD驱动器---完成---确定 2、将ansible.iso的光盘连接上(右下角呈绿色状态) 3、查看光盘挂载信息 df -h…...
springboot421社区医疗服务可视化系统(论文+源码)_kaic
摘 要 传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装社区医疗服务可视化系统软件来发挥其高效地信息处理的作用…...
vue渲染时闪光白屏问题怎么解决(笔记)
Vue渲染时出现闪光白屏问题,可能是因为页面内容需要较长时间才能加载完成,而在加载期间,页面会显示白屏或者空白内容,给用户体验带来负面影响。 解决方法: 为标签绑定 v-cloak 在样式中写 v-clock {displaÿ…...
duxapp 2024-12-09 更新 PullView可以弹出到中间,优化CLI使用体验
UI库 修复 Button 禁用状态失效的问题Modal 组件即将停用,请使用 PullView 基础库 PullView side 新增 center 指定弹出到屏幕中间PullView 新增 duration 属性,指定动画时长新增 useBackHandler hook 用来阻止安卓端点击返回键 RN端 修复 windows …...
vue调试插件vue Devtools下载安装
Vue.js Devtools_6.6.3_Chrome插件下载_极简插件 进图官网点击推荐下载下载该调试工具 解压缩找到扩展程序开启开发者模式 将解压缩的文件拖拽到该页面空白处,点击添加扩展程序 点击详情允许访问文件网址 页面右键检查可以发现已经成功添加该页面调试插件...
.net core使用AutoMapper
AutoMapper 是一个用于 .NET 平台的对象映射工具,它简化了不同对象类型之间的转换过程。在软件开发中,尤其是在分层架构的应用程序里,常常需要在不同的对象模型之间进行数据传递,例如从数据库实体到视图模型、DTO(数据…...
HTTP头注入
注入类漏洞经久不衰,多年保持在owasp Top 10的首位。今天就聊聊那些被人遗忘的http头注入。用简单的实际代码进行演示,让每个人更深刻的去认识该漏洞。 3|0HOST注入 在以往http1.0中并没有host字段,但是在http1.1中增加了host字段ÿ…...
110.【C语言】编写命令行程序(1)
目录 1.前置知识 "命令"的含义 运行C语言程序 2.介绍 main函数的参数 实验1 执行结果 实验2 执行结果 修改代码 实验3 分析 方法:遍历数组argv[]中的所有参数 执行结果 修改代码 执行结果 1.前置知识 "命令"的含义 WINR输入cmd,在cmd窗口下…...
k8s 之 StatefulSet
深入理解StatefulSet(一):拓扑状态 k8s有状态与无状态的区别 无状态服务:deployment Deployment被设计用来管理无状态服务的pod,每个pod完全一致.什么意思呢? 无状态服务内的多个Pod创建的顺序是没有顺序的. 无状态服务内的多…...
PTA 7-2 从身份证号码中提取出出生年月
分数 12 作者 崔孝凤 单位 山东理工大学 输入一个18位的身份证号码,提取出年份和月份并输出,请判断输入的号码是否是18位,如果不是请提示"Invalid data,input again!",并重新输入新的号码。 输入格式: 输入一个18位…...
Plugin - 插件开发04_Spring Boot中的SPI机制与Spring Factories实现
文章目录 Pre方案概览使用插件的好处Spring Boot中的SPI机制与Spring Factories实现1. Spring Boot中的SPI机制Spring Factories文件 2. Spring Factories实现原理3. Code3.1 定义一个服务接口3.2 定义 实现类3.3 配置 spring.factories 文件3.4 创建一个Controller来加载插件3…...
GUNS搭建
一、准备工作 源码下载: 链接: https://pan.baidu.com/s/1bJZzAzGJRt-NxtIQ82KlBw 提取码: criq 官方文档 二、导入代码 1、导入后端IDE 导入完成需要,需要修改yml文件中的数据库配置,改成自己的。 2、导入前端IDE 我是用npm安装的yarn npm…...
路径规划之启发式算法之十四:蜘蛛蜂优化算法(Spider Wasp Optimizer, SWO)
蜘蛛蜂优化算法(Spider Wasp Optimizer, SWO)是一种受自然界中蜘蛛蜂行为启发的元启发式智能优化算法。由Mohamed Abdel-Basset等人于2023年提出,算法模拟了雌性蜘蛛蜂的狩猎、筑巢和交配行为,具有独特的更新策略,适用于具有不同探索和开发需求的广泛优化问题。 一、算法背…...
OpenGL ES详解——多个纹理实现混叠显示
目录 一、获取图片纹理数据 二、着色器编写 1. 顶点着色器 2. 片元着色器 三、绑定和绘制纹理 1. 绑定纹理 2. 绘制纹理 四、源码下载 一、获取图片纹理数据 获取图片纹理数据代码如下: //获取图片1纹理数据 mTextureId loadTexture(mContext, R.mipmap.…...
Java多线程与线程池技术详解(八)
《星游记》 “如果只有傻瓜才相信梦想,那么就叫我大傻瓜吧!” 《一人之下》 “想走的路不好走,想做人不好做,都说是身不由己,不是废话么。己不由心,身又岂能由己!” 目录 上一篇博客习题讲解 编…...
2024年12月11日Github流行趋势
项目名称:maigret 项目维护者:soxoj, kustermariocoding, dependabot, fen0s, cyb3rk0tik项目介绍:通过用户名从数千个站点收集个人档案信息的工具。项目star数:12,055项目fork数:870 项目名称:uv 项目维护…...
ThinkPHP 6.0 PHP新手教程
1、系统配置文件 下面系统自带的配置文件列表及其作用: 配置文件名描述app.php应用配置cache.php缓存配置console.php控制台配置cookie.phpCookie配置database.php数据库配置filesystem.php磁盘配置lang.php多语言配置log.php日志配置middleware.php中间件配置rou…...
【Excel学习记录】02-单元格格式设置
1.单元格格式工具美化表格 单元格格式位置 选中单元格,右键→设置单元格格式 合并居中 跨越合并 字体类型、大小、颜色、填充底纹、边框 斜线 软回车:alt enter 格式刷 2.单元格数字格式 格式不影响数值,只是展示形式 日期本质也是数…...
Paimon Tag和Branch创建文件存储过程
结论: 如果data-file被引用则不会被压缩,压缩仅针对未被引用的文件,创建tag时候根据当前快照进行创建 1、实际表和Manifest的内容 查看tag的内容 select * from table$tags;或者直接查看tag ossutil cat oss://test-dataware/warehouse/te…...
HCIA笔记8--DHCP、Telnet协议
1. DHCP介绍 对于主机的网络进行手动配置,在小规模的网络中还是可以运作的,但大规模网络是无力应对的。因此就有了DHCP协议来自动管理主机网络的配置。 DHCP(Dynamic Host Configuration Protocol): 动态主机配置协议,主要需要配置的参数有…...
Tableau数据可视化与仪表盘搭建
1.Tableau介绍 可视化功能 数据赋能 数据赋能就是将我们的数据看板发布到我们的线上去 这里的IP地址是业务部门可以通过账号密码登入的 我们也可以根据需要下载,选中并点击下载即可 下载下来之后,自己就能根据数据进行自定义的分析 也可以下载图片 还有…...
Django结合websocket实现分组的多人聊天
其他地方和上一篇大致相同,上一篇地址点击进入, 改动点1:在setting.py中最后再添加如下配置: # 多人聊天 CHANNEL_LAYERS {"default":{"BACKEND": "channels.layers.InMemoryChannelLayer"} }因此完整的se…...
自动驾驶控制与规划——Project 1: 车辆纵向控制
目录 零、任务介绍一、环境配置1.1 CARLA的配置1.2 Docker Ubuntu 20.04 ROS2 Foxy的配置 二、算法2.1 定速巡航2.2 自适应巡航2.3 离散PID控制 三、代码实现3.1 代码补全3.2仿真验证 零、任务介绍 课程主页 配置Carla仿真器配置carla-ros-bridge补全src\ros-bridge\carla_s…...