firefox下delete有bug?

链式极昼
• 阅读 2812

看书时,发现firefox 35.0下的控制台或者firebug里,delete表现跟理论上不太一样,chrome表现则很正常,难道firefox有问题?

搜了篇文章,看完才知道问题出在哪里,分享一下。Understanding delete

Theory

那么, 为什么我们能够删除对象的属性:

var o = { x: 1 };
delete o.x; // true
o.x; // undefined

却不能删除这样声明的对象:

var x = 1;
delete x; // false
x; // 1

或者函数呢:

function x(){}
delete x; // false
typeof x; // "function"

注意: 当一个属性无法被删除时,delete操作符只会返回false

要理解这个, 我们首先需要掌握这些有关变量实例和属性特性的概念——这些概念很不幸地, 很少在JavaScript书中被提及. 我将试着在接下来的几个段落中简单地复习一下这些概念. 这些概念是很难理解的!如果你不在乎"为什么这些东西会以这种方式工作"的话,尽情跳过这一章节好了.
代码的类型:

在ECMAScript中, 有3种不同类型的可执行代码: 全局代码(Global code), 函数代码(Function code)和 Eval代码(Eval code). 这些类型从名称上来说或多或少是有自解释性的, 这里有一个简短的概述:

当一段源代码被看成程序(Program)时, 它将会在全局环境下被执行, 并且被认为是全局代码(Global code). 在一个浏览器环境中, 脚本元素的内容通常被解释为程序, 因此被作为全局代码来执行.

任何直接在一个函数中执行的代码显然被认为是函数代码(Function code). 在浏览器中, 事件属性的内容(如 <p onclick="....">)通常被解释成函数代码.

最后, 被应用到内置函数eval的代码文本被解释成Eval代码(Eval code). 很快我们会发现为什么这种类型是特殊的.

执行上下文(Execution context):

当ECMAScript代码执行时, 它通常会发生在特定的执行上下文中.执行上下文是一个有些抽象的实体概念, 它能帮助理解范围(Scope)和变量实例(Variable instantiation)是如何工作的. 对三种可执行代码的每一种, 都有一个执行上下文相对应. 当一个函数被执行的时候, 我们说"程序控制进入了函数代码的执行上下文"; 当一段全局代码被执行时, 程序控制进入了全局代码的执行上下文, 等等.

正如你所见, 执行上下文可以在逻辑上构成一个堆栈. 首先, 可能有一段全局代码和其自己的执行上下文, 然后这段代码可能会调用一个函数, 并带着它(函数)的执行上下文. 这段函数可以调用另外一个函数, 等等等等. 即使函数是递归调用的, 每次调用时被也会进入一个新的执行上下文.
活动对象(Activation object) / 变量对象(Variable Object):

每一个执行上下文都有一个跟其所关联的所谓变量对象(Variable Object). 类似于执行上下文, 变量对象是一个抽象实体, 一种用来描述变量实例的机制. 有趣之处在于, 在源代码中声明的变量和函数通常会被当做属性(properties)增加到这个变量对象上.

当程序控制进入全局代码的执行上下文时, 一个全局对象(Global object)被用来作为一个变量对象. 这正是为什么声明为全局的函数变量会变成全局对象属性的原因.

/* remember that `this` refers to global object when in global scope */
var GLOBAL_OBJECT = this;

var foo = 1;
GLOBAL_OBJECT.foo; // 1
foo === GLOBAL_OBJECT.foo; // true

function bar(){}
typeof GLOBAL_OBJECT.bar; // "function"
GLOBAL_OBJECT.bar === bar; // true

好, 所以全局变量会变成全局对象的属性, 但是局部变量(那些在函数代码中定义的变量)会发生什么呢? 其实它们的行为也非常类似: 它们会变成变量对象(Variable object)的属性. 唯一的不同在于, 当在函数代码中时, 一个变量对象并不是全局对象, 而是所谓的活动对象(Activation object). 活动对象在会每次进入函数代码的执行上下文时被创建.

