31、JavaScript 对象操作与特性深入解析

JavaScript 对象操作与特性深入解析

1. 对象属性操作与枚举

1.1 新增属性与属性描述符

在 JavaScript 中,我们可以为自定义对象添加新的属性和属性描述符。例如,在 TechBook 自定义对象打印出图像属性后,添加了一个新属性 experience 和对应的属性描述符。通过 Object.getOwnPropertyDescriptor 方法可以获取该属性的描述符对象,再使用 JSON.stringify 方法打印出描述符的值:

// 假设已经有 TechBook 对象
const descriptor = Object.getOwnPropertyDescriptor(TechBook, 'experience');
console.log(JSON.stringify(descriptor)); 
// 输出: {"enumerable":false,"configurable":true}

接着对属性描述符的值进行测试。由于 experience 属性不可枚举,使用 for...in 循环无法枚举该属性,结果为:

Techbook has prototype

当将 experience 属性的 enumerable 属性改为 true 后,在 Firefox 中枚举该属性会得到:

Techbook has prototype experience

但 Chrome 不会获取到原型属性。之后创建 TechBook 对象的新实例并添加 experience 属性,将其打印出来以验证属性添加成功。

最后,示例代码添加了另一个新属性 publisher 和属性描述符。这是一个数据属性描述符,可以设置默认值为 “O’Reilly” 。将 writable 属性描述符设置为 false configurable enumerable 描述符设置为 true 。当尝试更改 publisher 的值时,由于 writable 属性为 false ,仍然会打印出原始的 O'Reilly 值。

Object.defineProperty(TechBook.prototype, 'publisher', {
    value: "O’Reilly",
    writable: false,
    configurable: true,
    enumerable: true
});
let techBook = new TechBook();
techBook.publisher = "New Publisher"; 
console.log(techBook.publisher); 
// 输出: O’Reilly

1.2 枚举对象属性的方法

当我们想要查看一个对象具有哪些属性时,可以使用以下几种方法:
- 使用 for...in 循环

for (var prop in obj) {
    alert(prop); // 打印出属性名
}
  • 使用 Object.keys 方法 :返回所有可枚举属性的名称。
alert(Object.keys(obj).join(", "));
  • 使用 Object.getOwnPropertyNames 方法 :获取所有属性的名称,无论是否可枚举。
var props = Object.getOwnPropertyNames(obj);

1.3 示例对象属性枚举分析

对于基于以下对象定义创建的 newBook 对象实例:

function Book (title, author) {
    var title = title;
    this.author = author;
    this.getTitle = function() {
        return "Title: " + title;
    }
    this.getAuthor = function() {
        return "Author: " + author;
    }
}
function TechBook (title, author, category) {
    var category = category;
    this.getCategory = function() {
        return "Technical Category: " + category;
    }
    Book.apply(this, arguments);
    this.getBook = function() {
        return this.getTitle() + " " + author + " " + this.getCategory();
    }
}
// 链对象构造函数
TechBook.prototype = new Book();
// 创建新的技术书籍对象
var newBook = new TechBook("The JavaScript Cookbook", "Shelley Powers", "Programming");

使用 for...in 循环枚举 newBook 对象的属性:

var str = "";
for (var prop in newBook) {
    str = str + prop + " ";
}
alert(str);

弹出的消息包含以下值:

getCategory author getTitle getAuthor getBook

TechBook 中的 category 属性和 Book 中的 title 属性未被返回,因为它们是私有数据成员。在使用 WebKit nightly 或 Firefox Minefield 时,使用 Object.keys Object.getOwnPropertyNames 方法也会得到相同的结果。

然而,如果为 title 属性添加属性描述符并使其可枚举:

// 测试数据描述符
Object.defineProperty(newBook, "title", {
    writable: true,
    enumerable: true,
    configurable: true
});

再次枚举属性时, title 会显示在属性列表中,尽管仍然不能直接在对象上访问或重置该属性。

