# JavaScript

# js中的位运算符的使用方法

  1. & 按位与 & 是二元运算符,它以特定的方式的方式组合操作数中对应的位,如果对应的位都为1,那么结果就是1, 如果任意一个位是0 则结果就是0。

    1 & 3的结果为1

    那我们来看看他是怎么运行的

    1的二进制表示为 0 0 0 0 0 0 1

    3的二进制表示为 0 0 0 0 0 1 1

    根据 & 的规则 得到的结果为 0 0 0 0 0 0 0 1,十进制表示就是1

  2. | 按位或 | 运算符跟&的区别在于如果对应的位中任一个操作数为1 那么结果就是1。

    1的二进制表示为 0 0 0 0 0 0 1

    3的二进制表示为 0 0 0 0 0 1 1

    所以 1 | 3的结果为3

  3. ^ 按位异或 ^运算符跟|类似,但有一点不同的是 如果两个操作位都为1的话,结果产生0。

    1的二进制表示为 0 0 0 0 0 0 1

    3的二进制表示为 0 0 0 0 0 1 1

    所以 1 ^ 3的结果为2

  4. ~ 按位非 ~运算符是对位求反,1变0,0变1,也就是求二进制的反码

    1的二进制表示为 0 0 0 0 0 0 1

    所以 ~1 的结果是-2

  5. >> 右移 >>运算符使指定值的二进制所有位都右移规定的次数,对于其移动规则只需记住符号位不变,左边补上符号位即按二进制形式把所有的数字向右移动对应的位数,低位移出(舍弃),高位的空位补符号位,即正数补零,负数补1。

    1的二进制表示为 0 0 0 0 0 0 1

    所以 1>>1的结果为0

  6. << 左移 <<运算符使指定值的二进制所有位都左移规定的次数,对于其移动规则只需记住丢弃最高位,0补最低位即按二进制形式把所有的数字向左移动对应的位数,高位移出(舍弃),低位的空位补零。

    1的二进制表示为 0 0 0 0 0 0 1

    所以 1<<1的结果为2 7、>>> 无符号右移

# js中位运算符的妙用

  1. 使用&运算符判断一个数的奇偶

    偶数 & 1 = 0

    奇数 & 1 = 1

    那么0&1=0,1&1=1

  2. 使用~~,>>,<<,>>>,|来取整

    ~~3.14 = 3

    3.14 >> 0 = 3

    3.14 << 0 = 3 3.14 | 0 = 3 3.14 >>> 0 = 3(>>>不可对负数取整)

    注意:~~-3.14 = -3 其它的一样

  3. 使用<<,>>来计算乘除

    乘法:

    1*2 = 2

    1<>1 = 1(2/2的一次方)

  4. 利用^来完成比较两个数是否相等

    1 ^ 1 = 0

    1 ^ 非1数 !=0

    所以同一个数……同一个数等于0,否则不等于0

  5. 使用^来完成值交换

    a = 1

    b = 2

    a ^= b

    b ^= a

    a ^= b

    结果a=2,b=1

  6. 使用&,>>,|来完成rgb值和16进制颜色值之间的转换

  • 16进制颜色值转RGB:
    function hexToRGB(hex){
        var hex = hex.replace("#","0x"),
            r = hex >> 16,
            g = hex >> 8 & 0xff,
            b = hex & 0xff;
        return "rgb("+r+","+g+","+b+")";
    }
1
2
3
4
5
6
7
  • RGB转16进制颜色值:
    function RGBToHex(rgb){
        var rgbArr = rgb.split(/[^\d]+/),
            color = rgbArr[1]<<16 | rgbArr[2]<<8 | rgbArr[3];
        return "#"+color.toString(16);
    }
1
2
3
4
5

运行hexToRGB("#ffffff")返回"rgb(255,255,255)"

运行RGBToHex("rgb(255,255,255)")返回"#ffffff"

# 循环

  1. for循环

代码略

支持break、continue语法,break终止循环,continue跳过本次循环

  1. forEach

代码略

不支持break、continue语法,forEach循环不接受控制,必须从头遍历到尾

  1. every
    let arr = [1,2,3,4,5]

    arr.every((item) => {
        console.log(item)
        return true
    })
