玩命加载中🤣🤣🤣

JavaScript 常用查询


JavaScript

对象

1)Function

匿名函数

(function (param) {
    // body
    return result;
})

// 例
(function(a,b){
    return a + b;
})

第一种场景:定义完毕后立刻调用

(function(a, b) {
    return a + b
})(1, 2)

第二种场景:作为其它对象的方法,例如:

页面有一个元素

<p id="p1">
    点我一下
</p>

此元素默认有一个 onclick 方法,会在鼠标单击这个元素后被执行,onclick 方法刚开始是 null,需要赋值后才能使用

document.getElementById("p1").onclick = (function(){
    console.log("鼠标单击了...");
});

箭头函数

(参数) => {
    // 函数体
    return 结果;
}
// 举例
document.getElementById("p1").onclick = () =>  console.log("aa");

函数的本质是对象

举例查看属性

function abc() {
    console.log('bb')
}

console.dir(abc)
  • 函数作为方法参数
function a() {
    console.log('a')
}

function b(fn) {          // fn 将来可以是一个函数对象
    console.log('b')
    fn()                  // 调用函数对象
}

b(a)
  • 函数作为方法返回值
function c() {
    console.log("c")
    function d() {
        console.log("d")
    }
    return d
}

c()()

var 与 let 的区别

如果函数外层引用的是 let 变量,那么外层普通的 {} 也会作为作用域边界,最外层的 let 也占一个 script 作用域

let x = 10
if(true) {
    let y = 20
    function b() {
        console.log(x,y)
    }
    console.dir(b)
}

如果函数外层引用的是 var 变量,外层普通的 {} 不会视为边界

var x = 10
if(true) {
    var y = 20
    function b() {
        console.log(x,y)
    }
    console.dir(b)
}

如果 var 变量出现了重名,则他俩会被视为同一作用域中的同一个变量

var e = 10
if(true) {
    var e = 20
    console.log(e) // 打印 20
}
console.log(e) // 因为是同一个变量,还是打印 20

如果是 let,则视为两个作用域中的两个变量

let e = 10
if(true) {
    let e = 20
    console.log(e) // 打印 20
}
console.log(e) // 打印 10

要想里面的 e 和外面的 e 能区分开来,最简单的办法是改成 let,或者用函数来界定作用域范围

var e = 10
if(true) {
    var e = 20
    console.log(e)
}
console.log(e)
// 用函数来界定作用域范围
var e = 10
if(true) {
    function b() {
        var e = 20
    	console.log(e) // 打印20
    }
    b()
}
console.log(e)	// 打印10

2) Array

语法

// 创建数组
let arr = [1,2,3]

// 获取数组元素
console.log(arr[0]) // 输出 1

// 修改数组元素
array[0] = 5 // 数组元素变成了 [5,2,3]

API

  • 遍历
// 遍历数组元素,其中 length 是数组属性,代表数组长度
for (let i = 0; i < arr.length; i++) {
    console.log(arr[i])
}

// 遍历数组 of
for (const item of arr) {
    console.log(item)
}
  • push(添加)、shift(删除)、splice(删除)
let arr = [1,2,3]

arr.push(4) // 向数组尾部(右侧)添加元素, 结果 [1,2,3,4]
arr.shift() // 从数组头部(左侧)移除元素, 结果 [2,3,4]
arr.splice(1,1) // 删除【参数1】索引位置的【参数2】个元素,结果 [2,4]
  • join
let arr = ['a','b','c']

arr.join() // 默认使用【,】作为连接符,结果 'a,b,c'
arr.join('') // 结果 'abc'
arr.join('-') // 结果 'a-b-c'
  • map(会产生新的对象)
let arr = [1,2,3,6]

// 需求:arr => [10, 20, 30, 60]
arr.map( i => i * 10 ) //  [10, 20, 30, 60]

// 内部实现(伪代码)
function map(a) { // 参数是一个函数
    let narr = []
    for(let i = 0; i < arr.length; i++) {
        let o = arr[i] // 旧元素
        let n = a(o) // 新元素
        narr.push(n)
    }
    return narr
} 
  • filter(会产生新的对象)
let arr = [1,2,3,6]
arr.filter( (i)=> i % 2 == 1 ) // 结果 [1,3]
  • forEach
let arr = [1,2,3,6]
arr.forEach( (i) => console.log(i) )

arr 的合并及去重

