日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

一个对象的属性_【前端冷知识】如何判断一个对象的某个属性是可写的?

發布時間:2025/3/21 HTML 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一个对象的属性_【前端冷知识】如何判断一个对象的某个属性是可写的? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這是一個咋一聽好像很簡單,但是實際上卻沒那么簡單,而且是很有趣的問題。

我們先來看一下什么情況下一個對象的屬性是可寫的。

“屬性可寫”這個概念并沒有嚴謹的定義,我們這里先來規定一下。

屬性可寫,是指滿足如下條件:

對于任意對象object,該對象的a屬性可寫,是指如下代碼成立:

const?value?=?Symbol();

object.a?=?value;

console.assert(obj.a?===?value);

JavaScript有幾種情況下,對象屬性不可寫。

?? 第一種情況,如果這個屬性是accessor property,并且只有一個getter時,這個屬性不可寫。

const?obj?=?{

??get?a(){

????return?'a';

??}

};

console.log(obj.a);?// a

obj.a?=?'b';

console.log(obj.a);?// a

?? 第二種情況,如果這個屬性的Descriptor中設置了writable為false,這個屬性不可寫。

const?obj?=?{};

Object.defineProperty(obj,?'a',?{

??value:?'a',

??writable:?false,

});

console.log(obj.a);?// a

obj.a?=?'b';

console.log(obj.a);?// a

?? 第三種情況,目標對象被Object.freeze,實際上也是將對象上所有屬性的writable設為了false:

const?obj?=?{a:?'a'};

Object.freeze(obj);

console.log(obj.a);?// a

obj.a?=?'b';

console.log(obj.a);?// a

那么了解了這些情況,我們就可以嘗試寫一個方法來判斷對象屬性是否可寫了:

function?isOwnPropertyWritable(obj,?prop)?{

??const?des?=?Object.getOwnPropertyDescriptor(obj,?prop);

??return?des?==?null?||?des.writable?||?!!des.set;

}

上面這個方法可以簡單判斷一個對象自身的屬性是否可寫,判斷邏輯也不復雜,先通過Object.getOwnPropertyDescriptor(obj, prop)方法獲取對象自身屬性的Descriptor,接下來有三種情況對象的這個屬性可寫:

  • 這個Descriptor不存在,表示對象上沒有該屬性,那么我們可以動態添加這個屬性

  • 這個Descriptor存在,且writable為true,那么屬性可寫

  • 這個Descriptor存在,且擁有getter,那么屬性可寫

看似好像解決了這個問題,但是,實際上這個判斷有很多問題。

首先,最大的問題是,這個方法只能判斷對象自身的屬性,如果對象原型和原型鏈上的屬性,實際上getOwnPropertyDescriptor是訪問不到的,我們看一個簡單例子:

function?isOwnPropertyWritable(obj,?prop)?{

??const?des?=?Object.getOwnPropertyDescriptor(obj,?prop);

??return?des?==?null?||?des.writable?||?!!des.set;

}

class?A?{

??get?a()?{

????return?'a';

??}

}

const?obj?=?new?A();

console.log(isOwnPropertyWritable(obj,?'a'));?// true

console.log(obj.a);?// a

obj.a?=?'b';

console.log(obj.a);?// a

上面的代碼,我們預期的isOwnPropertyWritable(obj, 'a')應該返回false,但實際上卻是返回true,這是因為Object.getOwnPropertyDescriptor獲取不到class中定義的getter,該getter實際上是在obj的原型上。

要解決這個問題,我們需要沿原型鏈遞歸判斷屬性:

function?isPropertyWritable(obj,?prop)?{

??while(obj)?{

????if(!isOwnPropertyWritable(obj,?prop))?return?false;

????obj?=?Object.getPrototypeOf(obj);

??}

??return?true;

}

我們實現一個isPropertyWritable(obj, prop),不僅判斷自身,也判斷一下它的原型鏈。

這樣我們就解決了繼承屬性的問題。

function?isOwnPropertyWritable(obj,?prop)?{

??const?des?=?Object.getOwnPropertyDescriptor(obj,?prop);

??return?des?==?null?||?des.writable?||?!!des.set;

}

function?isPropertyWritable(obj,?prop)?{

??while(obj)?{

????if(!isOwnPropertyWritable(obj,?prop))?return?false;

????obj?=?Object.getPrototypeOf(obj);

??}

??return?true;

}

class?A?{

??get?a()?{

????return?'a';

??}

}

class?B?extends?A?{

}

const?a?=?new?A();

const?b?=?new?B();

console.log(isPropertyWritable(a,?'a'));?// false

console.log(isPropertyWritable(b,?'a'));?// false

但是實際上這樣實現還是有缺陷,我們其實還少了幾個情況。

首先,我們處理原始類型,比如現在下面的代碼會有問題:

const?obj?=?1;

obj.a?=?'a';

console.log(isPropertyWritable(obj,?'a'));?// true

console.log(obj.a);?// undefined

所以我們要修改一下isOwnPropertyWritable的實現:

function?isOwnPropertyWritable(obj,?prop)?{

??if(obj?==?null)?return?false;

??const?type?=?typeof?obj;

??if(type?!==?'object'?&&?type?!==?'function')?return?false;

??const?des?=?Object.getOwnPropertyDescriptor(obj,?prop);

??return?des?==?null?||?des.writable?||?!!des.set;

}

然后,其實還有一些case,比如:

function?isOwnPropertyWritable(obj,?prop)?{

??if(obj?==?null)?return?false;

??const?type?=?typeof?obj;

??if(type?!==?'object'?&&?type?!==?'function')?return?false;

??const?des?=?Object.getOwnPropertyDescriptor(obj,?prop);

??return?des?==?null?||?des.writable?||?!!des.set;

}