1
2
3
4
5
6

every的语法形式与forEach类似,同样接受一个function参数,但不同的是every需要一个返回值,当返回值为true的时候才会执行下一次循环,默认返回false,也就是说every与forEach不同点在于every接受程序控制,可以使用返回值达到break、continue的效果

  1. for in
    let arr = [1,2,3,4,5]

    for(let index in arr) {
        console.log(arr[index])
    }
1
2
3
4
5

forin也可以用来循环数组,但存在问题,因为forin是为遍历对象设计的,数组之所以可以被遍历,因为数组也是一个对象,对象可以被追加属性,例如在上例中如果为arr追加属性a,arr.a = 6,执行forin后a属性也会被遍历出来,但对于数组中我们理解的索引值a并不合法

forin同样支持break、continue语法

注意,对于forin中的自定义变量index,是索引,或者说是属性名,是一个字符串类型,判断时要注意变量类型

  1. for of
    let arr = [1,2,3,4,5]

    for(let item of arr) {
        console.log(item)
    }
1
2
3
4
5

for of为ES6新增语法,主要为解决自定义数据类型遍历问题,同样可以遍历数组,只是其功能要比上述其他遍历方法更强大

# 数组

# 伪数组

伪数组需要满足两个条件

  1. 元素按索引排列
  2. 具有length属性
    {0: 'a', 1: 'b', length: 2}
    {length: 5}

    // 这些都是伪数组
1
2
3
4

伪数组不可以被遍历,如果想像数组一样被遍历,需要转化为数组,例如获取到的NodeList、获取不定参arguments

ES5中转化伪数组使用:

    let arg = [].slice.call(arguments)
    let nodes = [].slice.call(document.querySelectorAll('img'))
1
2

# Array.from(ArrayLike, mapFn, this)

Array.from是ES6中新增语法,接收三个参数,第一个是伪数组,第二个是遍历函数,第三个是遍历函数当中的this指向

例如转换伪数组,可以用Array.from来做

    let arg = Array.from(arguments)
1

例如需要初始化一个长度为5且每位值为1的数组,第一个参数传递了一个伪数组,指定长度为5,第二个参数传递了一个遍历函数,因为数组长度为5,所以遍历函数会被执行5次,并且每次循环返回了1,为数组中每个元素赋值为1

    let arr = Array.form({ length: 5 }, () => {
        return 1
    })
1
2
3

# Array.of()

Array.of()用于生成新数组,接收一个或多个参数,指定元素内容,从结果来看与字面量写法相同。

    // 字面量写法
    let array = [1,2,3,4,5]
    // Array.of
    let array = Array.of(1, 2, 3, 4, 5)
1
2
3
4

# Array.prototype.fill(value, start, end)

Array.fill()顾名思义是用来填充数组的,第一个参数是要填充的值,后面两个参数为可选参数,默认为0和array.length - 1,用来指定填充的起始和终止位置。

    // 假设要初始化一个长度为5且元素均为0的数组
    let arr = Array(5).fill(0)

    // 假设需要修改数组中某些位置的值 索引2、3改为0
    let arr = Array.of(1, 2, 3, 4, 5)
    arr.fill(0, 2, 4)
    // 从这里可以看出,填充是包含start索引,不包含end索引的
1
2
3
4
5
6
7

# Array.prototype.find()

Array.find()是用来查找数组中元素的,与其对位的ES5种filter方法,他们之间的区别是:

  1. Array.filter() 方法的返回值是数组,包含了数组中所有符合条件的元素,当数组很长时,会消耗时间遍历所有元素;
  2. Array.find() 方法的返回值是被找到的元素,如果没有找到符合条件的元素则返回undefined,find()方法不会遍历完整数组,当找到第一个符合条件的元素时直接返回,后面的不会被遍历。
    let arr = [1, 2, 3, 4, 5]
    let find = arr.filter(function(item) => {
        return item % 2 === 0
    })
    // 2,4
    console.log(find)
    
    find = arr.find(function(item) => {
        return item % 2 === 0
    })
    // 2
    console.log(find)
1
2
3
4
5
6
7
8
9
10
11
12

# Array.prototype.findIndex()

