保护对象、Object.create、bind、数组API、严格模式

203 阅读7分钟

1. 保护对象

1.1. 为什么
  • JS 中的对象可随意修改属性值, 可随意添加 / 删除属性
1.2. 如何保护
1.2.1 对象属性的保护

保护对属性值的修改

  • 对象的属性分为
    • 命名属性: 可直接通过 . 访问到的属性
      • 数据属性: 实际存储属性值的属性
      • 访问属性: 不实际存储属性值, 专门对其数据属性的提供保护
    • 内部属性: 无法通过 . 访问到的属性
  1. 对象命名属性的保护

    命名属性: 可直接通过 . 访问到的属性

    1. 对命名属性中数据属性的保护:

      数据属性: 实际存储属性值的属性

      • 四大特性:

        1. value: 实际存储属性值

        2. writable: 控制属性是否可修改

          • 取值: false / true
        3. enumerable: 控制属性是否可遍历

          • 取值: false / true
          • 仅控制遍历, 无法控制访问 -> 依然可以用 . 访问
        4. configurable:

          1. 控制是否可删除属性

          2. 控制是否可修改其他两个特性

          • 强调: configurable 经常作为前两个特性的双重保险, 一旦设置为 false, 不可逆

      • 如何查看四大特性:

        • Object.getOwnPropertyDescriptor(obj, '属性名')
      • 如何修改四大特性:

        Object.defineProperty(obj, '属性名', {
           特性: 值,
           特性: 值
           ......
        })
        
        • 问题: defineProperty 一次只能修改一个属性

          • 解决: 同时修改多个属性:

            Object.defineProperties(obj, {
              属性名: {
                特性: 值,
                ......
              },
                属性名: {
                  特性: 值
                  ......
                }
            })
            
            • 问题: 无法使用自定义逻辑保护属性
              • 解决: 使用访问属性
    2. 对命名属性种访问属性的保护:

      访问属性: 不实际存储属性值, 专门对其数据属性的提供保护

      • 何时: 只要用自定义逻辑保护属性时

      • 如何: 2 步

        1. 定义一个隐藏的数据属性, 实际存储属性值

        2. 添加访问属性保护隐藏的数据属性:

          Object.defineProperty(obj, '属性', {
            get() { // 在试图获取属性值时自动调用
              // 返回受保护的数据属性值
            },
            set(val){ // 在试图修改属性值时自动调用
              //  参数 val 会自动获得要修改的新值
              // 如果验证 val 符合规则, 才将 val 赋值给受保护的属性
              // 否则 报错
            },
            enumerable: true,  // 控制属性是否可被遍历
            configurable: false //   1.控制是否可删除属性 2. 控制是否可修改其他两个特性
          })
          
        • Ex:
          var emp = {
            id: 1001,
            name: 'lcy',
            _age: 28
          }
          Object.defineProperty(emp, '_age', {
            enumerable: false,
            configurable: false
          })
          // 要求 age 必须介于 18 ~ 60 之间
          Object.defineproperty(emp, 'age', {
            get() {
              console.log('自动调用 get')
              return emp._age
            },
            set(val) {
              console.log('自动调用 set')
              if (val >= 18 && val <= 60) {
                this._age = val
              } else {
                throw new RangError('年龄必须介于 18 ~ 60 之间')
              }
            },
            enumerable: true,
            configurable: false
          })
          // 访问器属性和普通属性用法一致
          emp.age = emp.age + 1
          console.log(String(emp))
          emp.age = -2
          console.log(String(emp))
          
      • 如何使用访问器属性: 同普通属性用法完全一致

        • 其中: 赋值时自动调用 set, 取值时自动调用 get
  2. 对象内部属性的保护

    内部属性: 不能直接通过 . 访问的隐藏属性 -> Ex: __proto__

    • __proto__: Object.getPropertypeOf(obj)
