转行学前端的第 29 天 : 了解 ECMAScript 语句

3,213 阅读20分钟

我是小又又,住在武汉,做了两年新媒体,准备用 6 个月时间转行前端。

今日学习目标


昨天基于一些页面搜索,学习了《JavaScirpt 高级程序设计》(第三版)  第 3 章节中的 3.5 操作符。所以,今天主要是基于搜索来仔细学习 第 3 章节中的 3.6 语句,又是适合学习的一天,加油,小又又!!!!


今日学习概要

ECMA-262 规定了一组语句,从本质上看,语句定义了 ECMAScript 中的主要语法,语句通常使用一或多个关键字来完成给定任务。

语句可以很简单,例如通知函数退出
也可以比较复杂,例如指定重复执行某个命令的次数

  • if 语句
  • do-while 语句
  • while 语句
  • for 语句
  • for-in 语句
  • label 语句
  • break 和 continue 语句
  • with 语句
  • switch 语句
  • debugger 语句

if 语句

基础说明

当指定条件为真,if 语句会执行一段语句。如果条件为假,则执行另一段语句。

function testNum(a) {
  let result;
  if (a > 0) {
    result = 'positive';
  } else {
    result = 'NOT positive';
  }
  return result;
}

console.log(testNum(-5));
// expected output: "NOT positive"


语法说明

if (condition)
   statement1
[else
   statement2]
语句名 具体说明
condition 值为真或假的表达式
statement1 condition为真时执行的语句。可为任意语句,包括更深层的内部if语句。要执行多条语句,使用语句({ ... })将这些语句分组;若不想执行语句,则使用语句。
statement2 如果condition为假且 else从句存在时执行的语句。可为任意语句,包括块语句和嵌套的if语句。

详细说明


多层 if...else 语句可使用 else if 从句。

注意:在 Javascript 中没有 elseif (一个单词)关键字。

if (condition1)
   statement1
else if (condition2)
   statement2
else if (condition3)
   statement3
...
else
   statementN


要看看它如何工作,可以调整下嵌套的缩进

if (condition1)
   statement1
else
   if (condition2)
      statement2
   else
      if (condition3)
...

要在一个从句中执行多条语句,可使用语句块({ ... })。通常情况下,一直使用语句块是个好习惯,特别是在涉及嵌套if语句的代码中:

if (condition) {
   statements1
} else {
   statements2
}

不要将原始布尔值的truefalseBoolean对象的真或假混淆。任何一个值,只要它不是 undefinednull0NaN或空字符串(""),那么无论是任何对象,即使是值为假的Boolean对象,在条件语句中都为真。

例如:

var b = new Boolean(false);
if (b) //表达式的值为true

使用案例

使用 if...else

if (cipher_char === from_char) {
   result = result + to_char;
   x++;
} else {
   result = result + clear_char;
}

使用 else if



注意,Javascript中没有`elseif`语句。但可以使用`else`和`if`中间有空格的语句:
if (x > 5) {
 /* do the right thing */
} else if (x > 50) {
 /* do the right thing */
} else {
 /* do the right thing */
}

条件表达式中的赋值运算

建议不要在条件表达式中单纯的使用赋值运算,因为粗看下赋值运算的代码很容易让人误认为是等性比较。

比如,不要使用下面示例的代码:

if (x = y) {
   /* do the right thing */
}


如果你需要在条件表达式中使用赋值运算,用圆括号包裹赋值运算。例如:

if ((x = y)) {
   /* do the right thing */
}

do-while 语句

基础说明

do...while 语句创建一个执行指定语句的循环,直到condition值为 false。在执行statement 后检测condition,所以指定的statement至少执行一次。


语法说明

do
   statement
while (condition);
语句名 具体说明
statement 执行至少一次的语句,并在每次 condition 值为真时重新执行。想执行多行语句,可使用block语句({ ... })包裹这些语句。
condition 循环中每次都会计算的表达式。如果 condition 值为真, statement 会再次执行。当 condition 值为假,则跳到do...while之后的语句。

使用案例


下面的例子中,do...while 循环至少迭代一次,并且继续迭代直到 i不再小于 5 时结束。

HTML 内容

<div id="example"></div>

JavaScript 内容

var result = '';
var i = 0;
do {
   i += 1;
   result += i + ' ';
} while (i < 5);
document.getElementById('example').innerHTML = result;

结果



while 语句

基础说明

while 语句可以在某个条件表达式为真的前提下,循环执行指定的一段代码,直到那个表达式不为真时结束循环。

let n = 0;

while (n < 3) {
  n++;
}

