08.with、eval、严格模式、面向对象、属性描述符

with 语句

with 语句可以形成自己的作用域,严格模式下不允许使用 "with" 语句。

with 语句扩展一个语句的作用域链。

不建议使用 with 语句,因为它可能是混淆错误和兼容性问题的根源。

js 复制代码
var message = "Hello World"

var obj = {
    name: 'why',
    age: 18,
    message: 'kaimo313'
}

function foo() {
    console.log('foo---->', message)
    function bar() {
        with(obj) {
            console.log('bar---->', message)
        }
    }
    bar()
}

foo()

eval 函数

eval 是一个特殊的函数,它可以将传入的字符串作为 JavaScript 代码来执行。

js 复制代码
var jsString = "var message = 'Hello World'; console.log(message)";

eval(jsString);

console.log(message);

不建议在开发中使用 eval 函数

  • eval 代码的可读性非常差
  • eval 是一个字符串,可能在执行的过程中被刻意篡改,可能造成被攻击的风险
  • eval 的执行必须经过 js 解释器,不能被 js 引擎优化

认识严格模式

在 ECMAScript5 中引入了严格模式的概念(Strict Mode):

严格模式很好理解,是一种具有限制性的 JavaScript 模式,从而使代码隐式的脱离了'懒散(sloppy)模式'。

严格模式对正常的 JavaScript 语义进行了一些限制:

  • 严格模式通过抛出错误来消除一些原有的静默(silent)错误
  • 严格模式让 JS 引擎在执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理)
  • 严格模式禁用了在 ECMAScript 未来版本中可能会定义的一些语法

静默(silent)错误:不报错也没有任何效果

js 复制代码
123.name = 'abc'
var obj = {}
Object.defineProperty(obj, 'name', {
    writable: false
})
obj.name = 'why'

开启严格模式

严格模式通过在文件或者函数开头使用 "use strict" 来开启。

给文件跟函数开启严格模式:

js 复制代码
"use strict"

message = "Hello World"
console.log(message)

true.foo = 'bar'

function foo() {
    "use strict"
    true.foo = 'bar'
}

foo()

严格模式限制

几个严格模式模式下的严格语法限制:

  1. 无法意外的创建全局变量
  2. 严格模式会使引起静默失败的赋值操作抛出异常
  3. 严格模式下试图删除不可删除的属性
  4. 严格模式不允许函数参数有相同的名称
  5. 不允许0的八进制语法
  6. 在严格模式下,不允许使用 with
  7. 在严格模式下,eval 不再为上层引用变量
  8. 严格模式下,this 绑定不会默认转成对象
js 复制代码
// 意外的创建全局变量

message = "Hello World"
console.log(message)

function foo() {
    age = 20
}
foo()

// 不允许函数参数有相同的名称
function bar(age, age) {
    console.log(age, age)
}
bar(18, 20)

// 静默错误
true.foo = 'bar'
NaN = 123

// 试图删除不可删除的属性
var obj = {}
Object.defineProperty(obj, 'name', {
    configurable: false,
    value: 'why'
})
delete obj.name

// 不允许0的八进制语法
var num = 0123 // "0o123"
console.log(num)

// eval 不再为上层引用变量
var jsString = "var message = 'Hello World'; console.log(message)";

eval(jsString);

console.log(message);

在严格模式下,自执行函数(默认绑定)会指向 undefined

js 复制代码
'use strict'

function foo() {
    console.log(this) // undefined
}

var obj = {
    name: 'why',
    foo: foo
}
foo()

var bar = obj.foo
bar()

setTimeout(() => {
    console.log(this) // Window
}, 1000)

// chromium 浏览器 setTimeout 的实现
// fakeWin.setTimeout = function(fn, time) {
//     fakeWin.setTimeout.called = true
//     fakeWin.setTimeout.that = this
//     if(typeof fn === 'string') {
//         eval(fn)
//     } else {
//         fn.apply(this, Array.prototype.slice.call(arguments, 2))
//     }
// }
setTimeout(function () {
    console.log(this) // Window
}, 1000)

面向对象是现实的抽象方式

