JS高级-with、eval以及严格模式

一、JS碎片知识补充

  • 在JS当中,往往有很多小块的内容,这些是阻拦我们深入学习的绊脚石,有很多小块内容的作用不在于使用,而在于学习其他内容是的触类旁通

    • 在这次的学习当中,我们就来学习其中的两小块,一个是with一个是eval,但不管是哪个,都是我们现在所不提倡使用的,作为一个了解,补足知见就OK

1.1. with语句

  • with 语句在 JavaScript 中是一个较为古老且具有争议的特性,它的目的是为了简化多次写入同一个对象的属性时的代码

    • 然而,JavaScript 的原始版本是在非常短的时间内设计出来的,这意味着很多设计决策都是出于快速实现功能的考虑,而非长远的语言健全性。这在早期的 JavaScript 中造成了许多设计上的缺陷,而with 语句就是因为这原因而产生几个明显问题

    • 最致命的问题在于:with 语句会创建自己的作用域,其中包含的变量可以指向传入的对象的属性,也可以指向外部作用域的变量(优先考虑传入的对象属性)。这种行为很容易造成混淆,特别是当对象属性和外部变量同名时,很难判断代码的真正意图,如图12-1

  • 由于我们不可能再去使用with语句(写法和函数一样),所以在学习函数的时候,才会说函数和全局是目前会产生作用域的内容

var obj = {
  name:"小余",
  age:18
}
var obj2 = {
  name:"coderwhy",
  age:35
}
with(obj){//会形成自己的作用域
  console.log(name)//小余
  console.log(age)//18
}
with(obj2){//会形成自己的作用域
  console.log(name)//coderwhy
  console.log(age)//35
}

图片  
 
图12-1  with形成作用域

  • 不过with语句所想要达成的简化多次写入同一个对象的属性时的代码,在后续可以通过ES6之后的语法解构进行实现

    • 所以说JS这门语言一直在变得更好,是值得我们去学习投资的

1.2. eval函数

  • eval函数的出现和落幕也跟with语句是相似的

    • eval是一个特殊的函数,它可以将传入的字符串当做JavaScript代码来运行

  • 而之所以不在开发中进行使用的原因是:

    • eval代码的可读性非常的差(代码的可读性是高质量代码的重要原则)

    • eval 可以执行任何 JavaScript 代码,这意味着恶意代码也可能被执行。如果被用来执行用户输入的代码,可能会导致跨站脚本攻击(XSS)等安全问题

    • eval的执行必须经过JS解释器生成,不能被JS引擎优化,从效率和性能角度来说,就会差上很多

    • 通过 eval 执行的代码可能难以调试,因为它是动态生成的,可能不容易追踪到源代码位置

var evalString = `var message = "Hello World;console.log(message)"`
eval(evalString)//执行字符串代码
console.log(message)
  • 而这些代码为什么没有被废弃,直到今天也依旧可以使用,也是有重要原因的

    • 作为 Web 开发中最核心的脚本语言,JavaScript 需要在全球范围内广泛的浏览器和设备上保持运行的兼容性。这限制了对旧有缺陷进行根本修复的可能性,因为修复这些缺陷可能会破坏大量现有的网站和应用

    • 而这也是为什么每年ECMAScript系列发布新的API的时候,除了ECMAScript 2015(ES6)之外,往后每一年直到ES15,基本上都是挤牙膏的原因

    • 在没有确定下来之前,是不会进行大幅度改动的,因为每一次的改动,都算得上不可逆的。一旦打算废弃,所花费的代价是非常大的,所以更常用的做法是让时间去掩埋其中存在的痕迹,这也是我们对于这些内容产生陌生的原因

  • 但因为这个原因,所以JS目前的内容是非常稳固的,学习之后,在未来很多年都不会失去收益。而各种框架和第三方库就相对不稳定多了,无时无刻不再快速迭代,所带来的负面作用则是会偶尔产生破坏性的更新

    • 这种更新通常是大版本更新,因为各种同类竞争严重所产生的效果,内容繁多,也是大多数人学习非常累的原因

    • 学习没多久的内容就会落伍,就会失去效益,就像是吊在眼前的萝卜,诱人但直到失去力气都吃不到的东西。产生的原因就来自于我们自身

    • 所以需要通过学习其中相对稳定的内容,从这稳定的思维图式去延伸到新颖、前沿、不确定的内容上去

