基础知识

变量声明就是var或者let声明,现在一般用let声明

常量声明 const PI = 3.14,声明的同时要初始化

数据类型:数值number、大整数bigint、字符串string、布尔值boolean、空值null、未定义undefined、符号symbol

大整数以n结尾,范围可以无限大let c = 1000n,大整数只能与大整数进行运算

字符串,单引号和双引号都行

类型转换

转字符串:.toString()、String(变量名)

转数值:Number(变量名)

利用函数转换:parseInt()、parseFloat()

转大整数:BigInt(变量名)

转布尔值:Boolean(变量名)

空赋值:let f; f ??= 100 , 表示如果f为null或者undefined,就赋值为100,否则不赋值

比较:=====的区别,==会自动进行类型转换,===不会转换类型

instanceof,判断对象是否属于这个类

三元运算符:表达式1 ? 表达式2 ? 表达式3,表达式1的结果为真,则执行表达式2,否则执行表达式3

隐式类型转换

1
2
3
4
5
6
7
8
9
10
11
12
当+-作为一元运算符的时候,代表强制转换为数字,如果转换不了就报错
let d= "100"; d = +d;

当+-作为二元运算符的时候,会将两边的类型转换为数值再进行计算,如果无法转换,返回NaN
100 - "55" //45

当+作为二元运算符且有一边是字符串的时候,会都转成字符串进行拼接

对非数值进行关系运算的时候,会先转成数值再比较
100 > "55"

当关系运算符的两边都是字符串,会逐位比较字符的Unicode编码

函数传入的参数数量与函数定义的参数数量不一致时,如果传多了,那么多余的参数不会被用到,如果传少了,剩下不够的被赋值为undefined

函数声明的一种写法

1
2
3
4
const test = (a) => {
console.log(a);
}
test(2);

作用域

全局作用域

不包含在函数内部的代码都在全局作用域中

全局作用域在程序创建时启动,程序结束时销毁

定义在全局作用域中的变量,就是全局变量

函数作用域

定义在函数作用域中的变量、参数,就是局部变量,函数外部不能访问

注意函数内部不写关键字的变量也是全局变量,比如直接写a = 3,a在函数外部可以访问

块级作用域

指作用域范围在局部代码块,if、switch、while、do…while、for、for…in等语句代码块中

在这些代码块里,使用let声明的变量,其作用域在该代码块中

1
2
3
4
5
for(let i=0;i<10;i++)
{
...
}
console.log(i); // 访问不到

var关键字定义变量存在的问题

1
2
3
4
5
6
7
8
9
10
变量声明提前
console.log(a);
var a; // 输出undefined,不会报错,因为var相当于把声明放到第一句去了,也就是先执行的var a

声明提前,赋值不提前
console.log(a);
var a=5; // 输出undefined,不会输出5,因为只是声明提前,赋值的位置还是没变

块级作用域
通过var定义的变量没有块级作用域,至少是函数作用域

作用域链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let a = 100;
function abc(){
let b = 200;
function test(){
let c = 300;
// for循环的代码是可以访问到b变量的
for (let i = 0; i < 3; i++) {
let d = 400;
console.log(i, d, c, b, a);
}
}
test();
}
{
let f = 600;
function abd() {
let e = 500;
console.log(a, e, f);
}
abd();
}
abc();
console.log(a);
// for语句块 --> test函数块 --> abc函数块 --> 全局
// abd函数块 --> 局部代码块 --> 全局
// 只要是在作用域链上的变量,从前往后都是可以访问到的

同一个作用域内,使用let声明的变量不能重名,但是var声明的可以重名,只不过是重复定义了

匿名函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function () {
var a = 100;
var b = 200;
console.log(a, b);
}());

!function () {
var a = 100;
var b = 200;
console.log(a, b);
}();

(function (a, b) {
console.log(a + b);
}(100, 200));

对象

对象的创建

1
2
3
let obj1 = new Object();
let obj2 = Object();
let obj3 = {}; // 隐式创建

对象属性的赋值

1
2
3
let obj3 = {};
obj3.name = "hello";
obj3.age = "3";

对象属性的删除

1
delete obj.name;

属性的访问

1
2
obj.name;
obj['name'];

对象创建

1
2
3
4
let obj1 = {a:100 , b:200};
console.log(obj1.a);
let obj2 = {'a':100 , 'b':200};
console.log(obj2.a);

属性是变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let a = 'x';
let b= 'y';
let obj3 = {[a+b]:100};
console.log(obj3xy); // 成功输出
let obj4 = {a+b:100};
console.log(obj4.xy); // 报错
let obj5 = {a:100};
console.log(obj5.x); // undefined,因为没有x属性
let obj6 = {[a]:100};
console.log(obj6.x); // 成功输出

let obj = {};
let s = "xxx";
obj[s] = 100;
console.log(obj[s]);
console.log(obj.s); // undefined,因为没有s属性,只有xxx属性