并不是只有在函数代码中声明的变量和函数会变成活动对象的属性; 这也会在每个函数参数(对应相应的形式参数的名称)和一个特殊的Arguments对象(以arguments为名称)上发生. 注意, 活动对象是一个内部描述机制, 在程序代码中并不能被访问.

(function(foo){

  var bar = 2;
  function baz(){}

  /*
  In abstract terms,

  Special `arguments` object becomes a property of containing function's Activation object:
    ACTIVATION_OBJECT.arguments; // Arguments object

  ...as well as argument `foo`:
    ACTIVATION_OBJECT.foo; // 1

  ...as well as variable `bar`:
    ACTIVATION_OBJECT.bar; // 2

  ...as well as function declared locally:
    typeof ACTIVATION_OBJECT.baz; // "function"
  */

})(1);

最后, 在Eval代码中声明的变量会成为调用者上下文(calling context)的变量对象的属性. Eval代码只是简单地使用调用它的代码的执行上下文的变量对象.

var GLOBAL_OBJECT = this;

/* `foo` is created as a property of calling context Variable object,
  which in this case is a Global object */

eval('var foo = 1;');
GLOBAL_OBJECT.foo; // 1

(function(){

  /* `bar` is created as a property of calling context Variable object,
  which in this case is an Activation object of containing function */

  eval('var bar = 1;');

  /*
  In abstract terms,
  ACTIVATION_OBJECT.bar; // 1
  */

})();

属性的特性(property attributes)

我们几乎是已经在这了. 既然我们已经很清楚在变量上发生了什么(它们变成了属性), 唯一剩下的需要理解的概念就是属性的特性(property attributes)了. 每一个属性可以拥有0个或多个特性, 它们从以下集合中选取: ReadOnly, DontEnum, DontDelete和 Internal. 你可以把它们认为是flags —— 一种特性可以在属性中存在, 也可以不存在. 对于我们今天的讨论来说, 我们只对DontDelete感兴趣.

当被声明的变量和函数成为变量对象(或者函数代码的活动对象, 或全局代码的全局对象)的属性时, 这些属性在创建时就带上了DontDelete的特性. 然而, 任何显式(或隐式)的属性赋值所建立的属性将不会被带上DontDelete特性. 这就是为什么我们能够删除一些属性, 但删除不了其它的.

var GLOBAL_OBJECT = this;

/*  `foo` is a property of a Global object.
    It is created via variable declaration and so has DontDelete attribute.
    This is why it can not be deleted. */

var foo = 1;
delete foo; // false
typeof foo; // "number"

/* `bar` is a property of a Global object.
   It is created via function declaration and so has DontDelete attribute.
   This is why it can not be deleted either. */

function bar(){}
delete bar; // false
typeof bar; // "function"

/*  `baz` is also a property of a Global object.
    However, it is created via property assignment and so has no DontDelete attribute.
    This is why it can be deleted. */

GLOBAL_OBJECT.baz = 'blah';
delete GLOBAL_OBJECT.baz; // true
typeof GLOBAL_OBJECT.baz; // "undefined"

内置对象和DontDelete

所以, 这就是有关它(DontDelete)的所有: 属性的一个特殊特性, 用来控制这个属性是否能够被删除. 注意, 有些内置对象的属性是指定含有DontDelete的, 所以无法被删除. 如特殊的arguments变量(或者, 正如我们现在所知道的, 一个活动对象的属性)拥有DontDelete. 函数实例的length属性也具有DontDelete属性.

(function(){

  /* can't delete `arguments`, since it has DontDelete */

  delete arguments; // false
  typeof arguments; // "object"

  /* can't delete function's `length`; it also has DontDelete */

  function f(){}
  delete f.length; // false
  typeof f.length; // "number"

})();

函数参数所对应的属性也是从建立开始就拥有DontDelete特性的, 所以我们也无法删除它.