console.log(n);
// expected output: 3


语法说明

while (condition)
  statement
语句名 具体说明
condition 条件表达式,在每次循环前被求值。如果求值为真,statement就会被执行。如果求值为假,则跳出while循环执行后面的语句。
statement 只要条件表达式求值为真,该语句就会一直被执行。要在循环中执行多条语句,可以使用块语句({ ... })包住多条语句。注意:使用break语句在condition计算结果为真之前停止循环。

使用案例

下面的 while 循环会一直循环若干次,直到 n 等于 3

var n = 0;
var x = 0;
while (n < 3) {
  n++;
  x += n;
}


在每次循环中,n 都会自增 1,然后再把 n 加到 x 上。因此,在每轮循环结束后,xn 的值分别是:

  • 第一轮后:n = 1,x = 1
  • 第二轮后:n = 2,x = 3
  • 第三轮后:n = 3,x = 6

当完成第三轮循环后,条件表达式n< 3 不再为真,因此循环终止。


for 语句

基础说明

for 语句用于创建一个循环,它包含了三个可选的表达式,这三个表达式被包围在圆括号之中,使用分号分隔,后跟一个用于在循环中执行的语句(通常是一个块语句)。

let str = '';

for (let i = 0; i < 9; i++) {
  str = str + i;
}

console.log(str);
// expected output: "012345678"


语法说明

for ([initialization]; [condition]; [final-expression])
   statement
语句名 具体说明
initialization 一个表达式 (包含赋值语句) 或者变量声明。典型地被用于初始化一个计数器。该表达式可以使用 var 或 let 关键字声明新的变量,使用 var 声明的变量不是该循环的局部变量,而是与 for 循环处在同样的作用域中。用 let 声明的变量是语句的局部变量。该表达式的结果无意义。
condition 一个条件表达式被用于确定每一次循环是否能被执行。如果该表达式的结果为 true,statement 将被执行。这个表达式是可选的。如果被忽略,那么就被认为永远为真。如果计算结果为假,那么执行流程将被跳到 for 语句结构后面的第一条语句。
final-expression 每次循环的最后都要执行的表达式。执行时机是在下一次 condition 的计算之前。通常被用于更新或者递增计数器变量。
statement 只要condition的结果为true就会被执行的语句。要在循环体内执行多条语句,使用一个块语句{ ... })来包含要执行的语句。没有任何语句要执行,使用一个空语句;)。

使用案例

使用 for


以下例子声明了变量 i 并被初始赋值为 0for 语句检查 i 的值是否小于 9,如果小于 9,则执行语句块内的语句,并且最后将 i 的值增加 1。

for (var i = 0; i < 9; i++) {
   console.log(i);
   // more statements
}

可选的 for 表达式


for 语句头部圆括号中的所有三个表达式都是可选的。
例如,初始化块中的表达式没有被指定:

var i = 0;
for (; i < 9; i++) {
    console.log(i);
    // more statements
}

像初始化块一样,条件块也是可选的。如果省略此表达式,则必须确保在循环体内跳出,以防创建死循环。

for (var i = 0;; i++) {
   console.log(i);
   if (i > 3) break;
   // more statements
}

你当然可以忽略所有的表达式。同样的,确保使用了 break >语句来跳出循环并且还要修改(增加)一个变量,使得 break 语句的条件在某个时候是真的。

var i = 0;
for (;;) {
  if (i > 3) break;
  console.log(i);
  i++;
}

使用无语句的 for


以下 for 循环计算 final-expression 部分中节点的偏移位置,它不需要执行一个 statement 或者一组 block statement ,因此使用了空语句。

function showOffsetPos(sId) {
  var nLeft = 0, nTop = 0;
  for (
    var oItNode = document.getElementById(sId); /* initialization */
    oItNode; /* condition */
    nLeft += oItNode.offsetLeft, nTop += oItNode.offsetTop, oItNode = oItNode.offsetParent /* final-expression */
  ); /* 分号 semicolon */ 
  console.log('Offset position of \'' + sId + '\' element:\n left: ' + nLeft + 'px;\n top: ' + nTop + 'px;');
}
/* Example call: */
showOffsetPos('content');
// Output:
// "Offset position of "content" element:
// left: 0px;
// top: 153px;"

提示:这里的分号是强制性的,是 JavaScript 中的少数几种强制分号的情况。如果没有分号,循环声明之后的行将被视为循环语句。


for-in 语句

基础说明

for...in语句以任意顺序遍历一个对象的除Symbol以外的可枚举属性。


语法说明

for (variable in object)
  statement