说白了,就是用.访问属性的时候,就是把这个值作为直接的字符串去访问,只有用[]访问属性的时候才是取里面的值

属性的遍历

无法遍历到使用符号作为属性名的属性,无法遍历到属性被设置为不可枚举的属性

1
2
3
4
5
6
7
let s = Symbol();
let obj = {x: 100, y: 200, z: 300, [s]: 400};
for (const objKey in obj) {
console.log(objKey, obj[objKey]); // 无法遍历到Symbol属性
}
let ss = Object.getOwnPropertySymbols(obj);
console.log(ss);

对象的方法

也就是属性是一个函数

1
2
3
4
5
6
7
8
9
function test(a) {
console.log("test " + a);
return 100;
}
// console.log(test);
// console.log(test());
let obj = {x: 100, y: 200, z: 300, s: test};
console.log(obj.x);
console.log(obj.s('jjj'));

window对象

浏览器提供了一个window对象,可以直接访问

window对象代表的是浏览器窗口,通过该对象可以对浏览器窗口进行各种操作

window对象还存储JS中的内置对象(window.Arraywindow.Object)和浏览器的宿主对象(window.document)

window对象的属性可以通过window对象访问,也可以直接访问

1
2
window.console.log(...);
console.log(...);

在浏览器环境中,window对象就是全局对象

在浏览器环境的全局作用域中,可以使用this代指window

全局对象的特点

下面说的全局作用域,其实可以理解成浏览器的devtools环境中

全局作用域中,使用var声明的变量,都是全局对象的属性

全局作用域中,使用function声明的方法,都是全局对象的方法

1
window.a = 100;// 向全局对象添加属性

全局作用域中,使用let声明的变量,不会存储在window中

1
2
let a = 100;
console.log(window.a);

不使用关键字声明变量

1
b = 200; // 相当于window.b = 200;

vm2的安装

是在node.js中提供相对纯净的v8环境

1
npm install vm2

为什么需要纯净的v8环境?因为node.js中有些语法是独有的,和浏览器中不一样,有可能被检测

1
2
3
4
5
6
7
8
9
10
11
let xjb_require = require;
const {VM, VMScript} = require("vm2"); // 包含vm2模块,得到VM和VMScript属性
const fs = require("fs"); // fs模块用来读写文件
const vm = new VM({
sandbox: {setTimeout, xjb_require}
}); //创建虚拟机
const code = fs.readFileSync("./input.js"); //读入JS代码
const script = new VMScript(code, "./debugJS.js"); //创建脚本,这里运行之后会生成debugJS.js,调试的时候可以进到debugJS.js代码里进行调试,进不到input.js里,但是两个js文件内容一样
// const script = new VMScript(code); 也行,但是调试不了
vm.run(`['global', 'SharedArrayBuffer', 'GLOBAL', 'root', 'VMError', 'Buffer'].forEach(v=>delete this[v])`);
const result = vm.run(script); //运行脚本,返回结果

vm2是相对纯净,但是也不完全,如何构建纯净的v8环境

自己去编译v8引擎,这个比较难

在node代码里直接删除对象

1
delete global

在vm2中删除对象

1
2
// 删掉里面的node独有的对象
vm.run(`['global', 'SharedArrayBuffer', 'GLOBAL', 'root', 'VMError', 'Buffer'].forEach(v=>delete this[v])`);

vm2中添加对象,可以在读取的js代码中使用

1
2
3
4
5
let xjb_require = require;
const vm = new VM({
sandbox: {setTimeout, xjb_require}
});
// 在js代码中使用xjb_require即可,可以避免字符串检测

global在node.js环境中也是代指全局对象,和浏览器环境下的window作用一样

this的几种情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
在全局作用域中引用thisthis代指window
以函数形式调用,this是全局对象
以方法形式调用,this是调用方法的对象
function fn(){
console.log(this);
}
let obj1 = {name: "xiaojianbang", test: fn};
let obj2 = {name: "ruyi", test: fn};
console.log(this); // this是全局对象
fn(); // this是全局对象
obj1.test(); // this是obj1
obj2.test();

箭头函数没有自己的this,由外层作用域决定,且无法通过call、apply、bind修改this
对象的方法、事件回调一般不用箭头函数
let fn = () => console.log(this);
let obj1 = {name: "xiaojianbang", test: fn};
obj1.test(); // 箭头函数的外层作用域是全局作用域,所以这里this指的是window

let obj1 = {name: "xiaojianbang", test: function () {
let fn = () => console.log(this);
fn();
}};
obj1.test(); // 箭头函数的外层作用域是函数,所以这里this指的是obj1

其实简单记忆就是谁调用函数,谁就是this
通过call和apply调用的函数,第0个参数就是this
通过bind返回的函数,第0个参数就是this