findIndex() 与上面find() 类似,是用来找目标索引的

    let arr = [1, 2, 3, 4, 5]
    
    find = arr.findIndex(function(item) => {
        return item % 2 === 0
    })
    // 1
    console.log(find)
1
2
3
4
5
6
7

# Class

由于ES5以前是没有class的,所以面向对象编程都是以Function来实现的,主要缺点就是写起来比较麻烦,类的方法要挂在原型链上。 ES6中新增加了Class的概念,让类的定义变得更加简单,更像面向对象,注意这里用的是“像”,因为本质来讲ES6新增的Class属于语法糖,与Function除了写法简洁没什么其他的区别。

    Class Animal {
        constructor (type) {
            this.type = type
        }
        eat () {
            console.log(this.type + ' eat')
        }
        // getter setter
        get name () {
            return this.name
        }
        set name (val) {
            this._name = val
        }
        // 静态方法
        static talk () {
            console.log('talk')
        }
    }
    // 继承
    Class Dog extends Animal {
        constructor (type, age) {
            // 调用父类构造方法
            super(type)
            this.age = age
        }
        run () {
            console.log(this.type + ' ' + this.age + ' run')
        }
    }
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

# Function

# 函数参数默认值

    function f(x, y = 1, z = x + y) {}

    // 使用默认值可以用undefined赋值
    f(2, undefined, 3)
1
2
3
4

# 获取不定参

# Rest Parameter

在ES5中获取不定参使用arguments,但在ES6中已经被废弃,可以使用Rest Parameter来获取不定参

    function sum (base, ...nums) {
        let num = base
        nums.forEach(function (item) {
            num += item * 1
        })
        return num
    }

    console.log(sum(30, 1, 2, 3))// 36
    console.log(sum(30, 1, 2, 3, 4))// 40
1
2
3
4
5
6
7
8
9
10

或者可以继续使用Function.length来获取参数数量,但是需要注意,Function.length获取的事定参数量,也就是第一个默认参数前面的变量数

    function test (a = 2, b = 1, c) {
        console.log(test.length)
    }
    test('a', 'b')// 0
1
2
3
4

# Spread Operator

Spread Operator 和 Rest Parameter 是形似但相反意义的操作符,简单的来说 Rest Parameter 是把不定的参数“收敛”到数组,而 Spread Operator 是把固定的数组内容“打散”到对应的参数。示例如下:

    function sum (x = 1, y = 2, z = 3) {
        return x + y + z
    }

    console.log(sum(...[4]))// 9
    console.log(sum(...[4, 5]))// 12
    console.log(sum(...[4, 5, 6]))// 15
1
2
3
4
5
6
7

# 箭头函数

ES6中新增箭头函数,为常规函数的简写形式,需要注意的是,箭头函数并不修改this指向,定义函数时的this指向即为函数内部的this指向

    let hello = name => {
        console.log('say hello', name)
    }

    // 返回值为表达式可以省略return和{}
    let pow = x => x * x

    // 返回值是字面量对象,需要用小括号包裹起来
    let person = (name) => ({
        age: 20,
        addr: 'Beijing City'
    })

    // this指向定义时的this,所以下例中this指向test外层的window,window没有name属性,所以返回undefined
    let test = {
        name: 'test',
        say: () => {
            console.log(this.name, this)
        }
    }
    console.log(test.say())// undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# Object

# Object简写

    var x = 0, y = 0
    obj = { 
        x,
        y 
    }
1
2
3
4
5

# 使用表达式定义key

    let obj = {
        foo: 'bar',
        (['baz'+ quux()]): 42
    }
1
2
3
4

# 函数简写

    let obj = {
        foo (a, b) {

        },
        bar (x, y) {

        },
        * quux (x, y) {

        }
    }
1
2
3
4
5
6
7
8
9
10
11

# Object.assign()

此函数用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,返回目标对象。

    Object.assign(target, ...sources)

    const target = { a: 1, b: 2 }
    const source = { b: 4, c: 5 }

    const returnedTarget = Object.assign(target, source)

    console.log(target)
    // expected output: Object { a: 1, b: 4, c: 5 }

    console.log(returnedTarget)
    // expected output: Object { a: 1, b: 4, c: 5 }
1
2
3
4
5
6
7
8
9
10
11
12

