文章

JavaScript基础

开始

各个厂商对JavaScript都有自己的实现,都遵循ES标准

JavaScript的实现包含三个部分

  • ES标准
  • DOM:文档对象模型
  • BOM:浏览器对象模型

编写位置

  • HTML文档的script标签
  • 标签属性
  • 通过scrtip标签的src属性引入.js文件,引入后标签内的代码无效

注释

1
2
// 单行注释
/* 多行注释 */

使用严格模式:"use strict";

属性访问空安全:obj?.name

globalThis:对全局对象的抽象,始终指向全局对象

变量

变量声明

1
2
var variable;
let variable;

常量声明

1
const variable;

获取变量类型:typeof variable

数据类型

  • String:双引号,单引号都可以

  • Number

    Number.MAX_VALUE获取JS表示的最大值,超过最大值表示为字面量Infinity,类型Number

    不可计算的结果表示为字面量NaN,类型Number

    十六进制以0x开头,八进制以0开头,二进制表示0b开头

    大整数:数字后跟字符n,通过BigInt()转换

  • Boolean

  • Null

    null值用于表示为空的Object,null值的类型是Object

  • Undefined

    undefined值表示未定义的值,类型为undefined

  • Object:Object属于引用类型,其他都属于基本类型

类型强转

  • String()

  • Number():非法数字转换为NaN

    字符串解析为数字

    • parseInt(),可以指定进制
    • parseFloat()
  • Boolean()

    • 0和NaN为false,其余都为true
    • 空字符串为false,其余都为true
    • null和undefined为false
    • Object都为true

Symbol

Symbol数据类型表示一个唯一不重复的值,不能进行运算

Symbol创建

1
2
3
let s = Symbol();
let s1 = Symbol("Hello");
let s2 = Symbol.for("Hello");

var与let、const的区别

  • 变量的作用域不同

    • var声明的变量只有全局作用域和函数作用域
    • let声明的变量具有块作用域,全局作用域和函数作用域
    • const声明的变量和let作用域相同
  • const

    const声明的是常量,必须在声明时进行初始化,此后变量的值不变

    当const声明的是对象时,对象为引用类型,变量获取的实际是对象的常量指针,因此变量只能指向该对象,该对象的属性可变

解构赋值

  • 数组解构赋值

    1
    2
    
    const arr = [1, 2, 3, 4];
    let [i1, i2, i3, i4] = arr;
    
  • 对象解构赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    const obj = {
    	name: 'Hello',
        age: 20
    };
    let {name, age} = obj;
        
    // 函数参数解构
    function method({name, age}) {
        // ...
    }
    let obj = {
        name: "Hello",
        age: 20
    }
    method(obj);
    

函数

函数声明

1
2
3
function func(parms) {
    return returnvalue;
}

函数可作为函数对象使用,适用于闭包

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
30
31
// 构造器生成函数对象
let fun = new Function("console.log('hello');");
fun()

// 函数声明的函数对象
function func() {
    // ...
}
let fun = func;
fun();

// 匿名函数对象
let fun = function () {
    // ...
}
fun();

// 直接调用匿名函数对象
(function func(x) {
    // ...
})(x);

// 函数默认值
function method(a, b, c = 10) {
    // ...
}

// 解构默认值
function method({name, age = 20}) {
    // ...
}

函数对象方法:通过函数对象调用,都可以修改函数对象的this指向

  • call(obj, parms…):传入对象obj,执行该函数同时将该函数的this指向obj,parms为函数参数
  • apply(obj, parmsArray):将函数的this指向传入的obj,函数参数以数组形式传入
  • bind(thisArg):复制一个函数,并设置新函数的this,设置后不可更改,若第二个参数之后传入参数,则将原函数对应位置参数设为固定值

this、arguments

JavaScript的this以函数为导向,每个函数调用时包含两个隐含参数this和arguments,与原型对象不同,原型对象prototype在函数创建时生成,this和arguments在函数调用时传入

this的值为函数调用的上下文对象(object context),可理解为指向调用函数的对象

  • 对象调用时,this指向该对象

  • 全局函数调用时,this指向window对象

  • 函数中调用函数时,this指向window对象

    在全局作用域中声明的变量和函数,本质是声明Window对象的动态属性

    对于变量,使用var声明会声明到Window对象中,使用let声明不会声明到Window对象中

arguments是封装函数参数的对象,是一个伪数组,可以通过下标访问

rest参数

rest参数用于代替arguments,在参数列表中将多余参数包装成伪数组在函数中使用,rest参数必须在参数列表末尾

1
2
3
function method(...args) {
    // ...
}

