对象的一些方法

本文介绍了ES5中的属性描述符特性,包括可写性、可配置性和可枚举性的概念,以及如何使用getter和setter。同时,探讨了保持对象不变性的方法,如禁止扩展、密封和冻结。最后,对比了不同类型的遍历方式,如for...in和for...of循环的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、属性描述符

在es5之前,javaScript本身是没法直接检测属性特性的,比如判断属性是否是只读。

es5开始,有了属性描述符,在创建普通属性时属性描述符使用默认值。

var myObject = {
    a:2
};
Object.getOwnPropertyDescripter(myObject, "a");
//{
//  value: 2,
//  writable: true,     可写性
//  enmerable: true,    可枚举性
//  configurable: true  可配置性
//}

我们也可以自己设置,通过Object.defineProperty()方法(vue源码中的数据绑定也是通过这个方法外加观察者模式实现的)

var myObject = {}

Object.defineProperty(myObject, "a", {
    value: 2,
    writable: true,
    configurable: true,
    enumerable: true,
//  get: function(){  getter和setter不能和value或writable连用
//      return this._a_*2;
//  },
//  set: function(val){
//      this._a_ = val;
//  _a_代表一个普通变量,给a赋值时调用setter,触发对它的更改,直接写a的话,会发生栈溢出(而且逻辑也不对)
//  }
});
myObject.a // 2

下面来介绍下这里的属性~

Writable
决定是否可修改属性的值,设为false的话,属性值不可更改,如果在严格模式下更改,会抛出错误。设为false后,还会禁止删除这个属性

Configurable
决定属性是否可配置,设为false的话,不可再用Object.defineProperty改变它的状态,会报错。
所以把它修改成false是单向操作。
(但有一个小例外,即使把它设为false,我们还是可以把writable由true变为false,但不能反向改)

Enumerable
控制属性是否会出现在对象的属性枚举中,比如for···in循环,设为false后,属性就不会出现在枚举中,但仍然可以正常访问它。

Getter和Setter
这俩只能应用在单个属性上,无法应用在整个对象上(或许以后会有办法)。
getter在获取属性值时调用,setter在设置属性值时调用。

这俩还有另一种写法

var myObject = {
    get a(){
        return this._a_;
    },
    set a(val){
        this._a_ = val;
    }
};

接下来说一下,设置属性值时都发生了什么 (其实这是个略微复杂的过程,并不像看到的那么简单)

首先,如果这个属性已存在对象本身内部

看属性是否是setter,是就调用,不是的话判断属性writable是否为false,为false的话严格模式下会报错,非严格模式静默失败,不为false的话将值设为属性值。

如果属性存在于对象原型链上

如果属性writable为true的话,在对象内部添加同名的属性并赋值,如果为false,赋值语句会被忽略,严格模式下报错。如果属性是setter,那就一定会调用这个setter,并且不会在对象内部添加同名属性。

如果非要在对象内部添加同名属性,可用Object.defineProperty实现。

二、保持对象不变

禁止扩展

可以通过Object.preventExtensions()方法,禁止一个对象添加新方法并且保留已有属性。

var myObject = {
    a:2
}
Object.preventExtensions(myObject)
myObject.b = 3;
myObject.b // undefined

密封

Object.seal()密封对象,不仅不能添加新属性,也不能重新配置或删除任何现有属性(但可以修改属性值)

实际上就是在对象上调用Object.preventExtensions()并把所有属性的configurable设为false。

冻结

object.freeze()冻结对象,是应用在对象上最高级的不可变性,禁止对于对象本身及其任意直接属性的修改(对象内的属性如果也是对象的话,只可以保证这个引用不变,但改变这个属性对应的对象内部的东西它监测不到,除非遍历整个对象,不断调用freeze方法,实现深度冻结)

实际上是在对象上调用seal方法,并把所有“数据访问”属性标记为writable:false
(楼主面试的时候被问到过,自己封装一个freeze方法)

三、遍历

遍历对象,相信大家最常用的方法是for···in循环吧,它会遍历对象的可枚举属性,当然,数组也可以用它遍历,它会遍历数组的下标。

所以,不难猜出

for···in循环实际上遍历的是对象的属性名,而不是属性值。

还有一种循环可以直接遍历属性值,es6新增的for···of循环

举个例子

let arr = [1,2,3];

for(let i in arr){
    console.log(i);
} //0,1,2 遍历的是数组下标

for(let i of arr){
    console.log(i);
}//1,2,3 遍历的是值

这样大家可以看出区别了吧

但还有一个问题

for···of循环的原理是向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next方法实现遍历
(说白了,就是访问iterator接口,这也是es6提出的,有这个接口的可以直接用for···of循环,没有的不可以,像数组,字符串是天生就有的,但对象没有,所以对象不能直接用for···of循环,类数组大部分没有,但有特例,这部分以后写es6时会细说一下,es6的魅力还是很大的~)

我们可以手动访问这个接口,看看工作原理

let arr = [1,2,3];
let it = arr[Symbol.iterator]();

it.next();//{ value: 1, done: false }
it.next();//{ value: 2, done: false }
it.next();//{ value: 3, done: false }
it.next();//{ value: undefined, done: true }

就是这样的,每次调用next来遍历,返回value和done表示是否遍历完成,具体的以后写再说吧(不然太多了~)

虽然对象没有iterator接口,但活人总不能被尿憋死,我们可以自己给它加,这样就可以使用for···of循环了~

Object.defineProperty(myObject, Symbol.iterator, {
    enumerable: false,
    writable:false,
    configurable:false,
    value:function(){
        let o = this;
        let idx = 0;
        let ks = Object.keys(o);
        return {
            next:function(){
                return {
                    value:o[ks[idx++]],
                    done:(idx>ks.length)
                }
            }
        }
    }
});

这又是楼主当时的一道面试题,给对象加iterator接口,跟上面那个问题是一个公司问的,当时只是大概的实现了下,没写这么细,但感觉这家公司问的问题都挺有意思的,也挺有代表性,并且也在我最心仪的城市,所以最后决定去了这家公司,希望决定是对的吧,好了,今天先到这吧~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值