从语法中可以看出源对象的个数是不限制的,如果是0个,直接返回目标对象,如果是多个,相同的属性会被后面的源对象覆盖。

# Set 新增数据结构

Set数据结构可以理解为集合,其中存储的数据是完全去重的,参数必须为可遍历的,数组或自定义的可遍历结构

    let s = new Set([1, 2, 3])

    // 添加数据
    s.add('hello').add('world')

    // 删除数据
    s.delete('hello')

    // 删除全部数据
    s.clear()

    // 查找数据
    s.has('hello')

    // 统计数据
    s.size

    // 返回所有键名
    s.keys()

    // 返回所有键值
    s.values()

    // 返回所有键值对
    s.entries()
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

# Map 新增数据结构

Map数据结构可以理解为字典,由键值对组成,参数应为可遍历对象,其元素为键值对

    let map = new Map([['a', 1],['b', 2]])

    // 添加数据,已存在的键值会被修改
    map.set('a', 2)
    map.set('c', 3)

    // 删除数据
    map.delete('c')

    // 删除全部数据
    map.clear()

    // 统计数据
    map.size

    // 查找数据
    map.has('c')

    // 查询值
    map.get('b')

    // 返回所有键名
    map.keys()

    // 返回所有键值
    map.values()

    // 返回所有键值对
    map.entries()
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

Map数据类型与Object有什么区别?

  1. Object的键可以是字符串或Symbols,而Map的键可以为任意值,比如函数,对象;
  2. Map中的键值是有序的,而添加到对象中的键不是,因此,当对Map遍历的时候,是按照插入的顺序返回键值;
  3. Map可以通过size来快速获取键值对个数,而Object想要获取键值对个数只能手动计算;
  4. Map 可直接进行迭代,而 Object 的迭代需要先获取它的键数组,然后再进行迭代;
  5. Map在涉及频繁增删键值对的场景下会较Object更有性能优势。

# 正则表达式

# y修饰符

y修饰符意为粘连(Sticky),与g修饰符类似,但不同的是g修饰符多次匹配后只要剩余字符串中存在匹配成功即可,y修饰符要求多次匹配必须从剩余字符串的首位开始符合正则表达式,否则匹配失败。

const s = 'aaa_aa_a'
const r1 = /a+/g
const r2 = /a+/y

r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]

r1.exec(s) // ["aa"]
r2.exec(s) // null
1
2
3
4
5
6
7
8
9

上面代码有两个正则表达式,一个使用g修饰符,另一个使用y修饰符。这两个正则表达式各执行了两次,第一次执行的时候,两者行为相同,剩余字符串都是_aa_a。由于g修饰没有位置要求,所以第二次执行会返回结果,而y修饰符要求匹配必须从头部开始,所以返回null。

# u修饰符

u修饰符含义为unicode模式,用来处理大于\uFFFF的unicode字符,因为汉字中有些生僻字的UTF-16编码为四个字节,例如\uD83D\uDC2A

/^\uD83D/u.test('\uD83D\uDC2A')
// false
/^\uD83D/.test('\uD83D\uDC2A')
// true
1
2
3
4

但是,ES5不支持四个字节的UTF-16编码,会将其识别为两个字符,导致第二行代码结果为true。加了u修饰符以后,ES6就会识别其为一个字符,所以第一行代码结果为false。

简单来说,所有涉及到中文的正则表达式,都加上u修饰符,避免出现码点大于\uFFFF而不能正确匹配的情况。

# 异步操作 Promise

在ES5中如果存在大量异步操作,很容易引发回调地狱,举个例子,要求顺序加载三个js,就会按照如下的套娃形式:

    loadScript('./js/1.js', function() {
        loadScript('./js/2.js', function() {
            loadScript('./js/3.js', function() {
                // ......一步一步嵌套下去
            })
        })
    })
1
2
3
4
5
6
7