对于对象构造函数(如 Book TechBook )或任何内置对象,使用不同的枚举方法返回的值会有所不同。 for...in 循环和 Object.keys 方法返回 Book 对象的唯一可枚举属性 prototype ,而 Object.getOwnPropertyNames 方法返回 arguments callee caller length name prototype 等属性。

1.4 浏览器对属性枚举方法的支持情况

不同浏览器对属性枚举方法和属性描述符的支持情况不同,具体如下表所示:
| 浏览器 | defineProperty | Object.keys | Object.getOwnPropertyNames |
| ---- | ---- | ---- | ---- |
| Opera | 不支持 | - | - |
| WebKit nightly、Chrome beta | 支持 | 支持 | 支持 |
| Firefox nightly (Minefield) | - | 支持 | 不支持 |
| IE8 | 仅支持在 DOM 元素上使用新方法,不支持自定义对象 | - | - |

2. 防止对象扩展与修改

2.1 防止对象扩展

当我们想要防止他人扩展一个对象时,可以使用新的 ECMAScript 5 Object.preventExtensions 方法。该方法将对象锁定,防止未来添加新属性,但属性值本身仍然可写。

"use strict";
var Test = {
    value1: "one",
    value2: function() {
        return this.value1;
    }
};
try {
    Object.preventExtensions(Test);
    // 以下操作在严格模式下会失败并抛出异常
    Test.value3 = "test";
} catch (e) {
    alert(e);
}

可以使用 Object.isExtensible 方法检查对象是否可扩展:

if (Object.isExtensible(obj)) {
    // 扩展对象
}

目前没有浏览器实现 Object.preventExtensions 方法,但预计未来会有浏览器支持。

2.2 防止对象添加和属性描述符更改

若要防止对象扩展,同时禁止更改对象的属性描述符,可以使用新的 ECMAScript Object.seal 方法。

"use strict";
var Test = {
    value1: "one",
    value2: function() {
        return this.value1;
    }
};
try {
    // 密封对象
    Object.seal(Test);
    // 以下操作会成功
    Test.value2 = "two";
    // 以下操作在严格模式下会失败并抛出错误
    Test.newProp = "value3";
    // 以下操作也会失败
    Object.defineProperty(Test, "category", {
        get: function () { return category; },
        set: function (value) { category = value; },
        enumerable: true,
        configurable: true
    });
} catch (e) {
    alert(e);
}

可以使用 Object.isSealed 方法检查对象是否被密封:

if (Object.isSealed(obj)) {
    // 对象已被密封
}

目前该方法还没有浏览器实现,但有望在未来得到支持。

2.3 防止对象的任何更改

为了确保对象的属性不被其他应用程序重新定义或编辑,可以使用新的 ECMAScript 5 Object.freeze 方法。该方法将对象冻结,禁止对对象进行任何更改,包括扩展对象、更改属性描述符和编辑现有属性。

"use strict";
var Test = {
    value1: "one",
    value2: function() {
        return this.value1;
    }
};
try {
    // 冻结对象
    Object.freeze(Test);
    // 以下操作在严格模式下会抛出错误
    Test.value2 = "two";
    // 以下操作也会抛出错误
    Test.newProperty = "value";
    // 以下操作同样会抛出错误
    Object.defineProperty(Test, "category", {
        get: function () { return category; },
        set: function (value) { category = value; },
        enumerable: true,
        configurable: true
    });
} catch (e) {
    alert(e);
}

可以使用 Object.isFrozen 方法检查对象是否被冻结:

if (Object.isFrozen(obj)) {
    // 对象已被冻结
}

目前没有浏览器实现 Object.freeze Object.isFrozen 方法,但这种情况预计会很快改变。

2.4 三种对象保护方法的比较

方法 禁止添加新属性 允许修改属性值 允许修改属性描述符
Object.preventExtensions
Object.seal
Object.freeze

3. 一次性对象与命名空间

3.1 使用对象字面量实现命名空间