1.2.2. 对象结构的保护
  • 防篡改: 保护对对象结构的修改

  • 3 个级别:

    1. 防扩展: 禁止添加新属性

      • Object.preventExtensions(obj)

      • 原理: 每个 obj 内部都有一个隐藏属性 Extensible

        • Extensible: 默认为 true
        • preventExtensions(obj): 将 obj 中的隐藏属性 Extensible 改为 false
    2. 密封: 在防扩展的基础上, 进一步禁止删除现有属性

      • Object.seal(obj)
      • 原理:
        1. obj 中隐藏属性 Extensible 修改为 false
        2. obj 中所有属性的 configurable 都修改为 false
    3. 冻结: 在密封的基础上, 进一步禁止修改任何成员

      • Object.freeze(obj)
      • 原理:
        1. obj 中隐藏属性 Extensible 修改为 false
        2. obj 中所有属性的 configurable 都修改为 false
        3. obj 中所有属性的 writable 都修改为 false

2. Object.create()

直接用一个父对象创建一个子对象

2.1. 如何
var child = Object.create(father, {
	自有属性: {
    value: 值,
    writable: true,
    enumerable: true,
    configurable: true
  },
  ......
})

强调: 只要添加到对象中的属性, 四大特性默认为 false, 显示必须修改为 true