ES6中引入Promse很好的解决了这个问题

    function loadScript(src) {
        return new Promise((resolve, reject) => {
            let script = document.createElement('script')
            script.src = src
            document.head.append(script)
            script.onload = () => resolve(src)
            script.onerror = err => reject(err)

        })
    }

    loadScript('./js/1.js')
        .then(() => {
            loadScript('./js/4.js')
        }, (err) => {
            console.log(err)
        })
        .then(() => {
            loadScript('./js/3.js')
        }, (err) => {
            console.log(err)
        })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Promies接收两个参数分别是resolve和reject,代表成功和失败的回调,一个Promise存在以下三种状态之一:

  1. 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
  2. 已兑现(fulfilled):意味着操作成功完成。
  3. 已拒绝(rejected):意味着操作失败。

Promise具有凝固性,即只能由pending转为fulfilled或者rejected,一旦状态改变,就固定下来不可以再次被改变。 当Promise的状态改变之后,Promise的then方法就会被调用,根据状态不同,执行then的函数也不同。

# Promise.prototype.then(onFulfilled, onRejected)

是Promise原型链上的方法,接收的参数一是成功后的方法,二是失败后的方法,第二个参数可选。

# 链式调用规则

  1. Promise对象若无返回值的时候,会默认返回一个新的Promise,并且不定义的话自动返回fulfilled
  2. Promise对象若返回值不是一个Promise,那么将会自动转换成一个fulfilled的Promise对象
  3. Promise对象可以手动返回一个新的Promise,其状态可以我们自己决定,通过三元表达式方式
  4. Promise对象若抛出错误error,则返回一个reject的Promise对象,将其作为参数传递给下一个Promise链的then()的第二个参数或者是catch()方法
  5. Promise的值可以发生穿透现象,即使中间的then()没有回调参数,那么下面的then()都可以接收该参数
  6. Promise只能传递一个参数,如果传递多个的话,必须用数组或对象进行封装,否则多余参数都将返回undefined

# Promise 静态方法

# resolve & reject

假设点击按钮后需要从后端取到数据,但用户可能会多次点击,为了消除冗余请求,在第一次请求到数据之后就会缓存下来,第二次可以判断如果有值直接返回,此时可以使用promise提供的静态方法来快速返回数据,并且不影响后续then操作。

    function getUserInfo() {
        if(!userinfo) {
            return new Promise(resolve, reject) {
                // 请求数据
                resolve(userinfo)
            }
        } else {
            return Promise.resolve(userinfo)
        }
    }

    getUserInfo().then(()=>{})
1
2
3
4
5
6
7
8
9
10
11
12

# Catch

catch方法用于统一捕获reject状态,假设有多次链式操作,每个then都要处理reject就显得很麻烦,可以使用catch一次捕获。

     loadScript('./js/1.js')
        .then(() => {
            loadScript('./js/4.js')
        })
        .then(() => {
            loadScript('./js/3.js')
        })
        .catch(err) {
            console.log(err)
        }
1
2
3
4
5
6
7
8
9
10

# All

All也是Promise提供的静态方法,目的是同步调用,假设需要请求一批数据但先后顺序无所谓时,可以使用all同步请求节省时间。 All接受一个可遍历的对象,为所有需要同步操作的Promise,结果会统一放在value中。

    let p1 = new Promise(resolve, reject) {
        resolve(1)
    }

    let p2 = new Promise(resolve, reject) {
        resolve(2)
    }

    let p3 = new Promise(resolve, reject) {
        resolve(3)
    }

    Promise.all([p1, p2, p3]).then((value) => {})
1
2
3
4
5
6
7
8
9
10
11
12
13

# Race

Race也是Promise提供的静态方法,用来解决多源优先加载问题,例如一个图片有多个CDN地址,可以同时发起请求返回速度最快的那个。 Race接受的参数与All相同,都是可遍历对象包含Promise, 结果只会返回Promise中返回最快的。

    let p1 = new Promise(resolve, reject) {
        setTimeout(() => {
            resolve(1)
        }, 1000)
    }

    let p2 = new Promise(resolve, reject) {
        setTimeout(() => {
            resolve(2)
        }, 0)
    }

    // 2
    Promise.race([p1, p2]).then((value) => {})  
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Reflect 反射机制

Reflect是一个内置对象,它提供拦截Javascript操作的方法,这些方法与处理器对象的方法相同。Reflect不是一个函数对象,因此它是不可构造的。 与大多数全局对象不同,Reflect没有构造函数,所以不能将其与new一起使用,Reflect所有属性和方法都是静态的。