对象是 JavaScript 中一个非常重要的概念,这是因为对象可以将多个相关联的数据封装在一起,更好的描述一个事物。

比如我们可以描述一辆车:Car,具有颜色、速度、品牌、价格、行驶等等

比如我们可以描述一个人:Person,具有姓名、年龄、性别、身高、吃东西、跑步等等

用对象来描述事物,更有利于我们将现实的事物,抽离成代码中某个数据结构:

  • 所以有一些编程语言就是纯面向独享的编程语言,比如 Java;
  • 你在实现任何现实抽象时都需要先创建一个类,根据类再去创建对象

JavaScript 的面向对象

JavaScript 其实支持很多种编程范式的,包括函数式编程和面向对象编程。

JavaScript 中的对象被设计成一组属性的无序集合,像是一个哈希表,有key和value组成

key是一个标识符名称,value可以是任意类型,也可以是其他对象或者函数类型

如果value是一个函数,那么我们称之为对象的方法。

如何创建一个对象?

  • 早期使用创建对象的方式最多的是使用 Object 类,并且使用 new 关键字来创建一个对象(早期java开发者习惯于通过 new 来创建)
  • 通过字面量的形式来创建对象(简洁、内聚性强)
js 复制代码
// 创建一个对象,对某一个人进行抽象
var obj = new Object()
obj.name = 'why'
obj.age = 18
obj.sex = 'male'
obj.height = 1.8
obj.eat = function () {
    console.log('eat')
}

// 创建方式2:使用字面量方式
var obj2 = {
    name: 'why',
    age: 18,
    sex: 'male',
    height: 1.8,
    eat: function () {
        console.log('eat')
    }
}

对对象属性的操作

如果我们想要对一个属性进行比较精确的操作控制,那么我们就可以使用属性描述符

通过属性描述符可以精准的添加或修改对象的属性。

属性描述需要使用 Object.defineProperty 来对属性进行添加或者修改。

js 复制代码
var obj = {
    name: 'why',
    age: 18
}

// 获取属性
console.log(obj.name)

// 给属性赋值
obj.name = 'kaimo'
console.log(obj.name)

// 删除属性
delete obj.name
console.log(obj)

// 对属性操作时进行一些限制
// 1. 不允许某一个属性被赋值
// 2. 不允许某个属性删除
// 3. 不允许某些属性在遍历对象时被遍历出来

Object.defineProperty(obj, 'height', {
    value: 1.8
})

Object.defineProperty

Object.defineProperty(obj, prop, descriptor) 方法会直接在一个对象上定义一个新属性,或者修改一个已有的属性,并返回该对象。

  • obj:要定义属性的对象
  • prop:要定义的属性名
  • descriptor:属性描述符对象

属性描述符分类

属性描述符可以分为二种类型

  • 数据属性描述符(Data Properties Descriptor)
  • 存取属性描述符(Accessor Properties Descriptor)
configurable(属性是否可配置) enumerable(属性是否可枚举) value(属性值) writable(属性是否可写) get(获取属性值) set(设置属性值)
数据属性描述符 可以 可以 可以 可以 不可以 不可以
存取属性描述符 可以 可以 不可以 不可以 可以 可以

数据属性描述符

数据属性描述符有如下四个特性:

  • [[configurable]]:表示属性是否可以通过 delete 删除属性,是否可以修改它的的特性,或者是否可以将它修改为存取属性描述符
    • 直接在一个对象上定义某个属性时,这个属性的 configurable 值为 true
    • 通过属性描述符定义一个属性时, configurable 值为 false
  • [[enumerable]]:表示属性是否可以通过 for-in 或者 Object.keys() 返回该属性
    • 直接在一个对象上定义某个属性时,这个属性的 enumerable 值为 true
    • 通过属性描述符定义一个属性时, enumerable 值为 false
  • [[writable]]:表示是否可以修改属性的值
    • 直接在一个对象上定义某个属性时,这个属性的 writable 值为 true
    • 通过属性描述符定义一个属性时, writable 值为 false
  • [[value]]:属性的 value 值,读取属性时返回该值,修改属性时,会对其进行修改
    • 默认情况下这个值是 undefined