当我们想要封装库的功能以防止与其他库发生冲突时,可以使用对象字面量(一次性对象)来实现 JavaScript 版本的命名空间。例如:

var jscbObject = {
    // 返回元素
    getElem: function (identifier) {
        return document.getElementById(identifier);
    },
    stripslashes: function (str) {
        return str.replace(/\\/g, '');
    },
    removeAngleBrackets: function (str) {
        return str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
    }
};
var incoming = jscbObject.getElem("incoming");
var content = incoming.innerHTML;
var result = jscbObject.stripslashes(content);
result = jscbObject.removeAngleBrackets(result);
jscbObject.getElem("result").innerHTML = result;

所有 JavaScript 内置对象除了有正式的对象表示形式外,还有字面量表示形式。对象字面量的表示方法是将属性名和关联值成对列出,用逗号分隔,并用花括号括起来:

var newObj = {
    prop1: "value",
    prop2: function() { 
        // 函数体
    }
};

可以使用对象点表示法访问对象成员:

var tmp = newObj.prop2();
var val = newObj.prop1 * 20;

通过使用对象字面量,可以将库的所有功能封装起来,使所需的函数和变量不在全局空间中。唯一的全局对象就是对象字面量本身,如果使用一个包含功能、组、目的、作者等信息的唯一名称,就可以有效地对功能进行命名空间,防止与其他库发生名称冲突。

3.2 与其他数据类型字面量的对比

数据类型 正式创建方式 字面量表示方式
数组 var newArray = new Array('one', 'two', 'three'); var newArray = ['one', 'two', 'three'];
对象 var obj = new Object(); obj.prop = 'value'; var obj = { prop: 'value' };

4. 控制函数作用域与方法链

4.1 使用 bind 方法控制函数作用域

在 JavaScript 中, this 关键字表示函数的所有者或作用域。但在当前的 JavaScript 库中,我们无法保证函数应用的作用域。可以使用新的 ECMAScript 5 函数 bind 方法来控制函数的作用域。

window.onload = function() {
    window.name = "window";
    var newObject = {
        name: "object",
        sayGreeting: function() {
            alert("Now this is easy, " + this.name);
            var nestedGreeting = function(greeting) {
                alert(greeting + " " + this.name);
            }.bind(this);
            nestedGreeting("hello");
        }
    };
    newObject.sayGreeting("hello");
};

如果目标浏览器不支持 bind 方法,可以扩展 Function 对象,使用 Prototype.js 库中流行的代码:

Function.prototype.bind = function(scope) {
    var _function = this;
    return function() {
        return _function.apply(scope, arguments);
    }
};

bind 方法特别适用于定时器,如 setInterval setTimeout 。以下是一个使用 setTimeout 进行倒计时操作的示例:

<!DOCTYPE html>
<head>
    <title>Using bind with timers</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style type="text/css">
        #item { font-size: 72pt; margin: 70px auto; width: 100px;}
    </style>
    <script>
        if (!Function.bind) {
            Function.prototype.bind = function(scope) {
                var _function = this;
                return function() {
                    return _function.apply(scope, arguments);
                }
            }
        }
        window.onload = function() {
            var theCounter = new Counter('item', 10, 0);
            theCounter.countDown();
        }
        function Counter(id, start, finish) {
            this.count = this.start = start;
            this.finish = finish;
            this.id = id;
            this.countDown = function() {
                if (this.count == this.finish) {
                    this.countDown = null;
                    return;
                }
                document.getElementById(this.id).innerHTML = this.count--;
                setTimeout(this.countDown.bind(this), 1000);
            };
        }
    </script>
</head>
<body>
    <div id="item">
        10
    </div>
</body>

如果没有使用 bind 方法,定时器调用方法时会丢失对象作用域和计数器,导致应用程序无法正常工作。

4.2 方法链的实现

方法链是指在同一行代码中直接调用一个函数的结果来调用另一个函数的能力。要实现方法链,需要在想要链式调用的方法中添加特殊代码,即在执行完所需的功能后返回对象本身。例如:

function Book (title, author) {
    var title = title;
    var author = author;
    this.getTitle = function() {
        return "Title: " + title;
    }
    this.getAuthor = function() {
        return "Author: " + author;
    }
    this.replaceTitle = function (newTitle) {
        var oldTitle = title;
        title = newTitle;
    }
    this.replaceAuthor = function(newAuthor) {
        var oldAuthor = author;
        author = newAuthor;
    }
}
function TechBook (title, author, category) {
    var category = category;
    this.getCategory = function() {
        return "Technical Category: " + category;
    }
    Book.apply(this, arguments);
    this.changeAuthor = function(newAuthor) {
        this.replaceAuthor(newAuthor);
        return this;
    }
}
window.onload = function() {
    try {
        var newBook = new TechBook("I Know Things", "Shelley Powers", "tech");
        alert(newBook.changeAuthor("Book K. Reader").getAuthor());
    } catch (e) {
        alert(e.message);
    }
};

在上述示例中, changeAuthor 方法在替换作者后返回 this ,从而可以链式调用 getAuthor 方法。

通过以上这些 JavaScript 对象操作和特性的学习,我们可以更好地管理和控制对象,提高代码的可维护性和健壮性。同时,要注意不同浏览器对新特性的支持情况,合理使用这些特性来开发高质量的 JavaScript 应用程序。

4.3 方法链的优势与应用场景

方法链的主要优势在于可以使代码更加简洁和易读,尤其是在需要对对象进行一系列操作时。通过链式调用,可以避免创建多个临时变量,使代码逻辑更加连贯。以下是一些方法链的常见应用场景:

  • DOM 操作 :在操作 DOM 元素时,经常需要对元素进行一系列的设置和修改。例如:
document.getElementById('myElement')
    .style.color = 'red'
    .style.fontSize = '20px'
    .setAttribute('title', 'This is a styled element');
  • 数据处理 :在对数据进行处理时,也可以使用方法链来简化代码。例如,对数组进行过滤、映射和求和操作:
const numbers = [1, 2, 3, 4, 5];
const sum = numbers
    .filter(num => num % 2 === 0)
    .map(num => num * 2)
    .reduce((acc, num) => acc + num, 0);
console.log(sum); 

4.4 方法链的注意事项

虽然方法链可以使代码更加简洁,但在使用时也需要注意以下几点:

  • 返回值的一致性 :要确保每个链式调用的方法都返回对象本身,否则方法链会中断。
  • 错误处理 :由于方法链是连续调用的,一旦其中一个方法出现错误,可能会影响整个链的执行。因此,需要在每个方法中进行适当的错误处理。
  • 代码可读性 :当方法链过长时,可能会影响代码的可读性。因此,需要根据实际情况合理拆分方法链。

5. 严格模式的使用

5.1 严格模式的概念与作用

严格模式是 ECMAScript 5 引入的一种新的 JavaScript 运行模式。在严格模式下,一些旧的、不推荐使用的特性会被禁用或产生错误,同时一些可能不会抛出错误但不建议的操作也会抛出错误。使用严格模式可以帮助开发者编写更规范、更安全的代码,提高代码的质量和可维护性。

5.2 开启严格模式的方法

可以在代码的顶层或函数内部开启严格模式。以下是两种开启严格模式的示例:

  • 在代码顶层开启
"use strict";
// 代码逻辑
var x = 10;
  • 在函数内部开启
function myFunction() {
    "use strict";
    // 函数逻辑
    var y = 20;
}

5.3 严格模式的限制与异常

在严格模式下,会有一些限制和异常,以下是一些常见的例子:

  • 禁止使用 with 语句 with 语句在严格模式下会抛出错误,因为它会导致代码的作用域变得不清晰。
"use strict";
var obj = { a: 1 };
// 以下代码会抛出错误
with (obj) {
    console.log(a);
}
  • 禁止删除变量、函数参数或函数 :在严格模式下,使用 delete 操作符删除变量、函数参数或函数会抛出错误。