(function(foo, bar){

  delete foo; // false
  foo; // 1

  delete bar; // false
  bar; // 'blah'

})(1, 'blah');

未声明的赋值:

你可能还记着, 未声明的赋值会在全局对象上建立一个属性, 除非这个属性已经在这个作用域链中全局对象之前的其它地方被找到. 并且, 现在我们知道属性赋值和变量声明的不同之处——后者会设置DontDelete属性, 但前者不会. 我们必须清楚, 为什么未声明的赋值会建立一个可删除的属性.

var GLOBAL_OBJECT = this;

/* create global property via variable declaration; property has DontDelete */
var foo = 1;

/* create global property via undeclared assignment; property has no DontDelete */
bar = 2;

delete foo; // false
typeof foo; // "number"

delete bar; // true
typeof bar; // "undefined"

请注意: 特性是在属性被创建时被决定的, 之后的赋值不会修改已存在属性的特性. 理解这一点区别非常重要.

/* `foo` is created as a property with DontDelete */
function foo(){}

/* Later assignments do not modify attributes. DontDelete is still there! */
foo = 1;
delete foo; // false
typeof foo; // "number"

/* But assigning to a property that doesn't exist,
   creates that property with empty attributes (and so without DontDelete) */

this.bar = 1;
delete bar; // true
typeof bar; // "undefined"

Firebug的困惑:

在Firebug中发生了什么? 为什么在console中声明的变量可以被删除, 这不是违背了我们之前所学到的知识么? 嗯, 就像我之前所说的那样, Eval代码在面对变量声明时会有特殊的表现. 在Eval中声明的变量实际上是作为不带DontDelete特性的属性被创建的.

eval('var foo = 1;');
foo; // 1
delete foo; // true
typeof foo; // "undefined"

同样, 类似的, 当在函数代码中调用时:

(function(){

  eval('var foo = 1;');
  foo; // 1
  delete foo; // true
  typeof foo; // "undefined"

})();

这就是Firebug反常行为的依据. 在console中的所有文本都会被当做Eval代码来解析和执行, 而不是全局或函数代码. 显然, 这里声明的所有变量最后都会成为不带DontDelete特性的属性, 所以它们都能被轻松删除. 我们需要了解这个在全局代码和Firebug控制台之间的差异.
通过Eval来删除变量:

这个有趣的eval行为, 再加上ECMAScript的另一个方面, 可以在技术上允许我们删除"non-deletable"的属性. 有关函数声明的一点是, 它们能够覆盖相同执行上下文中同名的变量.

function x(){ }
var x;
typeof x; // "function"

注意函数声明是如何获得优先权并且覆盖同名变量(或者, 换句话说, 在变量对象中的相同属性)的. 这是因为函数声明是在变量声明之后被实例化的, 并且被允许覆盖它们(变量声明). 函数声明不仅会替换掉一个属性的值, 它还会替换掉那个属性的特性. 如果我们通过eval来声明一个函数, 那个函数就应该会用它自己的特性来替换掉原有的(被替换的)属性的特性. 并且, 由于通过eval声明的变量会创建不带DontDelete特性的属性, 实例化这个新函数将会实际上从属性中删除已存在的DontDelete特性, 从而使得一个属性能够被删除(并且, 显然会将其值指向新创建的函数).

var x = 1;

/* Can't delete, `x` has DontDelete */

delete x; // false
typeof x; // "number"

eval('function x(){}');

/* `x` property now references function, and should have no DontDelete */

typeof x; // "function"
delete x; // should be `true`
typeof x; // should be "undefined"

不幸的是, 这种"欺骗"在目前的任何实现中都不起作用. 也许我在这漏掉了什么, 或者是这种行为只是太晦涩了以至于实现者都没有注意到它.