扩展运算符...:将数组转换为一个参数序列(在ES8中支持对象的扩展运算符)

1
2
let arr = [1, 2, 3, 4];
// ...arr => 1, 2, 3, 4

扩展运算符的应用

  • 数组合并

    1
    2
    3
    
    let a = [1, 2];
    let b = [3, 4];
    let c = [...a, ...b];  // c = [1, 2, 3, 4]
    
  • 数组复制:浅拷贝

    1
    2
    
    let a = [1, 2, 3, 4];
    let b = [...a];
    
  • 将伪数组转换为真数组

    1
    2
    
    const divs = document.querySelectorAll("div");
    const divArr = [...divs];
    

箭头函数

类似于lambda表达式

1
2
3
4
5
6
7
let hello = function (a, b) {
    return a + b;
}
let hello = (a, b) => {
    return a + b;
}
let hello = (a, b) => a + b; // 单行返回

箭头函数this的指向:function函数有自己的this值,指向调用该函数的对象(object context),箭头函数没有自己的this值,其this值通过继承函数作用域链上最近的函数获取

箭头函数不能使用call、apply、bind函数改变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
let circle = {
    radius: 10,
    outer:function () {
        let inter = function () {
            console.log(this == window);
            console.log(this.value);
        };
        inter();
        console.log(this == circle)
    }
}
circle.outer() // true undefined true
// inter由函数调用,它的this指向浏览器对象window,window里没有radius属性,返回undefined
let circle = {
    radius: 10,
    outer:function () {
        let inter = () => {
            console.log(this == window);
            console.log(this.value);
        };
        inter();
        console.log(this == circle)
    }
}
circle.outer() // false 10 true
// inter是箭头函数,根据函数作用域链向上寻找并继承其他函数的this值,inter继承outer的this值,指向circle

数组

JavaScript数组可以动态扩容,且可以同时存放任意类型,length属性返回数组长度

1
2
3
4
5
6
7
8
// new创建
let arr = new Array();
arr[0] = 1;
// 字面量创建
let arr = [1, 2, 3];
let [a, b, c] = arr  // 解构赋值
let [d, e, f, g = 10] = arr  // 解构默认值
let [h, ...i] = arr // 解构剩余值数组,可将可迭代对象转换为数组

数组遍历

  • for三段式
  • for-in:获取可迭代对象的键,可遍历数组和对象属性,最好用于遍历对象属性
  • for-of:获取可迭代对象的值,可遍历数组和对象属性,最好用于遍历数组

数组方法

  • push():在尾部添加任意个数元素,返回新的长度
  • pop():删除最后一个元素,返回该元素
  • unshift():在开头添加任意个数元素,返回新的长度
  • shift():删除第一个元素,返回该元素
  • forEach():传入一个函数对象,对数组元素执行该函数,函数对象中的参数分别为数组元素,下标,整个数组
  • slice(start, end):[start, end),数组切片并返回,支持负数倒数索引
  • splice(start, n, obj…):删除指定索引开头的n个元素并已数组形式返回,在start处插入多个obj
  • concat():连接多个数组并返回新数组
  • join():将数组以字符串返回,可以传入字符串指定分隔符
  • reverse():反转数组
  • sort():排序,默认按照字典序排序,传入一个函数对象指定Comparator

对象

对象的分类

  • ES标准对象:String、Object等
  • 浏览器对象:console、document等
  • 自定义对象

对象创建

判断对象中是否存在某个属性:attr in obj,attr类型为String,以属性名匹配变量

1
2
3
4
5
6
7
8
let person = {
    name: "nnn",
    age: 20
}
console.log("name" in person)  // true
let {name, age} = person  // 对象解构赋值,名称必须与属性名相同
let {name:n1, age:n2} = person // 解构赋值别名
let {name:n1, age:n2 = 30} = person  // 解构赋值默认值

声明一个Object类型对象

1
2
3
4
5
6
let obj = new Object();
// 字面量创建
let obj = {
    name:"obj",
    age:18
};

可以在声明对象后,在外部动态赋值属性

1
2
3
4
5
6
7
let obj = {}; // Object类型
obj.name = "obj";
// 字典型赋值
// 若key不符合命名规范,则自动转换为String类型
obj[18] = 999; // {'18': 999}
// 若key符合命名规范,则声明该变量
obj["n1"] = 1000; // {n1: 1000}

使用计算属性(动态属性名)

1
2
3
4
5
6
7
8
9
10
let a = "Hello";
let method = "myMethod";
let obj = {
    // 中括号中可以使用复杂表达式
    [a]: 20,
    [method]() {
        // ...
    }
}
// obj[a]或obj['Hello']