const arrA = [{ id: 1, name: '张三' }, { id: 2, name: '李四' }]
const arrB = [{ id: 2, name: '李四' }, { id: 3, name: '王五' }]
// 合并
const combinedData = [...arrA, ...arrB]
// 去重
const arr = combinedData.reduce((acc, current) => {
    // 如果 acc 中还没有当前项,则添加进去
    if (!acc.some(item => item.name === current.name)) {
      acc.push(current)
    }
    return acc
    }, [])

将对象数组映射成 {key: item}: Object | map

以某个属性作为主键

const arr = [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' }
]

// 使用 reduce 方法创建 Object
const mapObject = arr.reduce((acc, item) => {
  acc[item.id] = item
  return acc
}, {})

console.log(mapObject)
// 输出:
// {
//   "1": { "id": 1, "name": "张三" },
//   "2": { "id": 2, "name": "李四" }
// }

// ※※※※※※※※※※※※※※※※※※※※
// 使用点符号访问
console.log(mapObject[1]) // 输出: { id: 1, name: '张三' }

// 或者使用方括号语法
console.log(mapObject['2']) // 输出: { id: 2, name: '李四' }

// ※※※※※※※※※※※※※※※※※※※※
// 检查键是否存在
if (mapObject.hasOwnProperty(1)) {
  console.log(mapObject[1])
}

// 或者使用 in 运算符
if (1 in mapObject) {
  console.log(mapObject[1])
}

// ※※※※※※※※※※※※※※※※※※※※
// 遍历映射对象的所有键值对
// 使用 for...in 循环
for (let id in mapObject) {
  if (mapObject.hasOwnProperty(id)) {
    console.log(`ID: ${id}, Name: ${mapObject[id].name}`)
  }
}

// 使用 Object.keys() 和 forEach
Object.keys(mapObject).forEach(id => {
  console.log(`ID: ${id}, Name: ${mapObject[id].name}`)
})

// 使用 Object.entries() 和解构赋值
Object.entries(mapObject).forEach(([id, item]) => {
  console.log(`ID: ${id}, Name: ${item.name}`)
})

ES6 的 Map 对象而不是普通的对象字面量

const arr = [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' }
]

// 使用 Map 构造函数和 Array.prototype.map 创建 ES6 Map
const map = new Map(arr.map(item => [item.id, item]))

console.log(map)
// 输出: Map(2) { 1 => { id: 1, name: '张三' }, 2 => { id: 2, name: '李四' } }

// 访问 Map 中的数据
console.log(map.get(1)) // 输出: { id: 1, name: '张三' }

// ※※※※※※※※※※※※※※※※※※※※
// map 操作见下一节

3) map

const arr = [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' }
]

// 使用 Map 构造函数创建 ES6 Map
const map = new Map(arr.map(item => [item.id, item]))

console.log(map)
// 输出: Map(2) { 1 => { id: 1, name: '张三' }, 2 => { id: 2, name: '李四' } }

获取单个值

console.log(map.get(1)) // 输出: { id: 1, name: '张三' }

// 如果尝试获取不存在的键,返回 undefined
console.log(map.get(3)) // 输出: undefined

检查键是否存在

if (map.has(1)) {
  console.log(map.get(1))
}

// 或者直接使用 has 方法
console.log(map.has(1)) // 输出: true
console.log(map.has(3)) // 输出: false

遍历 Map 中的所有键值对

// for...of
for (let [key, value] of map) {
  console.log(`ID: ${key}, Name: ${value.name}`)
}

// forEach
map.forEach((value, key) => {
  console.log(`ID: ${key}, Name: ${value.name}`)
})

// entries()迭代器
for (let entry of map.entries()) {
  console.log(`ID: ${entry[0]}, Name: ${entry[1].name}`)
}

// keys()和values()迭代器
// 遍历所有的键
for (let key of map.keys()) {
  console.log(`Key: ${key}`)
}

// 遍历所有的值
for (let value of map.values()) {
  console.log(`Name: ${value.name}`)
}

其他常用操作

// 添加新的键值对
map.set(3, { id: 3, name: '王五' })
console.log(map.get(3)) // 输出: { id: 3, name: '王五' }

// 删除键值对
map.delete(2)

// 清空
map.clear()

// 获取大小
console.log(map.size)

4) Object

语法

let obj = {
    属性名:,
    方法名: 函数,
    get 属性名() {},
    set 属性名(新值) {}
}

举例

let stu3 = {
    name: "小白",
    age: 18,
    study(){
        console.log(this.name + "来学习");
    }    
}

get,set

let stu4 = {
    _name: null, /*类似于java中私有成员变量*/
    get name() {
        console.log("进入了get");
        return this._name;
    },
    set name(name) {
        console.log("进入了set");
        this._name = name;
    }
}