图片  
 
图12-2  层出不穷的技术如眼前的胡萝卜

二、严格模式

严格模式(strict mode)是JavaScript语言的一个功能,目的是让代码运行在一个更严格的语法和行为环境中

2.1. 认识严格模式

  • 在ECMAScript5标准中,JavaScript提出了严格模式的概念(Strict Mode)

    • 严格模式很好理解,是一种具有限制性的JavaScript模式,从而使代码隐式的脱离了 ”懒散(sloppy)模式“

    • 支持严格模式的浏览器在检测到代码中有严格模式时,会以更加严格的方式对代码进行检测和执行

  • 严格模式对正常的JavaScript语义进行了一些限制:

    • 通过 抛出错误 来消除一些原有的 静默(silent)错误。术语 "静默"(silent) 错误指的是那些发生时不通知用户的错误

      图片  
       
      图12-3  奇怪的代码运行方式

    • JS引擎在执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理)

    • 为未来新版本的JavaScript做好铺垫。一些在严格模式中被禁用的语法或特性(比如关键字和保留字),可能是为了与将来JavaScript的发展方向保持一致,防止在以后发生错误

    • 提高编译器效率,增加运行速度。严格模式可以帮助JavaScript引擎优化代码执行

    1. 这种错误的处理方式通常会让问题长时间隐藏在代码中,直到产生更严重的后果时才被发现,这会增加调试和维护的难度

    2. 大多数情况下,就算马上发现了,我们会很讨厌这种错误,因为没有明确的报错,就需要去猜。代码复杂了就不是很好猜

    3. 还有可能形成用一个错误去弥补另一个错误,最后代码以奇怪的方式跑起来了,如图12-3

2.1.1. 开启严格模式
  • 那么如何开启严格模式呢?严格模式支持粒度话的迁移:

    • 可以支持在js文件中开启严格模式

    • 也支持对某一个函数开启严格模式;

  • 严格模式通过在文件或者函数开头使用 "use strict " 来开启,如图12-4

图片  
 
图12-4  开启全局严格模式

"use strict"//开启严格模式

//使用let作为标识符的名称
var name = "abc"
console.log(name)

//定义变量时不使用var
var message = "Hello World"
console.log(message)
function foo(){//在函数内开启严格模式
    "use strict";
    
    m = "foo"
    console.log(m)
}

foo()
  • 通过这两种方式,其实我们是可以猜到严格模式的运行范围的

    • 作用域范围,即是严格模式的生效范围

    • 在全局就所有地方生效,在函数作用域就该函数内生效

    • 但不管是哪一种方式,都需要写在作用域的最上面

  • 在全局开启严格模式,这种方式存在陷阱,我们不能盲目地合并冲突代码

    • 试想合并一个严格模式的脚本和一个非严格模式的脚本:合并后的脚本代码看起来是严格模式

    • 反之亦然:非严格合并严格看起来是非严格的。合并均为严格模式的脚本或均为非严格模式的都没问题,只有在合并严格模式与非严格模式有可能有问题

    • 建议按一个个函数去开启严格模式(至少在学习的过渡期要这样做)

2.2. 严格模式限制