可以声明其他类型的对象,动态赋值属性

1
2
3
let obj = new String();
obj.age = 100;
console.log(obj); // [String: ''] { age: 100 }

对象使用

属性访问和下标访问

1
2
3
4
5
6
7
8
9
obj.n // 属性访问将输入的属性名作为变量与对象属性变量进行匹配,若没有该属性则返回undefined
obj[n] // 下标访问将输入的属性名作为值与对象属性名进行匹配

let obj = {
    name:"abc"
}
let n = "name";
obj.n // undefined
obj[n] // "abc"

for/in遍历对象字段

1
2
3
4
for (let n in obj) {
    // typeof n = String
    obj[n]; // 访问对象变量值
}

对象方法

1
2
3
4
5
6
7
8
9
let obj = {
    fun:function (x) {
        // ...
    },
    fun1() {
        // ...
    }
}
obj.fun(x);

对象简化写法

对象字面量声明对象时,传入变量时简化属性名

1
2
3
4
5
6
7
8
9
10
let name = "Hello";
let age = 20;
const obj = {
    name,
    age,
    // 匿名函数简写
    method() {
        // ...
    }
}

模拟类

早期JavaScript不支持类,可以通过一些方法模拟类

构造函数

经典方法

1
2
3
4
5
6
7
8
function Person(name, age) {
    this.name = name; // 实例字段
    this.age = age;
    this.fun = function (x) {
        // ...
    } // 实例方法
}
let p = new Person(name, age);

模拟类方法(极简方法)

使用对象模拟一个类,其中定义构造函数

Javascript定义类(class)的三种方法 - 阮一峰的网络日志 (ruanyifeng.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var Person = { // 建议const
    create:function (name) {
        var p = {}; // 建议const
        p.name = name;
      	p.speak = function() {console.log("Hello");};
        return p;
    }
}
// 简化写法
/*
const Person = {
    create:function(name) {
        return {
            name:name,
            speak:function() {
                console.log("Hello");
            }
        };
    }
}
*/
let p = Person.create(name);
p.name;
p.speak();

使用该方法创建的对象总是Object类型({}类型)

1
2
3
4
let p = Person.create(name);
p instanceof Person // TypeError
p instanceof Person.create // false
p instanceof Object // true

该方法使用简单,便于理解,在不需要类型判断时可以使用

继承

使用极简方法可以很容易地实现继承的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Animal = {
    create: function () {
        return {
            name:"动物"
        };
    }
}

const Cat = {
    create: function () {
        const cat = Animal.create();
        cat.age = 2;
        return cat;
    }
}

访问限制

使用极简方法来模拟私有字段/方法和公有字段/方法

1
2
3
4
5
6
7
8
9
10
const Cat = {
    create: function () {
        const cat = {};
        // 相当于在构造时动态设置访问级
        let name = "nn"; // private
        cat.age = 2; // public
        cat.getName = function() {return name;};
        return cat;
    }
}

类字段和类方法

使用极简方法来模拟类字段和类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const Cat = {
    create: function () {
        const cat = Animal.create();
        cat.age = 2;
        return cat;
    },
    // 在Cat中定义的字段和方法可以看做类字段和类方法,通过Cat调用
    value: "nnn";
    speak: function () {
        console.log("Hello");
    }
}
Cat.speak();
Cat.value;

将类字段/方法与对象关联,使得对象可以操作类字段/方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const Cat = {
    create: function () {
        const cat = {};
        cat.age = 2; // 实例字段
        cat.speak = this.speak; // 类方法,该方法调用者为Cat,this指向Cat,或者赋值Cat.speak
        // 类字段共享只能使用方法来对类字段进行操作,若直接赋值,其实是动态添加了实例属性,无法做到共享
        // 操作时使用Cat类调用,该方法的调用者为cat对象,this指向cat对象,使用this调用无效
        cat.setValue = function (value) {
            Cat.value = value;
        }
        cat.getValue = function () {
            return Cat.value;
        }
        return cat;
    },
    // 在Cat中定义的字段和方法可以看做类字段和类方法,通过Cat调用
    value: "nnn",
    speak: function () {
        console.log("Hello");
    }
}

class类

在ES6中,js添加了类功能,可以使用class声明一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
    // 属性
    name  // 声明属性
    age = 20  // 初始化属性
	static attr  // 静态属性,无法通过实例访问,只能通过类访问
    // 方法
    fun() {
        // ...
        // 实例方法中的this指向实例对象
    }
    static fun1() {
        // ...
        // 静态方法中的this指向类
    }
    // 构造器
    constructor(name) {
        this.name = name
    }
}
let person = new Person()  // 构造函数
person instanceof Person  // 类型判断
// 声明动态属性
person.name = "Hello"