# Reflect.apply()

Reflect.apply(target, thisArgument, argumentList) 使用指定的参数列表发起对目标函数的调用

target是目标函数,thisArgument是规定执行目标函数时的this指向,argumentList是调用目标函数时的参数列表,这个参数是一个类数组对象,后两个参数为可选参数。返回值是调用完成目标函数后的返回结果

Reflect.apply(Math.floor, undefined, [2.5])
// 意为使用2.5这一个参数,去调用Math.floor函数向下取整
1
2

# Reflect.construct()

Reflect.construct(target, argumentList[, newTarget]) 操作相当于new target(argumentList)

target是被运行的目标构造函数,argumentList是调用目标构造函数时的参数列表,这个参数是一个类数组对象。返回值是以target函数为构造函数,argumentList为其初始化参数的对象实例。

newTarget参数为构造函数,如果传递了这个参数,则表示继承了newTarget这个超类。

    new Foo(...args)
    Reflect.conscruct(Foo. args);

    function someConstructor () {}
    ver result = Reflect.construct(Array, [], someConstructor)

    Reflect.getPrototypeOf(result)   // someContrustor.prototype
    Array.isArray(result)   // true
1
2
3
4
5
6
7
8

# Reflect.defineProperty()

Reflect.defineProperty(target, propertyKey, attributes) 静态方法,基本等同于Object.defineProperty(),唯一不同的是返回Bool值。用于向对象中直接添加一个属性,或者修改一个对象的现有属性。

target: 目标对象 propertyKey: 要定义或修改的属性名称 attributes: 要定义或修改的属性描述

    const student = {}
    Reflect.defineProperty(student, 'name', {
        value: 'Mike'
    })

    console.log(student.name)  // Mike
1
2
3
4
5
6

# Reflect.deleteProperty()

Reflect.deleteProperty(target, propertyKey)

删除一个对象上的属性,返回Bool值来确定是否成功。如果属性不存在返回true,如果属性被设置Object.freeze(obj)不可配置,返回false.

target: 目标对象 propertyKey: 将被删除的属性名称

    let obj = {
        x: 1,
        y: 2
    }

    Reflect.deleteProperty(obj, 'x')

    console.log(obj)    //{y: 2}
1
2
3
4
5
6
7
8

# Reflect.get()

Reflect.get(target, propertyKey[, receiver]) 用于从对象中获取属性的值

target: 目标对象 propertyKey: 需要获取值的属性名称 receiver: 如果遇到getter,此值将提供给目标调用

    // Object
    let obj = {
            x: 1,
            y: 2
        }
    Reflect.get(obj, 'x')    // 1

    // Array
    Reflect.get([1, 2], 1)    // 2


1
2
3
4
5
6
7
8
9
10
11

# Reflect.getOwnPropertyDescriptor()

Reflect.getOwnPropertyDescriptor(target, propertyKey) 静态方法,与Object.getOwnPropertyDescriptor()类似,如果propertyKey在对象中存在,返回属性描述符,否则返回undefined。

target: 目标对象 propertyKey: 获取属性描述符的属性名称

    Reflect.getOwnPropertyDescriptor({ x: 1}, 'x')
    // {value: 1, writable: true, enumerable: true, configurable: true}
1
2

Reflect.getOwnPropertyDescriptor()与Object.getOwnPropertyDescriptor()对比:

如果Reflect.getOwnPropertyDescriptor()的第一个参数不是一个对象,那么会造成TypeError错误,如果是Object.getOwnPropertyDesriptor()的以一个参数不是一个对象,那么会强制转换为对象处理。

# Reflect.getPropertyOf()

Reflect.getPrototypeOf(target) 静态方法,与Object.getPropertyOf()方法一样,返回指定对象的原型对象,即为prototype

target: 目标对象

# Reflect.has()

Reflect.has(target, propertyKey) 用于检查对象上是否有某个属性

target: 目标对象 propertyKey: 要检查的属性名

# Reflect.isExtensible()

Reflect.isExtensible(target) 用于检查一个对象是否是可扩展的,即是否可以添加新的属性,与Object.isExtensible()一样。

target: 目标对象

# Reflect.ownKeys()