调用get,set

stu4.name = "小白"

console.log(stu4.name)

属性的增删

let stu = {name:'张三'}
stu.age = 18					// 添加属性
delete stu.age					// 删除属性

stu.study = function() {		// 添加方法
    console.log(this.name + '在学习')
}

增加get,set

let stu = {_name:null}
// 给stu后加 get,set方法
Object.defineProperty(stu, "name", {
    get(){
        return this._name
    },
    set(name){
        this._name = name
    }
})

this

  • 【落单】的函数
let stu = {
    name: "小花",
    friends: ["小白","小黑","小明"],
    play() {
        this.friends.forEach(function(e){
            console.log(this.name + "与" + e + "在玩耍");
        });
    }
}
stu.play()

this.name 所在的函数是【落单】的函数,因此 this 代表 window

  • 用箭头函数
let stu = {
    name: "小花",
    friends: ["小白","小黑","小明"],
    play() {
        this.friends.forEach(e => {
            console.log(this.name + "与" + e + "在玩耍");
        })
    }    
}
stu.play()

this.name 所在的函数是箭头函数,因此 this 要看它外层的 play 函数,play 又是属于 stu 的方法,因此 this 代表 stu 对象

  • 不适用箭头函数的做法
let stu = {
    name: "小花",
    friends: ["小白","小黑","小明"],
    play() {
        let me = this
        this.friends.forEach(function(e){
            console.log(me.name + "与" + e + "在玩耍")
        })
    }
}

原型继承

let father = {
    f1: '父属性',
    m1: function() {
        console.log("父方法")
    }
}

let son = Object.create(father)

console.log(son.f1) // 打印 父属性
son.m1() // 打印 父方法
  • father 是父对象,son 去调用 .m1 或 .f1 时,自身对象没有,就到父对象找
  • son 自己可以添加自己的属性和方法
  • son 里有特殊属性 __proto__ 代表它的父对象,js 术语: son 的原型对象
  • 不同浏览器对打印 son 的 __proto__ 属性时显示不同
    • Edge 打印 console.dir(son) 显示 [[Prototype]]
    • Firefox 打印 console.dir(son) 显示 <prototype>

基于函数的原型继承

出于方便的原因,js 又提供了一种基于函数的原型继承

函数职责

  1. 负责创建子对象,给子对象提供属性、方法,功能上相当于构造方法

  2. 函数有个特殊的属性 prototype,它就是函数创建的子对象的父对象

    **注意!**名字有差异,这个属性的作用就是为新对象提供原型

function cons(f2) {
    // 创建子对象(this), 给子对象提供属性和方法
    this.f2 = f2;
    this.m2 = function () {
        console.log("子方法"
    }
}
// cons.prototype 就是父对象
cons.prototype.f1 = "父属性";
cons.prototype.m1 = function() {
    console.log("父方法")
}

配合 new 关键字,创建子对象

let son = new cons("子属性")

子对象的 __proto__ 就是函数的 prototype 属性

4) boolean补充

undefined 和 null

  • 执行表达式或函数,没有返回结果,出现 undefined
  • 访问数组不存在的元素,访问对象不存在的属性,出现 undefined
  • 定义变量,没有初始化,出现 undefined
console.log(1) // 函数没有返回值, 结果是  undefined
let a = 10 // 表达式没有返回值, 结果是 undefined
let b = [1,2,3]
console.log(b[10]) // 数组未定义元素是 undefined
let c = {"name":"张三"}
console.log(c.age) // 对象未定义属性是 undefined
let d
console.log(d) // 变量未初始化是 undefined
  • 二者共同点
    • 都没有属性、方法
    • 二者合称 Nullish
  • 二者区别
    • undefined 由 js 产生
    • null 由程序员提供

Truthy & Falsy

if (xxx), falsy如下:

  • false
  • Nullish( null, undefined)
  • 0, 0n, NaN
  • "" '' `` 长度为零的字符串

容易误当成 falsy 的例子:

  • "false", "0" 即字符串的 false 和 字符串的零
  • [] 空数组
  • {} 空对象

Promise

基础用法

  • 构建一个 promise: 模拟一个异步函数, 随机会经过n时间拿到结果
const setDelay = (millisecond) => {
    return new Promise((resolve, reject) => {
        if(typeof millisecond != 'number') {
            reject(new Error('参数必须时number类型'))
        }
        setTimeout(() => {
            resolve(`延迟 ${millisecond} 毫秒输出`)
        }, millisecond)
    })
}
  • 构建第二个 promise: 模拟一个异步函数, 随机会经过n时间拿到结果