语句名 具体说明
variable 在每次迭代时,variable会被赋值为不同的属性名。
object 非Symbol类型的可枚举属性被迭代的对象。

详细说明


for...in 循环只遍历可枚举属性(包括它的原型链上的可枚举属性)。像 ArrayObject使用内置构造函数所创建的对象都会继承自Object.prototypeString.prototype的不可枚举属性。

例如 StringindexOf()  方法或 ObjecttoString()方法。循环将遍历对象本身的所有可枚举属性,以及对象从其构造函数原型中继承的属性(更接近原型链中对象的属性覆盖原型属性)。


删除,添加或者修改属性


for...in 循环以任意序迭代一个对象的属性(请参阅delete运算符,了解为什么不能依赖于迭代的表面有序性,至少在跨浏览器设置中)。

如果一个属性在一次迭代中被修改,在稍后被访问,其在循环中的值是其在稍后时间的值。一个在被访问之前已经被删除的属性将不会在之后被访问。在迭代进行时被添加到对象的属性,可能在之后的迭代被访问,也可能被忽略。

通常,在迭代过程中最好不要在对象上进行添加、修改或者删除属性的操作,除非是对当前正在被访问的属性。

这里并不保证是否一个被添加的属性在迭代过程中会被访问到,不保证一个修改后的属性(除非是正在被访问的)会在修改前或者修改后被访问,不保证一个被删除的属性将会在它被删除之前被访问。


数组迭代和 for...in


**
提示:for...in不应该用于迭代一个 Array,其中索引顺序很重要。
数组索引只是具有整数名称的枚举属性,并且与通用对象属性相同。不能保证for ... in将以任何特定的顺序返回索引。

for ... in循环语句将返回所有可枚举属性,包括非整数类型的名称和继承的那些。

因为迭代的顺序是依赖于执行环境的,所以数组遍历不一定按次序访问元素。因此当迭代访问顺序很重要的数组时,最好用整数索引去进行for循环(或者使用 Array.prototype.forEach()for...of 循环)。


仅迭代自身的属性

如果你只要考虑对象本身的属性,而不是它的原型,那么使用 getOwnPropertyNames() 或执行 hasOwnProperty() 来确定某属性是否是对象本身的属性(也能使用propertyIsEnumerable)。或者,如果你知道不会有任何外部代码干扰,可以使用检查方法扩展内置原型。


使用案例

下面的函数接受一个对象作为参数。被调用时迭代传入对象的所有可枚举属性然后返回一个所有属性名和其对应值的字符串。

var obj = {a:1, b:2, c:3};

for (var prop in obj) { console.log("obj." + prop + " = " + obj[prop]); } // Output: // "obj.a = 1" // "obj.b = 2" // "obj.c = 3"


下面的函数说明了hasOwnProperty()的用法:继承的属性不显示。

var triangle = {a: 1, b: 2, c: 3};
function ColoredTriangle() {
  this.color = 'red';
}
ColoredTriangle.prototype = triangle;
var obj = new ColoredTriangle();
for (var prop in obj) {
  if (obj.hasOwnProperty(prop)) {
    console.log(`obj.${prop} = ${obj[prop]}`);
  } 
}
// Output:
// "obj.color = red"

label 语句

基础说明

标记语句可以和 break 或 continue 语句一起使用。标记就是在一条语句前面加个可以引用的标识符(identifier)。

let str = '';

loop1:
for (let i = 0; i < 5; i++) {
  if (i === 1) {
    continue loop1;
  }
  str = str + i;
}

console.log(str);
// expected output: "0234"

备注: 使用标记的循环或语句块非常罕见。通常情况下,可以使用函数调用而不是(基于标记的)循环跳转。


语法说明

label :
   statement
语句名 具体说明
label 任何不属于保留关键字的 JavaScript 标识符。
statement JavaScript 语句。break 可用于任何标记语句,而 continue 可用于循环标记语句。

详细说明

可使用一个标签来唯一标记一个循环,然后使用 breakcontinue 语句来指示程序是否中断循环或继续执行。

需要注意的是,JavaScript 没有 goto 语句,标记只能和 breakcontinue 一起使用。

严格模式中,你不能使用 “let” 作为标签名称。它会抛出一个 SyntaxError(因为 let 是一个保留的标识符)。


使用案例

在标记块中使用 break


你可以在代码块中使用标记,但只有 break 语句可以使用非循环标记。

foo: {
  console.log('face');
  break foo;
  console.log('this will not be executed');
}
console.log('swap');
// this will log:
// "face"
// "swap

标记函数声明


从ECMAScript 2015开始,标准的函数声明现在对规范的 Web 兼容性附件中的非严格代码进行了标准化。

