- JavaScript引擎与运行时入门小记:在哪些环境下可以运行JavaScript
- JavaScript学习笔记(一):理解概念,一等函数、编程范式、原型、单线程
继续梳理JavaScript基本概念。
有两个东西,表述起来像绕口令:
- global object:全局对象。全局环境的顶层对象。JavaScript中,始终会定义一个全局对象。(globalThis)
- global objects:全局对象们。涵盖了所有在全局环境中可用的内置对象。(standard built-in objects)。
全局对象
在 JavaScript 中,总是会存在一个全局对象。 在 Web 浏览器中,当使用使用 var 关键字定义全局变量时,它们将被创建为该全局对象的成员(在 Node.js 中,情况有所不同)。 例如:
- 在 Web 浏览器中,绝大多数 JavaScript 代码不作为后台任务使用。它们将 Window 作为其全局对象。
- 后台 Worker 中运行的代码有一个 WorkerGlobalScope 对象作为其全局对象。
- 在 Node.js 下运行的脚本有一个名为 global 的对象作为其全局对象。
先看个例子(在firefox、chrome、edge下可以运行,在node.js下不适用):
function myAdd(a, b) {
return a + b
}
var myAdd2 = function (a, b) {
return a + b
}
let myAdd3 = (a, b) => a + b
globalThis.myAdd4 = (a, b) => a + b
console.log(myAdd(1, 2), myAdd2(1, 2), myAdd3(1, 2), myAdd4(1, 2))
console.log(globalThis.myAdd(1, 2), globalThis.myAdd2(1, 2), globalThis.myAdd4(1, 2))
注意:
- globalThis 的属性自动是 全局变量(例子中myAdd4)。
- 通过var定义的变量,都会作为 globalThis 的属性存在(注意myAdd和myAdd2)。
- 这个也仅适用于浏览器,nodejs行为不一样,见后面var部分。
globalThis
globalThis 是在ES11(ES2020)中引入的,用于访问 全局对象。在Qt中,截至6.5,QJSEngine尚不支持这个东西。
在globalThis引入之前,
- 浏览器中 使用 window、self、frames 指代全局对象。Web workers中使用 self 指代全局对象
- Node.js中使用 global 指代全局对象
全局对象,有属性指向它自己,比如
在浏览器下:
console.log(globalThis === globalThis.globalThis); // true
console.log(window === window.window); // true
console.log(self === self.self); // true
console.log(frames === frames.frames); // true
在Nodejs下:
console.log(globalThis === globalThis.globalThis); // true
console.log(global === globalThis.global); // true
在QJSEngine下:
在QJSEngine(Qt6.6)下,不支持globalThis,也没有其他self、window、global等变通方式。
但是我可以通过让函数返回this来间接获取它:
#include <QCoreApplication>
#include <QJSEngine>
const char *js = R"js(
var a = "1+1=10";
let myGlobal = (function(){return this})();
console.log(myGlobal.Math === Math);
console.log(myGlobal.app === app);
console.log(myGlobal.a === a);
console.log(this.Math === Math);
console.log(this.a === a);
)js";
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QJSEngine engine;
engine.installExtensions(QJSEngine::ConsoleExtension);
engine.setObjectOwnership(&app, QJSEngine::CppOwnership);
engine.globalObject().setProperty("app", engine.newQObject(&app));
engine.evaluate(js);
return 0;
}
输出结果
js: true
js: true
js: true
js: true
js: true
var 与全局对象
Nodejs手册提到:
In browsers, the top-level scope has traditionally been the global scope. This means that
var something
will define a new global variable, except within ECMAScript modules. In Node.js, this is different. The top-level scope is not the global scope;var something
inside a Node.js module will be local to that module, regardless of whether it is a CommonJS module or an ECMAScript module.
在浏览器中,顶级作用域传统上就是全局作用域,这意味着var会定义全局变量。而在Node.js中,顶级作用域不是全局作用域,var定义的变量属于当前模块。
node.js
console.log(this === globalThis) // false
QJSEngine
在Qt下,即使没有globalThis,但是我们也可以在C++端轻松验证——var定义的变量是globalObject的属性:
QJSEngine engine;
engine.evaluate(R"(var a = "1+1=10")");
qDebug() << engine.globalObject().property("a").toString(); // 1+1=10
this 用法
不同于C++中的this和python中的self,javascript中这个东西似乎很乱。MDN中说:在非严格模式下,this
总是指向一个对象,在严格模式下可以是任意值。
this的值取决于函数的调用方式和上下文环境,状况很复杂。简单几个情况如下:
- 在对象的方法中,this指向调用该方法的对象
- 在构造函数中,this指向正在构造的对象
- 类静态方法中,this指向类
- 在箭头函数中,this指向定义箭头函数时的外层对象,不会随着调用方式改变
- 在事件处理函数中,this指向触发事件的元素??
- 在call,apply或bind方法中,this可以被显式地绑定到任意对象
- 在全局作用域或普通函数中,this不一定指向全局对象
对象方法
和C++、Python有点类似。这个比较直观,this指向调用该方法的对象:
const test = {
name: '1+1=10',
func: function () {
return this.name;
},
};
console.log(test.func()); // 1+1=10
const test2 = {
name: '1+1=2',
};
test2.func = test.func;
console.log(test2.func()); // 1+1=2
在函数内部,this
的值取决于函数如何被调用。同一个函数,根据其调用的方式,this
的值是不同的:
function func() {
return this.name
}
const obj1 = {name: 'obj1', func: func}
const obj2 = {name: 'obj2', func: func}
console.log(obj1.func()) // obj1
console.log(obj2.func()) // obj2
这这个基础上,对原型使用,也是符合预期:
const obj3 = {name: 'obj3', __proto__: obj1}
console.log(obj3.func()) // obj3
构造函数
这个也容易理解。当一个函数被用作构造函数(使用 new
关键字)时,无论构造函数是在哪个对象上被访问的,其 this
都会被绑定到正在构造的新对象上。
function Person(name, age) {
this.name = name;
this.age = age;
}
const person1 = new Person('1+1=10', 41);
console.log(person1.name); // 1+1=10
console.log(person1.age); // 41
类构造函数与此一样,this指向类实例:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const person1 = new Person('1+1=10', 41);
console.log(person1.name); // 1+1=10
console.log(person1.age); // 41
类静态方法
这个和python也有些类似
class MyClass {
static staticMethod() {
return this;
}
}
console.log(MyClass.staticMethod()); // class MyClass
箭头函数
在箭头函数中,this
保留了上下文的 this
值。
这个东西,挺反知觉的:
const obj = {
name: '1+1=10',
sayHello: () => {
console.log(`Hello, ${this.name}!`);
}
};
obj.sayHello();
输出是
Hello, undefined!
而不是
Hello, 1+1=10!
需要这样才行(注意:它指向全局的this,同时注意,在nodejs下this不等同于globalThis):
const obj = {
name: '1+1=10',
sayHello: () => {
console.log(`Hello, ${this.name}!`);
}
};
this.name = '1+1=2';
obj.sayHello(); // 1+1=2
在下面情况中,箭头函数中的this指向obj:
var obj = {
name: 'obj',
say: function() {
console.log(this.name);
let f = () => console.log(this.name);
return f;
}
};
obj.say()(); // obj obj
事件处理函数
如下程序,在浏览器下显示”true“,即this指向globalThis:
<!DOCTYPE html>
<html>
<head>
<title>DOM Event Handler</title>
</head>
<body>
<button onclick="handleClick()">Click me</button>
<script>
function handleClick() {
alert(this === globalThis);
}
</script>
</body>
</html>
而下面addEventListenser用法,结果显示"Button id is myButton",即this指向button:
<!DOCTYPE html>
<html>
<head>
<title>Button Event Listener Demo</title>
</head>
<body>
<button id="myButton">Click Me</button>
<script>
document.getElementById("myButton").addEventListener("click", function() {
alert("Button id is: " + this.id);
});
</script>
</body>
</html>
call、apply、bind
JavaScript中的call()和apply()方法都是用来改变函数的this指向的,它们可以让一个函数以指定的对象作为执行上下文,从而访问该对象的属性和方法。
const person = {
name: '1+1=10',
age: 100,
};
// Function to display person's details
function displayDetails() {
console.log(`Name: ${this.name}, Age: ${this.age}`);
}
// Using call
displayDetails.call(person);
// Using apply
displayDetails.apply(person);
// Using bind
const boundFunction = displayDetails.bind(person);
boundFunction();
全局作用域或普通函数
在全局作用域或普通函数中:非严格模式下,this指向全局对象(globalThis);严格模式下,this是undefined??
全局作用域中 this
全局作用域下,this并不一定是全局对象:
先看一段代码:
// 'use strict';
console.log(this.Math === Math);
在Firefox浏览器与QJSEngine下,结果都是true,在Node.js中,结果为false。是否是strict没有影响。
更直接一点:
console.log(this === globalThis);
在Firefox下true,在node.js下为false,在QJSEngine下报错(不支持globalThis)。
普通函数中的this
搞不懂,普通函数中为什么要设计this。
在Firefox、Node.js、QJSEngine下测试以下:
function returnThis() {
return this;
}
console.log(typeof returnThis());
console.log(returnThis().Math === Math);
结果:
object
true
在严格模式下测试:
'use strict';
function returnThis() {
return this;
}
console.log(typeof returnThis());
结果
undefined
全局对象们(内置标准对象)
JavaScript内置标准对象是指JavaScript语言自带的一些对象,它们可以直接在代码中使用,而不需要引入其他文件或库。
这东西挺多,暂时先摘抄一点。
JavaScript内置标准对象可以分为以下几类:
值属性
这些全局属性返回一个简单值,这些值没有自己的属性和方法。
- globalThis
- Infinity
- NaN
- undefined
关于NaN一个梗,香蕉是怎么练成的:
console.log(('b'+ 'a' + + 'b' + 'a').toLowerCase()) // banana
注,NaN的特点:
console.log(typeof NaN) // number
console.log(NaN === NaN) // false
对于C++看看:
// c++
double a = std::nan("");
std::cout << std::boolalpha << (a == a) << std::endl; // false
函数属性
这些全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。
例如:
- eval(),
- isFinite(),
- isNaN(),
- parseFloat(),
- parseInt()
基本对象
这些对象是定义或使用其他对象的基础。
例如:
- Object,
- Function,
- Boolean,
- Symbol
注意:symbol 是一种基本数据类型,每个从 Symbol()
返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的。
const mySymbol = Symbol('mySymbol');
const obj = {
normal: 'Hello, normal',
[mySymbol]: 'Hello, symbol!'
};
console.log(obj[mySymbol]); // Hello, symbol!
symbol 在 for in中不可见,在JSON.stringify()也不可见:
const mySymbol = Symbol('mySymbol');
const obj = {
normal: 'Hello, normal',
[mySymbol]: 'Hello, symbol!'
};
for (let key in obj) {
console.log(key); // Hello, normal
}
console.log(JSON.stringify(obj)); // {"normal":"Hello, normal"}
错误对象
这些对象是一种特殊的基本对象,它们用来表示和处理错误。
例如
- Error
- EvalError
- RangeError
- ReferenceError
数字和日期对象
这些对象用来表示和操作数字、日期和执行数学计算的对象。
例如
- Number
- BigInt
- Math
- Date
字符串对象
这些对象用来表示和操作字符串的对象。例如,String,RegExp等。
可索引的集合对象
这些对象用来表示和操作按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。
例如
- Array,
- Int8Array,
- Uint8Array,
- Uint8ClampedArray
使用键的集合对象
这些对象用来表示和操作使用键来存储数据的集合,包括可迭代的Map和Set,以及支持按照插入顺序来迭代元素的对象。
例如,
- Map,
- Set,
- WeakMap,
- WeakSet
结构化数据对象
这些对象用来表示和操作结构化的缓冲区数据,或使用JSON(JavaScript Object Notation)编码的数据。例如,ArrayBuffer,SharedArrayBuffer,Atomics,DataView,JSON等。
控制抽象对象
这些对象用来帮助构造代码,尤其是异步代码(例如不使用深度嵌套的回调)。
例如
- Iterator
- AsyncIterator
- Promise
- GeneratorFunction
- AsyncGeneratorFunction
- Generator
- AsyncGenerator
- AsyncFunction
反射对象
这些对象用来实现对对象的底层操作,或创建和操作代理对象。例如,Reflect,Proxy等。 Reflect是ES6引入的,Reflect对象提供了一系列的静态方法,它们与对象的基本操作一一对应,例如Reflect.get(),Reflect.set(),Reflect.has()等。这些方法的作用是将对象的操作转为函数调用,从而可以在函数中实现更灵活的逻辑:
const person = {
name: '1+1=10',
};
console.log(person.name); // 1+1=10
console.log(person['name']); // 1+1=10
console.log(Reflect.get(person, 'name')); // 1+1=10
console.log(Reflect.has(person, 'name')); // true
Reflect.deleteProperty(person, 'name');
console.log(Reflect.has(person, 'name')); // false
Proxy对象用于创建一个对象的代理,它接受两个参数,第一个是目标对象,第二个是一个处理器对象,它包含了一些陷阱(trap)函数,用于拦截目标对象的基本操作。这些陷阱函数的名称和Reflect对象的方法一一对应,例如get,set,has等。
const person = {
name: '1+1=10',
};
// Create a proxy object
const personProxy = new Proxy(person, {
get(target, property) {
console.log(`Getting property: ${property}`);
return Reflect.get(target, property);
},
set(target, property, value) {
console.log(`Setting property: ${property} to ${value}`);
return Reflect.set(target, property, value);
},
has(target, property) {
console.log(`Checking if property exists: ${property}`);
return Reflect.has(target, property);
},
deleteProperty(target, property) {
console.log(`Deleting property: ${property}`);
return Reflect.deleteProperty(target, property);
},
});
console.log(personProxy.name); // Getting property: name, 1+1=10
console.log(personProxy['name']); // Getting property: name, 1+1=10
console.log(Reflect.get(personProxy, 'name')); // Getting property: name, 1+1=10
console.log(Reflect.has(personProxy, 'name')); // Checking if property exists: name, true
Reflect.deleteProperty(personProxy, 'name'); // Deleting property: name
console.log(Reflect.has(personProxy, 'name')); // Checking if property exists: name, false
国际化对象
这些对象用来支持多语言处理。例如,Intl,Intl.Collator,Intl.DateTimeFormat,Intl.NumberFormat等。
例子:
// Create a new date object
const date = new Date();
const formattedDate_en_US = new Intl.DateTimeFormat('en-US').format(date);
console.log(formattedDate_en_US);
const formattedDate_zh_CN = new Intl.DateTimeFormat('zh-CN').format(date);
console.log(formattedDate_zh_CN);
结果
1/20/2024
2024/1/20
注,截至目前,QJSEngine不支持Intl。
参考
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis
- Symbol - JavaScript | MDN (mozilla.org)
- JavaScript 标准内置对象 - JavaScript | MDN (mozilla.org)
- https://doc.qt.io/qt-6/qtqml-javascript-functionlist.html