2.2. 原理
  1. 创建一个新的空对象: var newObj = {}
  2. 让新的空对象自动继承父对象: Object.setPropertypeOf(newObj, father)
  3. 为新对象扩展新的自有属性: Object.defineProrperties(newObj, { ...... // 扩展新的自有属性 })
2.3. 何时
  • 如果没有构造函数, 只有父对象, 也想创建子对象时

3. bind

3.1. 什么是

基于一个现有函数, 创建一个新函数, 并永久绑定 this 为指定对象

3.2. 何时
  • 只有回调函数中的 this 不是想要的, 就可用 bind 替换函数中的 this
3.3. 如何
  • var newFun = fun.bind(obj): 基于现有函数 fun, 创建一个功能完全相同的新函数 newFun, 并永久绑定 newFun 中的 this 指向 obj

试题: call / apply / bind 区别

  • callapply: 强行 调用 一个函数并 临时 替换函数中的 this 为指定对象

    • 差别: 仅在参数上
      • call: 要求传入函数的参数, 必须单独用 , 分隔
      • apply: 要求传入函数的参数, 必须放入数组中整体传入 -> apply 可自动打散数组类型的参数
  • bind: 基于现有函数 创建 一个功能完全相同的函数, 并 永久 绑定 this 指向指定对象, 还可 永久 绑定部分固定参数值

  • 强调: 用 bind 绑定的 this, 不能在被 call / apply 替换

总结

  • 只要函数中的 this 不是想要的

    • call / apply: 如果立即执行函数
      • call: 如果参数单独传入
      • apply: 如果参数放在数组中传入
    • bind: 希望创建一个新函数作为回调函数给别人使用
  • Ex:

    function calc(base, bonusOne, bonusTwo) {
    	console.log(this.name + '的总工资是' + (base + bonusOne + bonusTwo))
    }
    var LiLei = { name: 'LiLet' }
    var HMM = { name: 'HanMeiMei' }
    // LiLei 临时借用函数 calc 
    calc.call(LiLei, 10000, 1000, 2000)
    // HMM 临时借用函数 calc
    calc.apply(HMM, [5000, 3000, 5000])
    // LiLei 新买了一个计算器
    var LiLeiNewCale = calc.bind(LiLei, 10000)
    LiLeiNewCalc(1000, 2000)
    

4. 数组 API

4.1. 判断

判断数组中元素是否符合要求

  • 返回值: Boolean
4.1.1. every

判断数组中是否每个元素都满足要求

  • arr.every(function (item, index, arr) { ...... return 判断条件 })
  • every 只要遇到不满足要求就停止向下执行
4.1.2. some

判断数组中是否包含满足要求元素

  • arr.some(function (item, index, arr) { ...... return 判断条件 })

  • some 只要遇到满足就停止向下执行

  • 强调: 数组 API 的回调函数中 this 默认指向 window -> 所以, 不能使用 this 指代当前元素值, 比如用 arr[index] / item 代替

  • Ex:

    var everyTestArr = [1, 2, 3, 4, 5]
    var someTestArr = [1, 2, 3, 4, 5]
    // 判断数组是否全由偶数组成
    var estimateEvenArr = everyTestArr.every(function (item, index, arr) {
      return item % 2 == 0
    })
    console.log(estimateEvenArr) // false
    // 判断数组是否升序排列
    var estimateASCArr = everyTestArr.every(function (item, index, arr) {
      return index < arr.length - 1 ? item <= arr[index + 1] : true
    })
    console.log(estimateASCArr) // true
    // 判断数组是否包含偶数
    var estimateHaveEven = someTestArr.some(function (item, index, arr) {
      return item % 2 == 0
    })
    console.log(estimateHaveEven) // true
    
4.2. 遍历
4.2.1. forEach

对原数组中每个元素执行相同的操作

  • arr.forEach(function (item, index, arr) { ......// 对当前元素执行的操作 })
4.2.2. map

取出原数组中每个元素, 执行相同操作后, 放入新数组中, 将新数组返回

  • var resultArr = arr.map(function (item, index, arr) { ...... return 对当前元素执行操作后的返回值 })

  • Ex:

    if (!('nmap' in Array.prototype)) {
      Array.prototype.map = function (fun) {
        for(var index = 0, returnArr = []; index < this.length; index++) {
          var newItem = fun(this[index], index, this)
          returnArr.push(newItem)
        }
        return returnArr
      }
    }
    var forEachTestArr = [1, 2, 3, 4, 5]
    var mapTestArr = [1, 2, 3, 4, 5]
    forEachTestArr.forEach(function (item, index, arr) {
      // item *= 2 // 错误: 按值传递, 修改副本不影响原值
      arr[index] = item * 2 // 或者 arr[index] *= 2
    })
    var mapResArr = mapTestArr.map(function (item, index, arr) {
      return item * 2
    })
    console.log(forEachTestArr) // [2, 4, 6, 8, 10]
    console.log(mapTestArr) // [1, 2, 3, 4, 5]
    console.log(mapResArr) // [2, 4, 6, 8, 10]
    
4.3. 过滤

复制出原数组中符合条件的元素组成新数组, 将新数组返回

  • var subArr = arr.filter(function (item, index, arr) { ...... return 判断条件 }

  • 筛选出 arr 中符合判断条件的元素, 放入新数组中, 将新数组返回

  • Ex:

    var filterTestArr = [1, 2, 3, 4, 5, 6]
    var filterResArr = filterTestArr.filter(function (item, index, arr) {
      return item % 2 == 0
    })
    console.log(filterTestArr) // [1, 2, 3, 4, 5, 6]
    console.log(filterResArr) // [2, 4, 6]
    
4.4. 汇总

将数组中的所有元素, 统计出一个汇总结果

var resValue = arr.reduce(function (prev, item, index, arr) { 
	......
  // prev: 截止目前的临时汇总值
  return prev + item
}, startVal)
  • 将数组 arr 中的每个值累加, 返回累加值

    • reduce 不一定非要从 0 开始累加, 可以指定值, 从指定值开始累加
      • startVal: 最开始的临时汇总值
  • Ex:

    var reduceTestArr = [1, 2, 3, 4, 5]
    var reduceArrSum = reduceTestArr.reduce(function (prev, item, index, arr) {
      return prev + item
    })
    console.log(reduceArrSum) // 15
    reduceTestArr = [2, 4, 6, 8, 10]
    reduceArrSum = reduceTestArr.reduce(function (prev, item, index, arr) {
      return prev + item
    }, reduceArrSum)
    console.log(reduceArrSum) // 45
    

5. 严格模式

5.1. 什么是

比普通 JS 运行模式更加严格的模式

5.2. 为什么
  • 解决普通 JS 运行模式中固有的弊端和缺陷
5.3. 何时: 2 种
  1. 新项目: 建议必须使用严格模式
  2. 旧项目: 建议逐个模块向严格模式迁移
5.4. 如何: 2 种
  1. 整个代码段启用严格模式
    • script 元素 / JS 的开头位置加入 use strict
  2. 仅对单个函数启用严格模式
    • function 内, 函数体的顶部加入 use strict
5.5. 严格模式的要求
  1. 不允许对未声明的变量赋值
  2. 静默失败升级为错误
  3. 不建议使用 argumentsarguments.callee 实现递归