L: function F() {}

严格模式中,这会抛出 SyntaxError

'use strict';
L: function F() {}
// SyntaxError: functions cannot be labelled

无论是否处于严格模式下,生成器函数都不能被标记:

L: function* F() {}
// SyntaxError: generator functions cannot be labelled

break 语句

基础说明

break 语句中止当前循环,switch语句或label 语句,并把程序控制流转到紧接着被中止语句后面的语句。

break语句包含一个可选的标签,可允许程序摆脱一个被标记的语句。break语句需要内嵌在引用的标签中。被标记的语句可以是任何 语句;不一定是循环语句。


语法说明

break [label];
语句名 具体说明
label 可选。与语句标签相关联的标识符。如果 break 语句不在一个循环或 switch 语句中,则该项是必须的。

使用案例

下面的函数里有个 break 语句,当 i 为 3 时,会中止 while 循环,然后返回 3 * x 的值。

function testBreak(x) {
  var i = 0;

  while (i < 6) {
    if (i == 3) {
      break;
    }
    i += 1;
  }

  return i * x;
}

continue 语句

基础说明

continue 声明终止当前循环或标记循环的当前迭代中的语句执行,并在下一次迭代时继续执行循环。

let text = '';

for (let i = 0; i < 10; i++) {
  if (i === 3) {
    continue;
  }
  text = text + i;
}

console.log(text);
// expected output: "012456789"



语法说明

continue [ label ];
语句名 具体说明
label 标识标号关联的语句

详细说明


break 语句的区别在于, continue 并不会终止循环的迭代,而是:

  • while 循环中,控制流跳转回条件判断;
  • for 循环中,控制流跳转到更新语句。

continue 语句可以包含一个可选的标号以控制程序跳转到指定循环的下一次迭代,而非当前循环。此时要求 continue 语句在对应的循环内部。


使用案例


下述例子展示了一个在i 为 3时执行continue 语句的 while 循环。因此,n 的值在几次迭代后分别为 1, 3, 7 和 12 .

i = 0;
n = 0;
while (i < 5) {
   i++;
   if (i === 3) {
      continue;
   }
   n += i;
}

with 语句

基础说明

with语句 扩展一个语句的作用域链。


语法说明

with (expression) {
    statement
}
语句名 具体说明
expression 将给定的表达式添加到在评估语句时使用的作用域链上。表达式周围的括号是必需的。
statement 任何语句。要执行多个语句,请使用一个语句 ({ ... })对这些语句进行分组。

推荐写法

因为大量使用 with 语句会导致 性能下降,同时也会给调试代码造成困难,因此在开发大型应用程序时,不建议使用 with 语句~~~

严格模式下不允许使用 with 语句,否则将会视为语法错误~~


详细说明


JavaScript查找某个未使用命名空间的变量时,会通过作用域链来查找,作用域链是跟执行代码的context或者包含这个变量的函数有关。

'with'语句將某个对象添加到作用域链的顶部,如果在statement中有某个未使用命名空间的变量,跟作用域链中的某个属性同名,则这个变量将指向这个属性值。如果沒有同名的属性,则将拋出ReferenceError异常。

不推荐使用with,在 ECMAScript 5 严格模式中该标签已被禁止。推荐的替代方案是声明一个临时变量来承载你所需要的属性。


性能方面的利与弊

  • 利:with语句可以在不造成性能损失的情況下,减少变量的长度。其造成的附加计算量很少。使用'with'可以减少不必要的指针路径解析运算。需要注意的是,很多情況下,也可以不使用with语句,而是使用一个临时变量来保存指针,来达到同样的效果。
  • 弊:with语句使得程序在查找变量值时,都是先在指定的对象中查找。所以那些本来不是这个对象的属性的变量,查找起来将会很慢。如果是在对性能要求较高的场合,'with'下面的statement语句中的变量,只应该包含这个指定对象的属性。

语义不明的弊端

  • 弊端:with语句使得代码不易阅读,同时使得JavaScript编译器难以在作用域链上查找某个变量,难以决定应该在哪个对象上来取值。请看下面的例子:
function f(x, o) {
  with (o) 
    print(x);
}

f被调用时,x有可能能取到值,也可能是undefined,如果能取到, 有可能是在o上取的值,也可能是函数的第一个参数x的值(如果o中没有这个属性的话)。如果你忘记在作为第二个参数的对象o中定义x这个属性,程序并不会报错,只是取到另一个值而已。

  • **弊端:**使用with语句的代码,无法向前兼容,特別是在使用一些原生数据类型的时候。看下面的例子:
