这是一个咋一听好像很简单,但是实际上却没那么简单,而且是很有趣的问题。我们先来看一下什么情况下一个对象的属性是可写的。
“属性可写”这个概念并没有严谨的定义,我们这里先来规定一下。属性可写,是指满足如下条件:
对于任意对象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这样的静态类型语言在编译时检查,才是比较好的方案~
来源:https://mp.weixin.qq.com/s/tEupyvUsJLBnHKGq_Nqw3w
原创: 月影前端,公众号:前端冷知识
display:none;会让元素完全从渲染树中消失,渲染的时候不占据任何空间;visibility: hidden;不会让元素从渲染树消失,渲染师元素继续占据空间,只是内容不可见,display: none;是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示;
元素宽高width,min-width,max-width等元素宽度设置百分比,以包含块的宽度为标准进行计算;height,min-height,max-height等元素宽度设置百分比,以包含块的高度为标准进行计算;
readonly 只对 <input> 和 <textarea> 标签有效;disabled 对所有表单元素都有效, 包括:<input>, <textarea>, <button>, <label>, <option>, <select>等
事实上我挺长一段时间都没弄清楚overflow:scroll与overflow:auto的差别,今天测试了一下,总算是明白了。visible: 不剪切内容。hidden: 将超出对象尺寸的内容进行裁剪,将不出现滚动条。scroll: 将超出对象尺寸的内容进行裁剪,并以滚动条的方式显示超出的内容。
这篇文章主要介绍了Vue Prop属性功能与用法,结合实例形式较为详细的分析了vue.js中Prop属性的功能、原理、使用方法及相关操作注意事项,写的十分的全面细致,具有一定的参考价值
层叠顺序的大小比较;层叠顺序级别高的元素覆盖级别低的元素。首先要注意,z-index:auto 虽然可以看作z-index:0 ,但是这仅仅是在层叠顺序的比较上;从层叠上下文上讲,二者有本质差别:auto 不会创建层叠上下文,z-index:0 会创建层叠上下文。
所有的计算属性都以函数的形式写在Vue实例中的computed选项内,最终返回计算后的结果。在一个计算属性中可以完成各种复杂的逻辑,包括运算、函数调用等,只要最终返回一个结果即可。
CSS分类目录 文本/字体/颜色 文本相关 字体相关 颜色相关 背景相关 大小/布局 大小属性 margin 外边距 padding 内边距 border 边框 position 定位 列表/表格 多列属性 可伸缩框属性 列表属性 Grid属性 Table属性 动画属性 Animation 动画属性 Transition 过渡属性
word-wrap正常来说,在一行文本中,如果出现这一行已经放不下的单词,浏览器会自动将该文字转入下一行。white-space规定段落中的文本不进行换行。
border 在一个声明中设置所有的边框属性。 border-bottom在一个声明中设置所有的下边框属性。border-bottom-color设置下边框的颜色。border-bottom-style设置下边框的样式。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!