这里我们来说几个严格模式下的严格语法限制:

  • JavaScript为了让新手开发者更容易上手,所以有时候本来错误语法,被认为也是可以正常被解析的

  • 但是这种宽松的设计方式可能给以后留下安全隐患

  • 而在严格模式下,这种失误就会被当做错误,以便可以快速的发现和修正

    • 之所以不直接将严格模式替换为默认的JS运行环境,还是因为我们之前说的原因,改变几乎是不可逆的,一旦将严格模式设置为默认情况,之前的很多老代码都将无法运行,这是不能接受的事情,也是全局开启严格模式所存在的陷阱

    • 所以才需要将严格模式设置为可选的情况,拔高这门语言的上限,从这个角度看,过于自由的语言会带来较为严重的隐患,但在一门语言的发展中,是不可避免会出现的事情

    • 随着发展的推移,设计得严谨,就会像C++中的内容变得非常繁琐冗余,设计得宽松则会像JS出现各种缺陷。新的语言诞生会吸收以往的教训,形成在当下更好的形式,并且以极快的速度发展,但当新语言成为前沿者,没有其他教训可以参考的时候,也会走上这条权衡利弊的道路,去摸索其中的平衡点

    • 所以,不需要纠结要不要学习最新的语言,而是应当根据自己的需求去学习。避免被潮流所淹没思考,但至少就前端来说,只有一门JS语言以及它的超集TS语言,如果要学习前端,就不至于去纠结这些问题

  • 市场上仍然有大量的浏览器版本只部分支持严格模式或者根本就不支持,所以使用的时候,不要盲目依赖

2.2.1. 限制内容

无法意外地创建全局变量

  • 非严格模式下,如果一个变量没有被声明(即没有使用var, let, const),它会自动成为全局变量,这种声明方式我们之前说过是隐式声明。这增加了全局污染的风险,代码会更难维护和理解

  • 严格模式下,如果尝试使用未声明的变量,JavaScript会抛出错误,从而发现并修正这种潜在的错误

严格模式会使引起静默失败的赋值操作抛出异常

  • 非严格模式下,某些赋值失败(如尝试写入只读属性)不会报错,只是静默失败

  • 严格模式改变了这一行为,这类赋值失败也会抛出异常,从而即时通知开发者问题的存在,增加代码的透明度和可预测性

严格模式下试图删除不可删除的属性

  • 非严格模式下,删除一个不可配置(non-configurable)的属性时不会报错,只是操作无效

  • 严格模式下,这种操作会抛出错误,提醒开发者这一行为是不允许的,避免错误预期

严格模式不允许函数参数有相同的名称

  • 允许重名的参数可以导致代码逻辑混乱,难以理解和错误的参数值使用

  • 严格模式禁止这种情况,确保函数的参数列表的清晰和一致性

不允许0开头的八进制语法

  • 非严格模式下,以0开头的数字被视为八进制数。这种语法在某些情况下可能导致混淆和错误

  • 严格模式中,八进制必须明确使用新的0o0O前缀,提高代码的清晰度

**在严格模式下,不允许使用with**:

  • with语句可以改变代码块的作用域链,增加运行时错误的风险,并且使得代码更难优化和分析

  • 在严格模式下禁用with,以便保持作用域的清晰和代码的可优化性

在严格模式下,eval不再为上层引用变量

  • 非严格模式下,eval可以修改它的调用环境的作用域

  • 严格模式中,eval有自己的作用域,不影响外部作用域,这样增加了代码的安全性和模块化

严格模式下,this绑定不会默认转成对象

  • 非严格模式下,函数中的this如果是基本类型(如数字或字符串),会自动转换为对应的对象

  • 严格模式下,this保持原有的值(如果没有明确的对象绑定,this值为undefined),这样减少了意外的类型转换,使得函数行为更加可预测

这些是限制内容是最常见的类型,不需要刻意去记,只需要了解就行

//常见的限制

//1.以外创建全局变量,不会生效而是报错
message = "Hello World"
console.log(message);
//同样的在严格模式下会报错
function foo(){
    age = 18
}
foo()
console.log(age);

//2.不允许函数有相同的参数名称
function foo(x,y,x){//两个x就是相同参数名称,如果不开启严格模式,后面的x会将前面的x覆盖掉
    console.log(x,y,x);
}
foo(10,20,30)//30,20,30(非严格模式)