点赞
收藏
评论区
推荐文章
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Easter79 Easter79
4年前
tcp_tw_recycle参数引发的数据库连接异常
【问题描述】开发反馈有个应用在后端数据库某次计划性重启后经常会出现数据库连接异常问题,通过监控系统的埋点数据,发现应用连接数据库异常有两类表现:  其一:连接超时  <spanstyle"backgroundcolor:FFFF00"131148.00msTomcatConnectionPool</span  其二:连接耗时过
Gwendolyn62 Gwendolyn62
5年前
MySQL的语句执行顺序
今天遇到一个问题就是mysql中insertinto和update以及delete语句中能使用as别名吗?目前还在查看,但是在查阅资料时发现了一些有益的知识,给大家分享一下,就是关于sql以及MySQL语句执行顺序:sql和mysql执行顺序,发现内部机制是一样的。最大区别是在别名的引用上。 一、sql执行顺序 
Karen110 Karen110
4年前
​一篇文章总结一下Python库中关于时间的常见操作
前言本次来总结一下关于Python时间的相关操作,有一个有趣的问题。如果你的业务用不到时间相关的操作,你的业务基本上会一直用不到。但是如果你的业务一旦用到了时间操作,你就会发现,淦,到处都是时间操作。。。所以思来想去,还是总结一下吧,本次会采用类型注解方式。time包importtime时间戳从1970年1月1日00:00:00标准时区诞生到现在
Wesley13 Wesley13
4年前
Unity3d学习笔记之(三)
这个必须要吐槽一下,花了一个星期,才把groundplane在iphone5s上跑起来了。中间各种坑。参考了网上的各种资料。最后发现问题出在官网教程有一个问题,所以导致没有办法运行。因此在这里留个贴,希望后续大家不要再这个问题上跌倒了。系统1Win7,Unity3d2017.4.10f1,Vuforia7.0.43, 红米Pro,And
Stella981 Stella981
4年前
Firefox 因为全球服务数据同步丢失问题
之前用Firefox时,知道有国际版,国际版和中国版数据不互通。装个中国版就好。然后在不同的电脑和手机设备上,使用同一账号,便可以同步数据。前几天由于换电脑,重新装了一下Firefox,登陆账号提示我账号不存在,还以为Firefox有问题,当时感冒头晕,脑子也转不过来,同事提醒后才知道,肯定是我全球服务和本地服务搞混了。仔细观察发现,我新安装
Wesley13 Wesley13
4年前
MySQL 清空表(truncate)与删除表中数据(delete) 详解
删除表信息的方式有两种:truncatetabletable\_name;delete\fromtable\_name;注:truncate操作中的table可以省略,delete操作中的\可以省略truncate、delete清空表数据的区别:1truncate是整体删除(速度较快),delete是逐条删
Stella981 Stella981
4年前
IE浏览器上传图片的type类型小坑
写了个CMS里面传图片一直在IE6.0、IE7.0、IE8.0里面传不上去。一直没管它,因为后台都用firefox,没有问题。但是今天遇到问题了,同事那里只有XPIE,然后仔细观察了一下,发现是IE678上传图片的时候会把type做如下的转换:image/jpeg    image/pjpegimage/png    i
Wesley13 Wesley13
4年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。
Stella981 Stella981
4年前
Redis3.0安装 for win10
平时没有用过nosql,这次准备自己安装Redis学习学习,我首先选择的版本是Redis3.0.1windowsX64版本,使用win10系统(感觉开虚拟机太麻烦),有问题请不要见笑,可以指点谢谢在官方下载后发现与网上的大部分安装说明都不一样,查了一下资料才知道,需要编译一下,所以我直接下载了编译后的文件,‍‍‍‍https://yunpan
Wesley13 Wesley13
4年前
Java动态追踪技术探究
引子在遥远的希艾斯星球爪哇国塞沃城中,两名年轻的程序员正在为一件事情苦恼,程序出问题了,一时看不出问题出在哪里,于是有了以下对话:“Debug一下吧。”“线上机器,没开Debug端口。”“看日志,看看请求值和返回值分别是什么?”“那段代码没打印日志。”“改代码,加日志,重新发布一次。”“怀疑是线程池的问题,重启会破坏现场。”