const setDelaySecond = (seconds) => {
    return new Promise((resolve, reject) => {
        if(typeof seconds != 'number') {
            reject(new Error('参数必须时number类型'))
        }
        setTimeout(() => {
            resolve(`延迟 ${seconds} 秒输出`)
        }, seconds * 1000)
    })
}

链式调用

模拟第一个异步函数拿到结果后调用第二个函数

setDelay(2000)
  .then(result => {
      const data = result.split(' ')
      console.log('一顿操作之后的结果 >>> ', data[1]) // 模拟处理结果
      return setDelaySecond(parseInt(data[1] / 1000))
  })
  .then(result => {
      console.log(result)
  })
  .catch(err => {
      console.log(err)
  })

reject 示例

then 式链式写法的本质其实是一直往下传递返回一个新的 Promise,也就是说 then 在下一步接收的是上一步返回的 Promise

setDelay(2000)
  .then(result => {
      console.log(result)
      return setDelaySecond('2')
  })
  .then(result => {
      console.log(result)
  }, err => {
      //console.log(err)
      console.log('第二步报错了')
  })
  .catch(err => {
      console.log(err) // 此时已经在前面的reject处理过了,因此不会走到这里
  })
  .then(result => {
      console.log('继续执行') // 依然会走到这里
      console.log(result) // 此时是undefined,因为上一个then没有返回一个Promise
  })

返回自己定义的值

setDelay(2000)
  .then(result => {
      let msg = '第一步的值'
      return Promise.resolve(msg)
  })
  .then(result => {
      console.log('上一步的值 >>> ', result)
  })
  .catch(err => {
      console.log(err)
  })

跳出Promise链式

setDelay(2000)
.then(result => {
    return setDelaySecond(1)
})
.then(result => {
    console.log('主动跳出链式')
    return Promise.reject('跳出链式msg')
})
.then(result => {
    console.log('不执行了')
})
.catch(msg => {
    console.log('跳出的失败结果 >>> ', msg)
})

串行

arr = [setDelay, setDelay, setDelay]
  arr[0](1000)
  .then(result => {
      console.log(result)
      return arr[1](1000)
  })
  .then(result => {
      console.log(result)
      return arr[2](1000)
  })
  .then(result => {
      console.log(result)
  })

for循环中的串行

for 循环中的串行, 并且逐一将结果推入数组

var res = []
// 初始化
var chain = setDelay(1000 + Math.ceil(Math.random() * 1000))
for (let i = 0; i < 8; i++) {
    // 更新Promise链
    chain = chain.then(result => {
        // 封装结果
        res.push(result + ' >>> ' + i)
        // 查看结果
        console.log(res)
        // 执行下一次函数,并且将Promise的结果向下传递
        return setDelay(1000 + Math.ceil(Math.random() * 1000))
    })
}

通过setDelay的睡眠

const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))

// 方法调用
await sleep(1000)

TypeScript

入门

npm install -g typescript

编写 ts 代码

function hello(msg: string) {
  console.log(msg)
}

hello('hello,world')

执行 tsc 编译命令

tsc xxx.ts

编译生成 js 代码,编译后进行了类型擦除

function hello(msg) {
    console.log(msg);
}
hello('hello,world');

类型

类型 备注
字符串类型 string
数字类型 number
布尔类型 boolean
数组类型 number[],string[], boolean[] 依此类推
任意类型 any 相当于又回到了没有类型的时代
复杂类型 type 与 interface
函数类型 () => void 对函数的参数和返回值进行说明
字面量类型 “a”|“b”|“c” 限制变量或参数的取值
nullish类型 null 与 undefined
泛型 <T><T extends 父类型>

复杂类型

type
type Cat = {
  name: string,
  age: number
}

const c1: Cat = { name: '小白', age: 1 }
const c2: Cat = { name: '小花' }					  // 错误: 缺少 age 属性
const c3: Cat = { name: '小黑', age: 1, sex: '公' } // 错误: 多出 sex 属性
interface
interface Cat {
  name: string,
  age: number
}

const c1: Cat = { name: '小白', age: 1 }
const c2: Cat = { name: '小花' }					  // 错误: 缺少 age 属性
const c3: Cat = { name: '小黑', age: 1, sex: '公' } // 错误: 多出 sex 属性
可选属性

如果需要某个属性可选,可以用下面的语法

interface Cat {
  name: string,
  age?: number
}

const c1: Cat = { name: '小白', age: 1 }
const c2: Cat = { name: '小花' }					  // 正确: age 属性可选
  • 可选属性要注意处理 undefined 值