js 复制代码
// name 跟 age 没有使用属性描述符定义,但是它们也是具备对应的特性的
// 以 name 属性为例
// value: 'why'
// configurable: true
// enumerable: true
// writable: true
var obj = {
    name: 'why',
    age: 18
}

// 数据属性描述符
Object.defineProperty(obj, 'address', {
    value: '广州市', // 默认值 undefined
    // 不能修改、删除、重新定义
    configurable: false, // 默认值 false
    // 不能被枚举
    enumerable: false, // 默认值 false
    // 不能修改属性值
    writable: false // 默认值 false
})

// 测试 configurable 的作用
// delete obj.name
// console.log(obj.name)
// delete obj.address
// console.log(obj.address)

// // Uncaught TypeError: Cannot redefine property: address
// Object.defineProperty(obj, 'address', {
//     value: '北京市',
//     configurable: true
// })

// 测试 enumerable 的作用
// for (var key in obj) {
//     console.log(key)
// }
// console.log(Object.keys(obj))

// 测试 writable 的作用
// obj.address = '北京市'
// console.log(obj.address)

存取属性描述符

存取属性描述符有如下四个特性:

  • [[configurable]]:表示属性是否可以通过 delete 删除属性,是否可以修改它的的特性,或者是否可以将它修改为存取属性描述符
    • 直接在一个对象上定义某个属性时,这个属性的 configurable 值为 true
    • 通过属性描述符定义一个属性时, configurable 值为 false
  • [[enumerable]]:表示属性是否可以通过 for-in 或者 Object.keys() 返回该属性
    • 直接在一个对象上定义某个属性时,这个属性的 enumerable 值为 true
    • 通过属性描述符定义一个属性时, enumerable 值为 false
  • [[get]]:获取属性时会执行的函数。默认值为 undefined
  • [[set]]:设置属性时会执行的函数。默认值为 undefined
js 复制代码
var obj = {
    name: 'why',
    age: 18,
    _address: '广州市'
}

// 存取属性描述符
// 1、隐藏某一个私有属性,不希望直接被外界使用和赋值
// 2、如果我们希望接拦截获取某一属性,访问和设置值的过程时,也会使用存取属性描述符
Object.defineProperty(obj, 'address', {
    // 不能修改、删除、重新定义
    configurable: false, // 默认值 false
    // 不能被枚举
    enumerable: false, // 默认值 false
    get: function () {
        foo()
        return this._address
    },
    set: function (newValue) {
        bar()
        this._address = newValue
    }
})

function foo() {
    console.log('获取了一次address的值')
}

function bar() {
    console.log('设置了一次address的值')
}

obj.address = '北京市'
console.log(obj.address)
相关推荐
睡不着的可乐2 小时前
Math对象
javascript
xuankuxiaoyao2 小时前
Vue.js实践-组件基础上
前端·javascript·vue.js
M ? A2 小时前
你的 Vue 3 响应式状态,VuReact 如何生成 React Hooks 依赖数组?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
competes2 小时前
React.js JavaScript前端技术脚本运行框架。程序员进行研发组项目现场工作落地的一瞬之间适应性恒强说明可塑性强度达到应用架构师的考核标准
前端·javascript·人工智能·react.js·java-ee·ecmascript
jiayong233 小时前
第 25 课:给学习笔记页加上搜索、标签筛选和 URL 同步
开发语言·前端·javascript·vue.js·学习
jiayong233 小时前
第 12 课:`watch` 和防抖到底该怎么用
前端·javascript·vue.js
im_AMBER3 小时前
Leetcode 158 数组中的第K个最大元素 | 查找和最小的 K 对数字
javascript·数据结构·算法·leetcode·
qq_12084093713 小时前
Three.js 场景性能优化实战:首屏、帧率与内存的工程化治理
开发语言·javascript·性能优化·three.js
竹林8183 小时前
Solana前端开发:从连接钱包到发送交易,我如何用@solana/web3.js搞定第一个DApp
前端·javascript