1. 保护对象
1.1. 为什么
JS中的对象可随意修改属性值, 可随意添加 / 删除属性
1.2. 如何保护
1.2.1 对象属性的保护
保护对属性值的修改
- 对象的属性分为
- 命名属性: 可直接通过 . 访问到的属性
- 数据属性: 实际存储属性值的属性
- 访问属性: 不实际存储属性值, 专门对其数据属性的提供保护
- 内部属性: 无法通过 . 访问到的属性
- 命名属性: 可直接通过 . 访问到的属性
-
对象命名属性的保护
命名属性: 可直接通过 . 访问到的属性
-
对命名属性中数据属性的保护:
数据属性: 实际存储属性值的属性
-
四大特性:
-
value: 实际存储属性值 -
writable: 控制属性是否可修改- 取值:
false/true
- 取值:
-
enumerable: 控制属性是否可遍历- 取值:
false/true - 仅控制遍历, 无法控制访问 -> 依然可以用 . 访问
- 取值:
-
configurable:-
控制是否可删除属性
-
控制是否可修改其他两个特性
-
强调:
configurable经常作为前两个特性的双重保险, 一旦设置为false, 不可逆
-
-
-
如何查看四大特性:
Object.getOwnPropertyDescriptor(obj, '属性名')
-
如何修改四大特性:
Object.defineProperty(obj, '属性名', { 特性: 值, 特性: 值 ...... })-
问题:
defineProperty一次只能修改一个属性-
解决: 同时修改多个属性:
Object.defineProperties(obj, { 属性名: { 特性: 值, ...... }, 属性名: { 特性: 值 ...... } })- 问题: 无法使用自定义逻辑保护属性
- 解决: 使用访问属性
- 问题: 无法使用自定义逻辑保护属性
-
-
-
-
对命名属性种访问属性的保护:
访问属性: 不实际存储属性值, 专门对其数据属性的提供保护
-
何时: 只要用自定义逻辑保护属性时
-
如何: 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
- 其中: 赋值时自动调用
-
-
-
对象内部属性的保护
内部属性: 不能直接通过 . 访问的隐藏属性 -> Ex:
__proto____proto__:Object.getPropertypeOf(obj)
1.2.2. 对象结构的保护
-
防篡改: 保护对对象结构的修改
-
3 个级别:
-
防扩展: 禁止添加新属性
-
Object.preventExtensions(obj) -
原理: 每个
obj内部都有一个隐藏属性ExtensibleExtensible: 默认为truepreventExtensions(obj): 将obj中的隐藏属性Extensible改为false
-
-
密封: 在防扩展的基础上, 进一步禁止删除现有属性
Object.seal(obj)- 原理:
- 将
obj中隐藏属性Extensible修改为false - 将
obj中所有属性的configurable都修改为false
- 将
-
冻结: 在密封的基础上, 进一步禁止修改任何成员
Object.freeze(obj)- 原理:
- 将
obj中隐藏属性Extensible修改为false - 将
obj中所有属性的configurable都修改为false - 将
obj中所有属性的writable都修改为false
- 将
-
2. Object.create()
直接用一个父对象创建一个子对象
2.1. 如何
var child = Object.create(father, {
自有属性: {
value: 值,
writable: true,
enumerable: true,
configurable: true
},
......
})
强调: 只要添加到对象中的属性, 四大特性默认为
false, 显示必须修改为true
2.2. 原理
- 创建一个新的空对象:
var newObj = {} - 让新的空对象自动继承父对象:
Object.setPropertypeOf(newObj, father) - 为新对象扩展新的自有属性:
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 区别
-
call和apply: 强行 调用 一个函数并 临时 替换函数中的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 种
- 新项目: 建议必须使用严格模式
- 旧项目: 建议逐个模块向严格模式迁移
5.4. 如何: 2 种
- 整个代码段启用严格模式
- 在
script元素 /JS的开头位置加入use strict
- 在
- 仅对单个函数启用严格模式
- 在
function内, 函数体的顶部加入use strict
- 在
5.5. 严格模式的要求
- 不允许对未声明的变量赋值
- 静默失败升级为错误
- 不建议使用
arguments和arguments.callee实现递归