封装

私有属性使用#前缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person {
    #name  // 私有属性必须先声明
    constructor(name) {
        this.#name = name
    }
    // 可提供getter和setter
    getName() {
        return this.#name
    }
    setName(name) {
        this.#name = name
    }
    // 简化语法糖,可简化为属性访问形式
    get name() {
        return this.#name
    }
    set name(name) {
        this.#name = name
    }
}

继承与多态

js使用extends实现继承,支持方法重写

1
2
3
4
5
6
7
8
9
10
class Animal {
    // ...
}
class Dog extends Animal {
    // 重写构造器时,调用父类构造,同Java
    constructor() {
        super()
    }
    // ...
}

多态:js是动态语言,它的多态并不依赖于继承,在使用一个未知类型对象时,只关心该对象内部是否有相应的属性或方法

原型对象

每个函数、对象都自动存在一个原型对象,隐含的prototype属性指向原型对象,而原型对象中的constructor属性指向该函数对象

image-20221004153502667

当函数以构造函数的形式调用时(类实例化),该构造函数创建的对象中会包含一个隐含属性__proto__,该属性指向原型对象

若函数是构造函数,则类中声明的方法会存储在原型对象中

1
2
3
4
5
fun.prototype  // 函数访问自己的原型对象,通常使用该方式访问原型对象
obj.__proto__  // 对象访问自己的原型对象
fun.prototype == obj.__proto__  // true
fun.prototype.constructor == fun  // prototype和constructor属性是对应的
Object.getPrototypeOf(obj)  // 通过Object安全访问原型对象

原型对象可看做一个类对象(静态),其中定义的属性和方法可作为各个对象实例的类字段和类方法

创建出对象或构造函数后,可以向原型对象里动态定义字段和方法

1
2
3
4
5
6
function Person(name) {
    this.name = name;
}
let p = new Person(name);
Person.prototype.age = 20;
console.log(p.age) // 20

instanceof

instanceof用于判断对象实例类型,判断实例是否是某个构造函数创建的,不能用于判断原型对象

TypeException:Right-hand side of 'instanceof' is not callableinstanceof的右值必须是可调用的

instanceof实际是判断实例对象的__proto__和构造函数的prototype是否相同

1
2
3
4
5
6
function Person(name) {
    this.name = name;
}
let p = new Person(name);
console.log(p instanceof Person) // true
console.log(p instanceof Person.prototype) // error

原型链

构造函数与对象的原型关系

img

两种构造方法的原型链

经典方法(对应类实例化):真正的创建了Person构造函数,生成了Person的原型对象

image-20221004185029520

极简方法或字面量方法:本质上person是个Object对象,没有创建新的原型对象

image-20221005155435211

原型继承:js继承通过原型链实现,子类的原型对象就是父类的实例对象

对象方法

  • hasOwnProperty:判断对象实例自身的字段

    使用obj.hasOwnProperty("attr")判断对象实例自身的字段,不包含原型对象的字段

    1
    2
    3
    4
    5
    6
    7
    
    function Person(name) {
        this.name = name;
    }
    let p = new Person(name);
    Person.prototype.age = 20;
    "age" in p // true
    p.hasOwnProperty("age") // false
    
  • Object.is:判断两个对象是否完全相等

  • Object.assign:对象合并,将后一参数对象合并到前一参数对象,若出现重名成员,则覆盖

  • Object.setPrototypeOf:设置对象原型对象

  • Object.getPrototypeOf:获取对象原型对象

  • Object.fromEntries:通过二维数组或Map来创建对象

  • Object.entries:将对象转换为二维数组

常用工具对象

Date

创建Date对象自动以当前时间创建

1
2
3
Date date = new Date();
// 传入指定时间字符串创建,格式:mm/dd/yyyy hh:MM:ss
Date date = new Date("10/5/2022 16:17:00");

方法

  • getDate():返回日期
  • getDay():返回日期是周几,周日返回0
  • getMonth():返回月份,从0开始
  • getFullYear():返回年份
  • getTime():返回时间戳(自1970年1月1日到至今的毫秒数)
  • Date.now():获取当前时间戳

字符串

字符串支持下表访问,length获取字符串长度

  • charCodeAt():返回指定位置字符的Unicode编码
  • String.fromCharCode():返回Unicode编码对应的字符
  • concat():连接多个字符串
  • indexOf(char, start):获取字符串中字符的索引,不存在则返回-1,start指定开始查找的位置
  • lastIndexOf():从尾部查找字符的索引
  • slice():获取子串
  • substring():获取子串,不支持负数索引
  • split():字符串分割为数组,传入字符串分隔符,传入空串,则以每个字符分割
  • search():搜索字符串中的子串,返回第一次出现的索引,不存在则返回-1
  • replace():将字符串中的第一个指定子串替换为新内容
  • trimStart():去除字符串起始空白
  • trimEnd():去除字符串末尾空白
  • trim():去除字符串起始和末尾空白