function f(foo, values) {
    with (foo) {
        console.log(values)
    }
}

如果是在ECMAScript 5环境调用f([1,2,3], obj),则with语句中变量values将指向函数的第二个参数values。但是,ECMAScript 6标准给Array.prototype添加了一个新属性values,所有数组实例将继承这个属性。所以在ECMAScript 6环境中,with语句中变量values将指向[1,2,3].values


使用案例

下面的with语句指定Math对象作为默认对象。with语句里面的变量,分別指向Math对象的PI 、 cossin函数,不用在前面添加命名空间。

var a, x, y;
var r = 10;

with (Math) {
  a = PI * r * r;
  x = r * cos(PI);
  y = r * sin(PI / 2);
}

switch 语句

基础说明

switch 语句评估一个表达式,将表达式的值与case子句匹配,并执行与该情况相关联的语句

const expr = 'Papayas';
switch (expr) {
  case 'Oranges':
    console.log('Oranges are $0.59 a pound.');
    break;
  case 'Mangoes':
  case 'Papayas':
    console.log('Mangoes and papayas are $2.79 a pound.');
    // expected output: "Mangoes and papayas are $2.79 a pound."
    break;
  default:
    console.log(`Sorry, we are out of ${expr}.`);
}



语法说明

switch (expression) {
  case value1:
    // 当 expression 的结果与 value1 匹配时,执行此处语句
    [break;]
  case value2:
    // 当 expression 的结果与 value2 匹配时,执行此处语句
    [break;]
  ...
  case valueN:
    // 当 expression 的结果与 valueN 匹配时,执行此处语句
    [break;]
  [default:
    // 如果 expression 与上面的 value 值都不匹配,执行此处语句
    [break;]]
}
语句名 具体说明
expression 一个用来与 case 子语句匹配的表达式。
case valueN  可选 用于匹配 expression 的 case 子句。如果 expression 与给定的 valueN 相匹配,则执行该 case 子句中的语句直到该 switch 语句结束或遇到一个 break 。
default   可选 一个 default 子句;如果给定,这条子句会在 expression 的值与任一 case 语句均不匹配时执行。

详细说明


一个 switch 语句首先会计算其 expression 。然后,它将从第一个 case 子句开始直到寻找到一个其表达式值与所输入的 expression 的值所相等的子句(使用 严格运算符===)并将控制权转给该子句,执行相关语句。(如果多个 case 与提供的值匹配,则选择匹配的第一个 case,即使这些 case 彼此间并不相等。)

如果没有 case 子句相匹配,程序则会寻找那个可选的 default 子句,如果找到了,将控制权交给它,执行相关语句。若没有 default 子句,程序将继续执行直到 switch 结束。按照惯例,default 子句是最后一个子句,不过也不需要这样做。

可选的 break 语句确保程序立即从相关的 case 子句中跳出 switch 并接着执行 switch 之后的语句。若 break 被省略,程序会继续执行 switch 语句中的下一条语句。


使用案例


下面的例子中,如果 expr 计算为 "Bananas",程序就会匹配值为 "Bananas" 的 case 然后执行相关语句。当遇到 break 时,程序就跳出 switch 然后执行 switch 后的语句。若 break 被省略,值为 "Cherries" 的 case 中的语句就也将被执行。

switch (expr) {
  case 'Oranges':
    console.log('Oranges are $0.59 a pound.');
    break;
  case 'Apples':
    console.log('Apples are $0.32 a pound.');
    break;
  case 'Bananas':
    console.log('Bananas are $0.48 a pound.');
    break;
  case 'Cherries':
    console.log('Cherries are $3.00 a pound.');
    break;
  case 'Mangoes':
  case 'Papayas':
    console.log('Mangoes and papayas are $2.79 a pound.');
    break;
  default:
    console.log('Sorry, we are out of ' + expr + '.');
}

console.log("Is there anything else you'd like?");

debugger 语句

基础说明

debugger 语句调用任何可用的调试功能,例如设置断点。 如果没有调试功能可用,则此语句不起作用。


语法说明

debugger;

使用案例

下面的例子演示了一个包含 debugger 语句的函数,当函数被调用时,会尝试调用一个可用的调试器进行调试。

function potentiallyBuggyCode() {
    debugger;
    // do potentially buggy stuff to examine, step through, etc.
}

今日学习总结


今日心情


今日主要是基于搜索来仔细学习 第 3 章节中的 3.5 语句,感受到操作符与语句组合的厉害之处~~~,感觉很不错,希望明天学习到更多东西~~~~

本文使用 mdnice 排版