"use strict";
var myVar = 10;
// 以下代码会抛出错误
delete myVar;
  • 大多数 eval 的使用会产生错误 :在严格模式下, eval 的行为会受到更多的限制,使用 eval 可能会抛出错误。

5.4 浏览器对严格模式的支持情况

目前,大多数现代浏览器都支持严格模式,但在使用时仍需要注意兼容性问题。以下是一些常见浏览器对严格模式的支持情况:

浏览器 支持情况
Chrome 支持
Firefox 支持
Safari 支持
Edge 支持
IE10 及以上 支持
IE9 及以下 不支持

6. 总结与最佳实践

6.1 总结

本文深入探讨了 JavaScript 对象的多种操作和特性,包括属性操作与枚举、防止对象扩展与修改、一次性对象与命名空间、控制函数作用域与方法链以及严格模式的使用。通过学习这些内容,我们可以更好地管理和控制 JavaScript 对象,提高代码的可维护性和健壮性。

6.2 最佳实践

为了更好地应用这些知识,以下是一些最佳实践建议:

  • 合理使用属性描述符 :在需要控制属性的可枚举性、可配置性和可写性时,使用属性描述符可以提供更精细的控制。
  • 选择合适的对象保护方法 :根据实际需求,选择 Object.preventExtensions Object.seal Object.freeze 方法来保护对象,防止不必要的修改。
  • 使用命名空间封装代码 :使用对象字面量实现命名空间,避免全局变量的污染,防止与其他库发生冲突。
  • 正确使用 bind 方法 :在处理函数作用域问题时,使用 bind 方法可以确保函数在正确的作用域中执行。
  • 适度使用方法链 :在需要对对象进行一系列操作时,使用方法链可以使代码更加简洁,但要注意代码的可读性和错误处理。
  • 开启严格模式 :在开发过程中,开启严格模式可以帮助发现潜在的问题,提高代码的质量。

6.3 未来展望

随着 JavaScript 的不断发展,新的特性和方法可能会不断涌现。我们需要持续关注这些变化,学习和掌握新的知识,以更好地应对不断变化的开发需求。同时,要注意不同浏览器对新特性的支持情况,合理使用这些特性来开发高质量的 JavaScript 应用程序。

通过遵循这些最佳实践,我们可以写出更加健壮、可维护和高效的 JavaScript 代码,提升开发效率和代码质量。

6.4 流程图总结

下面是一个 mermaid 格式的流程图,总结了本文中涉及的主要对象操作流程:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B{选择操作类型}:::decision
    B -->|属性操作与枚举| C(新增属性与描述符):::process
    B -->|属性操作与枚举| D(枚举对象属性):::process
    B -->|防止对象扩展与修改| E(防止对象扩展):::process
    B -->|防止对象扩展与修改| F(防止属性描述符更改):::process
    B -->|防止对象扩展与修改| G(防止对象任何更改):::process
    B -->|一次性对象与命名空间| H(使用对象字面量):::process
    B -->|控制函数作用域与方法链| I(使用bind方法):::process
    B -->|控制函数作用域与方法链| J(实现方法链):::process
    B -->|严格模式| K(开启严格模式):::process
    C --> L(测试属性描述符):::process
    D --> M(选择枚举方法):::process
    E --> N(检查对象可扩展性):::process
    F --> O(检查对象密封性):::process
    G --> P(检查对象冻结性):::process
    H --> Q(封装库功能):::process
    I --> R(处理函数作用域问题):::process
    J --> S(简化代码逻辑):::process
    K --> T(遵循严格模式规则):::process
    L --> U([结束]):::startend
    M --> U
    N --> U
    O --> U
    P --> U
    Q --> U
    R --> U
    S --> U
    T --> U

这个流程图展示了 JavaScript 对象操作的主要流程,从选择操作类型开始,到具体的操作步骤,最后结束。通过这个流程图,可以更清晰地理解各个操作之间的关系和顺序。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值