正则表达式

JavaScript正则表达式使用RegExp对象封装

1
2
3
let reg = new RegExp("expression", "pattern");
// 字面量创建
let reg = /expression/pattern;

pattern

  • “i”:忽略大小写
  • “g”:全局匹配

相关方法

  • test(str):测试str是否符合正则表达式
  • 与字符串相关,由字符串调用
    • split():传入正则表达式,根据正则表达式的匹配结果分割字符串,默认全局匹配
    • search():传入正则表达式,返回第一个匹配结果的索引,不支持全局匹配
    • match():传入正则表达式,返回第一个匹配结果,正则设置为全局匹配g,返回所有匹配结果的数组
    • replace():传入正则表达式,将第一个匹配结果替换为新内容,正则设置为全局匹配g,将所有匹配结果替换

JSON

  • JSON.parse(jsonString):将json字符串转换为json对象
  • JSON.stringify(obj):将js对象转换为json字符串

Map

Map的键值支持任意对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let map = new Map()
map.size
map.keys()
map.values()
map.entries()
map.set(key, value)
map.get(key)  // 不存在返回undefined
map.delete(key)  // 删除key
map.has(key)  // 是否包含key
map.clear()
let arr = Array.from(map)  // 将map转换为k-v数组
let map = new Map([[k1, v1], [k2, v2], [k3, v3]])  // 数组转换为map
// 遍历map
for (let entry of map) {
    // entry = [k, v]
    // ...
}
for (let [k, v] of map) {
    // ...
}

Set

1
2
3
4
5
6
7
8
9
let set = new Set()
set.size
set.add(value)
set.delete(value)
set.has(value)
for (let item of set) {
    // ...
}
let set = new Set([v1, v2, v3])  // 数组转换为Set

迭代器

迭代器用于for…of访问,实际通过Symbol.iterator方法获取迭代器Iterator,调用Iterator的next方法遍历,next方法返回一个包含valuedone两个属性的对象,value为遍历值,done表示遍历是否完成

自定义迭代器

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
30
31
class MyArray {

    #array = ["a", "b", "c"];

    [Symbol.iterator]() {
        let i = 0;
        let _this = this;
        // 返回一个Iterator对象,带有next方法
        return {
            next() {
                // next方法返回一个带有value和done两个属性的对象
                if (i < _this.#array.length) {
                    return {
                        value: _this.#array[i++],
                        done: false
                    }
                } else {
                    return {
                        value: undefined,
                        done: true
                    }
                }
            }
        }
    }
}

const myArray = new MyArray();
for (let v of myArray) {
    console.log(v);
}

生成器

生成器是一个函数,可以实现代码逻辑的迭代执行

1
2
3
4
5
6
7
8
9
function* generate() {
    // 生成器中可以使用yield,中断执行,返回输出
    yield 1
    yield 2
    yield 3
}
for (let v of generate()) {
    console.log(v);
}

生成器传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* generate(arg) {
    // 调用传参
    console.log(arg)
    // next方法传参
    let res1 = yield
    console.log(res1)  // A
    let res2 = yield
    console.log(res2)  // B
    let res3 = yield
    console.log(res3)  // C
}

let generator = generate("Hello")
generator.next()  // 执行console.log(arg)
generator.next("A")  // 赋值res1,执行console.log(res1)
generator.next("B")  // 赋值res2,执行console.log(res2)
generator.next("C")  // 赋值res3,执行console.log(res3)

模块化

在ES6之前有许多模块化规范

  • CommonJS:NodeJS
  • AMD:requireJS
  • CMD:SeaJS

模块化语法

导出:export

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 导出变量
export let variable = 20;

// 导出函数
export function method() {
    // ...
}

// 统一导出
export {
    variable,
    function1
}

// 默认导出,通过default属性获取
export default {
    name: variable,
    age: 20,
    method: function() {
        // ...
    }
}

导出:import

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 导入所有成员
import * as my_module from 'filepath'

// 解构导入,使用别名
import {variable as other, function1} from 'filepath'

// 默认导出解构
import {default as m} from 'filepath'

// 默认导出语法糖
import m from 'filepath'

// 动态导入
import("./hello.js").then(module => {
    // ...
})
本文由作者按照 CC BY 4.0 进行授权