Reflect.ownKeys(target) 返回由目标对象自身属性键构成的数组

target: 目标对象

# Reflect.preventExtensions()

Reflect.preventExtensions(target) 用于阻止新属性添加到对象。

target: 目标对象

    let obj = {}

    obj.isExtensiable(obj)     // true
    obj.preventExtensions(obj)
    obj.isExtensiable(obj)     // false
1
2
3
4
5

# Reflect.set()

Reflect.set(target, propertyKey, value[, receiver])

在对象上设置属性,返回一个Bool值来表示是否成功设置属性。

target: 目标对象 propertyKey: 设置的属性名称 value: 设置的属性值 receiver: 如果遇到setter, this将提供给目标调用

Reflect.set()还可以用于截断数组

    let arr = [1, 2, 3]
    Reflect.set(arr, 'length', 1)
    console.log(arr)    // [1]
1
2
3

当Reflect.set()只有一个参数target时,propertyKey和value值是undefined

    let obj = {}
    Reflect.set(obj)
    Reflect.getOwnPropertyDescriptor(obj, 'undefined')
    // { value: undefined, writable: true, enumerable: true, configurable: true }
1
2
3
4

# Reflect.setPropertyOf()

Reflect.setPropertyOf(target, prototype) 用于改变对象的原型对象,即为prototype,返回一个Bool值表示设置是否成功。

target: 目标对象 prototype: 新的原型对象

    const object1 = {};

    console.log(Reflect.setPrototypeOf(object1, Object.prototype));
    // expected output: true

    console.log(Reflect.setPrototypeOf(object1, null));
    // expected output: true

    const object2 = {};

    console.log(Reflect.setPrototypeOf(Object.freeze(object2), null));
    // expected output: false
    // 因为Object.freeze()冻结了对象object2,使其无法被修改,所以想要更改其原型对象就是被禁止的操作,所以Reflect.setPrototypeOf()失败,返回False
1
2
3
4
5
6
7
8
9
10
11
12
13

# Proxy 代理

Proxy对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义。 Proxy(target, handler)

target: 要代理的目标对象 handler: 一个对象,其属性是当执行一个操作时定义代理的行为的函数

基本用法,例如使用Proxy拦截对于对象属性的读取和修改

    let obj = {
        price: 100,
        name: 'tooth'
    }

    // 这是一个包含商品进货信息的对象,假设我们期望对外展示商品名和售价,那么商品名正常输出,价格需要加上利润,此时可以使用Proxy做拦截
    // 同理如果想修改售价,我们需要保证售价不能低于进货价,所以要进行拦截

    let user_obj = new Proxy(obj, {
        get(target, property) {
            if (property == 'price') {
                return target[property] + 20
            }
                return target[property]
        },
        set(target, property, value) {
            if (property == 'price') {
                if (value <= 100) {
                    throw new TypeError('Price too low')
                }
            }
            target[property] = value
        }
    })

    console.log(user_obj.name, user_obj.price)
    user_obj.price = 120
    console.log(user_obj.name, user_obj.price)
    user_obj.price = 80
    console.log(user_obj.name, user_obj.price)
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

# 使用案例:定义一个类,使每个实例对象都有自己的id,且只读

    class A {
        constructor() {
            this.proxy = new Proxy({
                id: Math.random().toString(36).slice(-8)
            }, {})
        }
        get id() {
            return this.proxy.id
        }
    }

    let s = new A()
    console.log(s.id)

    // id没有定义setter方法,所以无法修改
    s.id = 2
    console.log(s.id)

    // proxy作为对象属性,没有拦截set,所以是可以修改的
    s.proxy.id = '123'
    console.log(s.proxy.id)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 临时代理

Proxy.revocable()

当代理的revoke函数被调用后,代理就被取消掉了

需要注意的是,当调用Proxy.revocable()时返回的对象与new Proxy()不同,Proxy.revocable()返回的proxy属性才是被代理的对象。

    let obj = {
        price: 100,
        name: 'tooth'
    }

    let p = Proxy.revocable(obj, {
        get (target, property) {
            return target[property]
        }
    })

    let data = p.proxy

    console.log(data.price)

    p.revoke()

    console.log(data.name)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
最后更新:: 3/12/2024, 3:17:51 PM