鸭子类型
interface Cat {
  name: string
}

function test(cat: Cat) {
  console.log(cat.name)
}

const c1 = { name: '小白', age: 1 } 
test(c1)
  • const c1 并没有声明类型为 Cat,但它与 Cat 类型有一样的属性,也可以被当作是 Cat 类型

方法类型

interface Api {
  foo(): void,
  bar(str: string): string
}

function test(api: Api) {
  api.foo()
  console.log(api.bar('hello'))
}

test({
  foo() { console.log('ok') },
  bar(str: string) { return str.toUpperCase() }
})

字面量类型

function printText(s: string, alignment: "left" | "right" | "center") {
  console.log(s, alignment)
}

printText('hello', 'left')
printText('hello', 'aaa') // 错误: 取值只能是 left | right | center

nullish 类型

function test(x?: string | null) {
  console.log(x?.toUpperCase())
}

test('aaa')
test(null)
test()
  • x?: string | null 表示可能是 undefined 或者是 string 或者是 null

泛型

下面的几个类型声明显然有一定的相似性

interface RefString {
  value: string
}

interface RefNumber {
  value: number
}

interface RefBoolean {
  value: boolean
}

const r1: RefString = { value: 'hello' }
const r2: RefNumber = { value: 123 }
const r3: RefBoolean = { value: true }

可以改进为

interface Ref<T> {
  value: T
}

const r1: Ref<string> = { value: 'hello' }
const r2: Ref<number> = { value: 123 }
const r3: Ref<boolean> = { value: true }
  • 泛型的要点就是 <类型参数>,把【类型】也当作一个变化的要素,像参数一样传递过来,这样就可以派生出结构相似的新类型

函数定义也支持泛型

function ref<T>(n: T): Ref<T> {
  return { value: n }
}

const v1 = ref("hello"); 	// Ref<string>
const v2 = ref(123.3333);	// Ref<number>

v1.value.toLocaleLowerCase()
v2.value.toFixed(2)

关于 TypeScript 与 JavaScript 中的类语法不是重点,class 相关语法只是起到辅助作用,更重要的是前面讲的 interface

基本语法

class User {
    name: string;
    
    constructor(name: string) {
        this.name = name
    }
}

const u = new User('张三')

其实会被编译成这个样子(默认 --target=es3)

var User = /** @class */ (function () {
    function User(name) {
        this.name = name;
    }
    return User;
}());
var u = new User('张三');

所以 js 中的 class,并不等价于 java 中的 class,它还是基于原型实现的,原理参考第二章(036、037)

只读属性

class User {
  readonly name: string;
  
  constructor(name: string) {
      this.name = name
  }
}

const u = new User('张三')
u.name = '李四'				// 编译错误
  • readonly 是 typescript 特有的,表示该属性只读

方法

class User {
  readonly name: string;
  
  constructor(name: string) {
      this.name = name
  }

  study() {
    console.log(`[${this.name}]正在学习`)
  }
}

const u = new User('张三')
u.study()

get,set

class User {
  _name: string;

  constructor(name: string) {
    this._name = name
  }

  get name() {
    return this._name
  }

  set name(name: string) {
    this._name = name
  }
}

const u = new User('张三')
console.log(u.name)
u.name = '李四'
console.log(u.name)
  • 注意,需要在编译时加上 tsc --target es6 .\xxx.ts 选项
  • es6 等价于 es2015,再此之上还有 es2016 … es2022

类与接口

interface User {
  name: string
  study(course: string): void
}

class UserImpl implements User {
  name: string;
  constructor(name: string) {
    this.name = name
  }
  study(course: string) {
    console.log(`[${this.name}]正在学习[${course}]`)
  }
  foo() { }
}

const user: User = new UserImpl('张三')
user.study('Typescript')
user.foo() // 错误,必须是接口中定义的方法

继承与接口

interface Flyable {
  fly(): void
}

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name
  }
}

class Bird extends Animal implements Flyable {
  fly() {
    console.log(`${this.name}在飞翔`)
  }
}

const b: Flyable & Animal = new Bird("小花")
b.fly()
  • Flyable & Animal 表示变量是 flyable 类型,同时也是 Animal 类型

方法重写

class Father {
  study(): void {
    console.log(`father study`)
  }
}

class Son extends Father {  
  study(): void {
    super.study()
    console.log(`son study`)
  }
}

const f: Father = new Son()
f.study()

文章作者: 👑Dee👑
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 👑Dee👑 !
  目录