function?isPropertyWritable(obj,?prop)?{

??// noprotected

??while(obj)?{

????if(!isOwnPropertyWritable(obj,?prop))?return?false;

????obj?=?Object.getPrototypeOf(obj);

??}

??return?true;

}

const?obj?=?{};

Object.seal(obj);

console.log(isPropertyWritable(obj,?'a'));?// true

obj.a?=?'b';

console.log(obj.a);?// undefined

我們還需要考慮seal的情況。

?? Object.seal 方法封閉一個對象,阻止添加新屬性并將所有現有屬性標記為不可配置。

所以對這種情況我們也要加以判斷:

function?isOwnPropertyWritable(obj,?prop)?{

??if(obj?==?null)?return?false;

??const?type?=?typeof?obj;

??if(type?!==?'object'?&&?type?!==?'function')?return?false;

??if(!(prop?in?obj)?&&?Object.isSealed(obj))?return?false;

??const?des?=?Object.getOwnPropertyDescriptor(obj,?prop);

??return?des?==?null?||?des.writable?||?!!des.set;

}

好了,那最后得到的版本就是這樣的:

function?isOwnPropertyWritable(obj,?prop)?{

??// 判斷 null 和 undefined

??if(obj?==?null)?return?false;

??// 判斷其他原始類型

??const?type?=?typeof?obj;

??if(type?!==?'object'?&&?type?!==?'function')?return?false;

??// 判斷sealed的新增屬性

??if(!(prop?in?obj)?&&?Object.isSealed(obj))?return?false;

??// 判斷屬性描述符

??const?des?=?Object.getOwnPropertyDescriptor(obj,?prop);

??return?des?==?null?||?des.writable?||?!!des.set;

}

function?isPropertyWritable(obj,?prop)?{

??while(obj)?{

????if(!isOwnPropertyWritable(obj,?prop))?return?false;

????obj?=?Object.getPrototypeOf(obj);

??}

??return?true;

}

這樣就100%沒問題了嗎?

也不是,嚴格來說,我們還是可以trick,比如給對象故意設一個setter:

function?isOwnPropertyWritable(obj,?prop)?{

??// 判斷 null 和 undefined

??if(obj?==?null)?return?false;

??// 判斷其他原始類型

??const?type?=?typeof?obj;

??if(type?!==?'object'?&&?type?!==?'function')?return?false;

??// 判斷sealed的新增屬性

??if(!(prop?in?obj)?&&?Object.isSealed(obj))?return?false;

??// 判斷屬性描述符

??const?des?=?Object.getOwnPropertyDescriptor(obj,?prop);

??return?des?==?null?||?des.writable?||?!!des.set;

}

function?isPropertyWritable(obj,?prop)?{

??while(obj)?{

????if(!isOwnPropertyWritable(obj,?prop))?return?false;

????obj?=?Object.getPrototypeOf(obj);

??}

??return?true;

}

const?obj?=?{

??get?a()?{

????return?'a';

??},

??set?a(v)?{

????// do nothing

??}

}

console.log(isPropertyWritable(obj,?'a'));?// true

obj.a?=?'b';

console.log(obj.a);?// a

你可能會說,這種trick太無聊了,但是事實上類似下面的代碼還是有可能寫出來的:

const?obj?=?{

??name:?'a',

??get?a()?{

????return?this.name;

??},

??set?a(v)?{

????this.name?=?v;

??}

};

Object.freeze(obj);

console.log(isPropertyWritable(obj,?'a'));

當然要解決這個問題也不是不可以,還要加上一個判斷:

function?isOwnPropertyWritable(obj,?prop)?{

??// 判斷 null 和 undefined

??if(obj?==?null)?return?false;

??// 判斷其他原始類型

??const?type?=?typeof?obj;

??if(type?!==?'object'?&&?type?!==?'function')?return?false;

??// 判斷是否被凍結

??if(Object.isFrozen(obj))?return?false;

??// 判斷sealed的新增屬性

??if(!(prop?in?obj)?&&?Object.isSealed(obj))?return?false;

??// 判斷屬性描述符

??const?des?=?Object.getOwnPropertyDescriptor(obj,?prop);

??return?des?==?null?||?des.writable?||?!!des.set;

}

所以,要考慮的情況著實不少,也不知道還有沒有沒考慮周全的。

有可能還真得換一個思路,從定義入手:

function?isPropertyWritable(obj,?prop)?{

??const?value?=?obj[prop];

??const?sym?=?Symbol();

??try?{

????obj[prop]?=?sym;

??}?catch(ex)?{

????// 解決在嚴格模式下報錯問題

????return?false;

??}

??const?isWritable?=?obj[prop]?===?sym;

??obj[prop]?=?value;?// 恢復原來的值

??return?isWritable;

}

這樣就解決了問題,唯一的問題是對屬性做了兩次賦值操作,不過應該也沒有太大的關系。

補充:經過大家討論,上面這個思路也不行,如果屬性的setter中執行一些操作,會有很大的問題,比如我們observe一些對象,用這個方法因為寫入了兩次,可能會觸發兩次change事件。。。

所以基本上運行時判斷某個屬性可寫,沒有特別好的手段,也許只能使用TypeScript這樣的靜態類型語言在編譯時檢查,才是比較好的方案~

好了,關于判斷對象屬性是否可寫的方法,你還有什么問題,歡迎在issue中討論。

關于奇舞周刊

《奇舞周刊》是360公司專業前端團隊「奇舞團」運營的前端技術社區。關注公眾號后,直接發送鏈接到后臺即可給我們投稿。

總結

以上是生活随笔為你收集整理的一个对象的属性_【前端冷知识】如何判断一个对象的某个属性是可写的?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。