//3.静默错误
true.name = "xiaoyu"
NaN = 123//非严格模式下不会报错
var obj = {}
Object.defineProperty(obj,'name',{
    configurable:false,//不可配置
    writable:false,//不可写
    value:"why"
})
console.log(obj.name)
obj.name = "xiaoyu"//静默错误,因为我们已经设置不可写入了

//4.不允许使用原先的八进制格式(严格模式)
var num = 0123//八进制
var num2 = 0x123//十六进制
var num3 = 0b100//二进制
console.log(num,num2,num3)//Uncaught SyntaxError: Octal literals are not allowed in strict mode

//5.eval函数不会向上引用变量
var jsString = "var message = 'Hello World';console.log(message)"
eval(jsString)
console.log(message)//这里会报错
2.2.2. 严格模式下的this
  • this指向在太过自由的环境下,效果会偏向于"非直观",难以预测

    • 而严格模式下的this并不会改变其规则逻辑,而是强化其使用的直观效果

  • 而主要改变的直观效果主要有一点:

    • 因为我们知道,this指向全局的时候,是有两种情况,一种是window,一种是global。这是在两种不同环境(浏览器、Node)下所指向的内容

    • 而在全局上面添加内容其实是我们所不提倡的事情,缺陷很大,因此除非在全局当中明确进行使用this指向之外。不允许函数中的this继续指向于全局,在这种情况下,我们之前延伸出来的一堆this面试题里的大部分情况其实都可以排除掉了,这也是有的人不喜欢面试题的原因,因为有部分面试题其实是拿缺陷当做特性来考察,但这种是属于比较没必要的事情,因为我们并不会那么写

    • 就拿我们曾经做过的this面试题来看下,在全局作用域中的函数内部的this曾经指向全局,在函数内如今不管在Node环境下还是浏览器下,都是指向于undefined,如图12-5。从这角度来说,在使用上,尤其是函数是我们使用频率最高的内容之一,能够避免很多隐患问题

    1. 在函数中,如果函数调用不明确设置thisthis 的值将是 undefined,而不是自动指向全局对象

"use strict"
//案例2
function foo1(){
    console.log("foo1",this);
}

function foo2(){
    console.log("foo2",this);
    foo1()
}

function foo3(){
    console.log("foo3",this);
    foo2()
}

foo3()

图片  
 
图12-5  严格模式下的this结果

  • 包括说,在自执行函数中,我们如果使用this.localStorage.setItem来使用全局的方法,也可以直接localStorage.setItem,会自动找到全局中的方法

"use strict"
//之前编写的代码中,自执行函数我们是没有使用过this直接去引用window的
function foo(){
    console.log(this)
    //通常在自执行函数里面我们想要调用window中的name属性的时候,我们不使用this.name
    localStorage.setItem//localStorage也会指向window
} 
foo()//正常情况下this指向window,当开启了严格模式后,自执行函数(默认绑定)会指向undefined

//setTimeout的this
setTimeout(()=>{
    console.log(this)
},2000)//window

setTimeout(function(){
    console.log(this)
},2000)//非严格模式下是window,严格模式下依旧是window,而不是undefined
//this指向window,且是自执行函数,为什么不会是undefined呢?那是因为里面可能执行了一次fn.apply(window),手动指向了this
//这个是在浏览器中实现的(伪造fake出来的setTimeout),而不是在v8引擎中实现的
  • 而我们曾经说过的特殊情况则没有改变,在严格模式下,定时器的this依旧指向于window,这是因为定时器如果不是内部中对this进行了显式的绑定处理,让我们在使用的时候不会受到其影响,就是浏览器做出了处理

    • 从Chrome的Chromium源码中,我们可以看到这么一段代码,对定时器的特殊处理:

      如果是字符串,就使用eval执行。如果是其他类型,则对其绑定this,而这里的显式绑定的this显然就是fakeWin,有什么操作都通过fake进行传达,所以可以通过这种方式,对一些特殊情况的this进行处理,如图12-6

图片  
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值