百度前端技术学院是一个为大学生创办的免费的前端技术实践、分享、交流平台。由百度校园招聘组、百度校园品牌部、百度前端技术部以及多个百度的前端团队联合创办。学院组织了一批百度在职工程师,精心编写了数十个实践编码任务,将技术知识点系统有机地串联在各个充满趣味与挑战的任务中,同学们通过实际地编码练习来掌握知识,再辅以互相评价、学习笔记等方式,加深对于学习内容的理解。在过去的三年中,百度前端技术学院累积吸引了上万名同学参加,并且有数十名同学在学习后,顺利加入了百度,成为了百度的前端工程师。

第17到18天笔记

作者胡佳课程第十七天到第十八天,“如果”可以“重来”740次浏览52018-05-13 17:40

记录时间:2018.05.12~2018.05.13  学习总用时:10h

认真的阅读了给出的MDN上JavaScript第一步及JavaScript基础要件 相关内容,一些小练习小测试也亲自动手跟着操作了,收获还是挺多的,然后将其中一些自己不太熟悉还未完全掌握的一些知识点总结到笔记中,方便后面复习查看。

变量

可以通过使用变量的方式来验证这个变量的数值是否在执行环境中已经存在。 “一个变量存在,但是没有数值” 则使用变量会显示undefined,“一个变量并不存在” 使用其时则会报错。

Note: 如果你写一个声明和初始化变量的多行JavaScript代码的程序,你可以在初始化变量之后再实际声明它,并且它仍然可以工作。这是因为变量的声明通常在在其余的代码执行之前完成。这叫做顶置

关于变量命名的规则

你可以给你的变量赋任何你喜欢的名字,但有一些限制。 一般你应当坚持使用拉丁字符(0-9,a-z,A-Z)和下划线字符。

  • 你不应当使用规则之外的其他字符,因为它们可能引发错误或者对国际用户来说难以理解。
  • 变量名不要以下划线开头—— 以下划线开头的被某些JavaScript设计为特殊的含义,因此可能让人迷惑。
  • 变量名不要以数字开头。这种行为是不被允许的,并且将引发一个错误。
  • 一个可靠的命名约定叫做 "小写驼峰命名法",用来将多个单词组在一起,小写整个命名的第一个字母然后大写剩下单词的首字符。我们已经在文章中使用了这种命名方法。
  • 让变量名直观,它们描述了所包含的数据。不要只使用单一的字母/数字,或者长句。
  • 变量名大小写敏感——因此myage与myAge是2个不同的变量。
  • 最后也是最重要的一点—— 你应当避免使用JavaScript的保留字给变量命名。保留字,即是组成JavaScript的实际语法的单词!因此诸如 var, function, let和 for等,都不能被作为变量名使用。浏览器将把它们识别为不同的代码项,因此你将得到错误。

Note: 你能从词汇语法—关键字找到一个相当完整的保留关键字列表来避免使用关键字当作变量。

好的命名示例:

age
myAge
init

变量类型

包括数字、字符串、Boolean 、数组、对象几种类型。

typeof()函数 ——返回所传递给它的变量的数据类型。

运算符(Operators)

JavaScript运算符允许我们执行比较,做数学运算,连接字符串,以及其他类似的事情。

有一些快捷操作符可用,称为增强赋值操作符。 例如,如果您想简单地添加一个新的文本字符串到一个现有的并返回结果,您可以这样做:

name += ' says hello!';

这相当于:

name = name + ' says hello!';

当我们执行true / false比较时(例如在条件语句 - 见下面),我们使用比较运算符,例如:

运算符 名称 示例
=== 严格等运算符(它们是否确实一样?) 5 === 2 + 4
!== 严格不等运算符(它们不一样么?) 'Chris' !== 'Ch' + 'ris'
< 小于运算符 10 < 6
> 大于运算符 10 > 20

递增和递减运算符

var num1 = 4;
num1++;

执行此操作时,您会看到返回值为4,这是因为浏览器返回当前值,然后增加变量。 如果您再次返回变量值,则可以看到它已经递增:

num1;

Note: 您可以使浏览器以其他方式进行操作 - 递增/递减变量,然后返回值 - 将操作符放在变量的开头,而不是结束。 再次尝试上面的例子,但这次使用++ num1和--num2。 上面例子如果换成++ num1则返回的是5。

操作运算符

运算符 名称 作用 示例 等价于
+= 递增赋值 右边的数值加上左边的变量,然后再返回新的变量。 x = 3;x += 4; x = 3;x = x + 4;
-= 递减赋值 左边的变量减去右边的数值,然后再返回新的变量。 x = 6;x -= 3; x = 6;x = x - 3;
*= 乘法赋值 左边的变量乘以右边的数值,然后再返回新的变量。 x = 2;x *= 3; x = 2;x = x * 3;
/= 除法赋值 左边的变量除以右边的数值,然后再返回新的变量。 x = 10;x /= 5; x = 10;x = x / 5;

请注意,您可以愉快地使用每个表达式右侧的其他变量,例如:

var x = 3; // x 包含值 3
var y = 4; // y 包含值 4
x *= y; // x 现在包含值 12

比较运算符

运算符 名称 作用 示例
=== 严格等于 测试左右值是否相同 5 === 2 + 4
!== 严格不等于 测试左右值是否相同 5 !== 2 + 3
< 小于 测试左值是否小于右值。 10 < 6
> 大于 测试左值是否大于右值 10 > 20
<= 小于或等于 测试左值是否小于或等于右值。 3 <= 2
>= 大于或等于 测试左值是否大于或等于正确值。 5 >= 4

Note: 您可能会看到有些人在他们的代码中使用==!=来平等和不相等,这些都是JavaScript中的有效运算符,但它们与===/!==不同,前者测试值是否相同, 但是数据类型可能不同,而后者的严格版本测试值和数据类型是否相同。 严格的版本往往导致更少的错误,所以我们建议您使用这些严格的版本。

如果您尝试在控制台中输入这些值,您将看到它们都返回true / false值 - 我们在上一篇文章中提到的那些布尔值。 这些是非常有用的,因为它们允许我们在我们的代码中做出决定 - 每次我们想要选择某种类型时,都会使用这些代码,例如:

  • 根据功能是打开还是关闭,在按钮上显示正确的文本标签。
  • 如果游戏结束,则显示游戏消息,或者如果游戏已经获胜,则显示胜利消息。
  • 显示正确的季节性问候,取决于假期季节。
  • 根据选择的缩放级别缩小或缩小地图。

其他知识点

  • querySelector()返回文档中与指定选择器或选择器组匹配的第一个Element。 如果找不到匹配项,则返回nullquerySelector()方法仅仅返回匹配指定选择器的第一个元素。如果你需要返回所有的元素,请使用 querySelectorAll()方法替代。

  • HTML<input>标签的 disabled 属性。disabled 属性规定应该禁用 input 元素。

    被禁用的 input 元素既不可用,也不可点击。可以设置 disabled 属性,直到满足某些其他的条件为止(比如选择了一个复选框等等)。然后,就需要通过 JavaScript 来删除 disabled 值,将 input 元素的值切换为可用。

    注释:disabled 属性无法与<input type="hidden"> 一起使用。

  • :disabled CSS 伪类表示任何被禁用的元素。如果一个元素不能被激活(如选择、点击或接受文本输入)或获取焦点,则该元素处于被禁用状态。元素还有一个启用状态(enabled state),在启用状态下,元素可以被激活或获取焦点。

  • parentNode 属性以 Node 对象的形式返回指定节点的父节点。如果指定节点没有父节点,则返回 null。

  • removeChild() 方法指定元素的某个指定的子节点。以 Node 对象返回被删除的节点,如果节点不存在则返回 null。

  • appendChild() 方法向节点添加最后一个子节点。提示:如果您需要创建包含文本的新段落,请记得添加到段落的文本的文本节点,然后向文档添加该段落。您也可以使用 appendChild() 方法从一个元素向另一个元素中移动元素。

  • createElement() 方法通过指定名称创建一个元素

  • textContent 属性设置或者返回指定节点的文本内容。

    如果你设置了 textContent 属性, 任何的子节点会被移除及被指定的字符串的文本节点替换。

    提示: 某些时候 textContent 属性可以被 nodeValue 属性取代,但是请记住这个属性同样可以返回所有子节点的文本。

  • HTMLElement.focus()方法可以设置指定元素获取焦点。`

  • lastResult.style.backgroundColor = 'white';

  • Math.floor() 返回小于或等于一个给定数字的最大整数。

  • Math.round() 方法可把一个数字舍入为最接近的整数。

  • Math.random() 函数返回一个浮点, 伪随机数在范围[0,1),也就是说,从0(包括0)往上,但是不包括1(排除1),然后您可以缩放到所需的范围。实现将初始种子选择到随机数生成算法;它不能被用户选择或重置.

  • console.log(i)  向 Web 控制台输出一条消息。
    
  • for循环应包含:起始值 、退出条件 和增量 。如for (var i = 1 ; i < 21 ; i++) { console.log(i) }

  • 内部函数中的代码运行在一个相对外部函数独立的范围中。在这种情况下,代码不会被运行也没有抛出异常,直到checkGuess()函数被第86行执行。

  • Window.prompt() 函数, 它要求用户通过一个弹出对话框回答一个问题然后将他们输入的文本存储在一个给定的变量中

  • Window.alert() 函数来显示另一个弹出窗口,其中包含一个字符串。

  • Number 对象将把传递给它的任何东西转换成一个数字。

    试一试:

    var myString = '123';
    var myNum = Number(myString);
    typeof myNum;
    
  • 每个数字都可以用一个名为 toString() 的方法,它将把它转换成等价的字符串。

    试试这个:

    var myNum = 123;
    var myString = myNum.toString();
    typeof myString;
    
  • 测试布尔值(true / false),和一个通用模式,你会频繁遇到它,任何是 false, undefined, null, 0, NaN 的值,或一个空字符串('')在作为条件语句进行测试时实际返回true,因此您可以简单地使用变量名称来测试它是否为真,甚至是否存在(即它不是未定义的)。

  • 逻辑运算符:AND,OR和NOT。&& — 逻辑与; || — 逻辑或; 逻辑运算符的最后一个, NOT, 用 ! 运算符表示, 可以用于对一个表达式取否.

    在条件语句中运用OR逻辑运算符最常见的错误是尝试声明变量后,仅检查该变量一次的情况下赋予很多个都会返回true的值,不同的值之间用 || (OR)运算符分隔。比如:

    if (x === 5 || 7 || 10 || 20) {
      // run my code
    }
    

    在这个例子里 if(...) 里的条件总为真,因为 7 (或者其它非零的数) 的值总是为真. 这个条件实际意思是 "如果x等于5, 或者7为真 — 它总是成立的". 这不是我们想要的逻辑,为了 让它正常工作你必须指定每个OR表达式两边都是完整的检查:

    if (x === 5 || x === 7 || x === 10 ||x === 20) {
      // run my code
    }
    

在字符串中查找子字符串并提取它

  1. 有时候你会发现一个较大的字符串是否存在于一个较大的字符串中(我们通常会说一个字符串中存在一个子字符串)。 这可以使用indexOf()方法来完成,该方法需要一个parameter 你想要的子字符串 搜索。 尝试这个:

browserType.indexOf('zilla');

这给了我们2的结果,因为子串“zilla”从“mozilla”内的位置2(0,1,2 - 所以3个字符)开始。 这样的代码可以用来过滤字符串。 例如,假设我们有一个Web地址列表,只想打印出包含“mozilla”的那些。

这可以以另一种方式完成,这可能更有效。 尝试以下:

browserType.indexOf('vanilla');

他应该给你一个结果-1 - 当在主字符串中找不到子字符串(在本例中为“vanilla”)时返回。

您可以使用它来查找不包含子串“mozilla”的所有字符串实例,或者如果使用否定运算符,请执行以下操作。 你可以这样做:

if(browserType.indexOf('mozilla') !== -1) {

  // do stuff with the string
}
  1. 当你知道字符串中的子字符串开始的位置,以及想要结束的字符时,slice()可以用来提取 它。 尝试以下:

browserType.slice(0,3);
这时返回"moz"——第一个参数是开始提取的字符位置,第二个参数是提取的最后一个字符后一位置。所以提取从第一个位置开始,直到但不包括最后一个位置。(此例中)你也可以说第二个参数等于被返回的字符串的长度。

  1. 此外,如果您知道要在某个字符之后提取字符串中的所有剩余字符,则不必包含第二个参数,而只需要包含要从中提取的字符位置 字符串中的其余字符。 尝试以下:

browserType.slice(2);
这返回“zilla” - 这是因为2的字符位置是字母z,并且因为没有包含第二个参数,所以返回的子字符串是字符串中的所有剩余字符。
Note: slice()的第二个参数是可选的 : 如果你没有传入参数,这个分片结束位置会在原始字符串的末尾。这个方法也有其他的选项。

转换大小写

字符串方法toLowerCase()toUpperCase()字符串并将所有字符分别转换为小写或大写。 例如,如果要在将数据存储在数据库中之前对所有用户输入的数据进行规范化,这可能非常有用。

让我们尝试输入以下几行来看看会发生什么:

var radData = 'My NaMe Is MuD';
radData.toLowerCase();
radData.toUpperCase();

替换字符串的某部分

您可以使用replace()方法将字符串中的一个子字符串替换为另一个子字符串。在基础的层面上, 这个工作非常简单。

它需要两个参数 - 要被替换下的字符串和要被替换上的字符串。 尝试这个例子:

browserType.replace('moz','van');

注意,想要真正更新browserType变量的值反应在真实程序里,您需要设置变量的值等于刚才的操作结果;它不会自动更新子串的值。所以事实上你需要这样写:browserType = browserType.replace('moz','van');

错误类型

一般来说,当您的代码出错的时候,您会遇到两种主要的错误类型:

  • 语法错误:这是您的代码的拼写错误,实际上导致程序不能运行在所有或停止通过工作的一部分,这样您通常会用一些提供的错误消息找到修复的方法,只要您熟悉正确的工具,知道错误消息的意思!

  • 逻辑错误:这些错误,其中语法实际上是正确的,但代码是不是你想要的,这意味着项目成功运行,但会产生不正确的结果。这些通常比语法错误更难以修复,因为通常没有错误指向错误源。

    其它常见错误

    1. 语法错误: 在声明后缺少" ; "这个错误通常意味着你漏掉了代码行后面的分号。

    2. 不管你输入的猜测是什么程序都说“你赢了!”。这可能是混淆赋值和严格相等运算符的又一症状。

    3. 语法错误: 在参数后缺少" ) "

    4. 语法错误: 在属性id后缺少" : "

    5. 语法错误: 在函数体后缺少" } "

    6. SyntaxError: expected expression, got 'string' or SyntaxError: unterminated string literal

      这个错误通常意味着你丢失了字符串值的开引号或关引号。 在上面的第一个错误中,string 将被替换为浏览器发现的意外字符,而不是字符串开头的引号。第二个错误意味着字符串没有用引号结尾。

数组

一些有用的数组方法

split() 方法

用于把一个字符串分割成字符串数组。

语法

stringObject.split(separator,howmany)
参数 描述
separator 必需。字符串或正则表达式,从该参数指定的地方分割 stringObject。
howmany 可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。

返回值

一个字符串数组。该数组是通过在 separator 指定的边界处将字符串 stringObject 分割成子串创建的。返回的数组中的字串不包括 separator 自身。

但是,如果 separator 是包含子表达式的正则表达式,那么返回的数组中包括与这些子表达式匹配的字串(但不包括与整个正则表达式匹配的文本)。

提示和注释

注释:如果把空字符串 ("") 用作 separator,那么 stringObject 中的每个字符之间都会被分割。

注释:String.split() 执行的操作与 Array.join 执行的操作是相反的。

join() 方法

用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。

语法

arrayObject.join(separator)
参数 描述
separator 可选。指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符。

返回值

返回一个字符串。该字符串是通过把 arrayObject 的每个元素转换为字符串,然后把这些字符串连接起来,在两个元素之间插入 separator 字符串而生成的。

toString() 方法

可把一个逻辑值转换为字符串,并返回结果。

toString()函数用于将当前对象以字符串的形式返回

该方法属于Object对象,由于所有的对象都"继承"了Object的对象实例,因此几乎所有的实例对象都可以使用该方法。

所有主流浏览器均支持该函数。

语法

object.toString( )

返回值

toString()函数的返回值为String类型。返回当前对象的字符串形式。

JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。

类型 行为描述
Array 将 Array 的每个元素转换为字符串,并将它们依次连接起来,两个元素之间用英文逗号作为分隔符进行拼接。
Boolean 如果布尔值是true,则返回"true"。否则返回"false"。
Date 返回日期的文本表示。
Error 返回一个包含相关错误信息的字符串。
Function 返回如下格式的字符串,其中 functionname 是一个函数的名称,此函数的 toString 方法被调用: "function functionname() { [native code] }"
Number 返回数值的字符串表示。还可返回以指定进制表示的字符串,请参考Number.toString()
String 返回 String 对象的值。
Object(默认) 返回"[object ObjectName]",其中 ObjectName 是对象类型的名称。

示例&说明

以下示例运行的宿主环境为Windows 7 简体中文旗舰版 64位,地点为中国大陆。不同的区域设置和语言设置,执行的输出结果可能不同。

//数组
var array = ["CodePlayer", true, 12, -5];
document.writeln( array.toString() ); // CodePlayer,true,12,-5

// 日期
var date = new Date(2013, 7, 18, 23, 11, 59, 230);
document.writeln( date.toString() ); // Sun Aug 18 2013 23:11:59 GMT+0800 (中国标准时间)

// 日期2
var date2 = new Date(1099, 7, 18, 23, 11, 59, 230);
document.writeln( date2.toString() ); // Fri Aug 18 1099 23:11:59 GMT+0800 (中国标准时间)

// 数字
var num =  15.26540;
document.writeln( num.toString() ); // 15.2654

// 布尔
var bool = true;
document.writeln( bool.toString() ); // true

// Object
var obj = {name: "张三", age: 18};
document.writeln( obj.toString() ); // [object Object]

// HTML DOM 节点
var eles = document.getElementsByTagName("body");
document.writeln( eles.toString() ); // [object NodeList]
document.writeln( eles[0].toString() ); // [object HTMLBodyElement]

添加和删除数组项

push() 方法

定义和用法

push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。

语法

arrayObject.push(newelement1,newelement2,....,newelementX)
参数 描述
newelement1 必需。要添加到数组的第一个元素。
newelement2 可选。要添加到数组的第二个元素。
newelementX 可选。可添加多个元素。

返回值

把指定的值添加到数组后的新长度。

说明

push() 方法可把它的参数顺序添加到 arrayObject 的尾部。它直接修改 arrayObject,而不是创建一个新的数组。push() 方法和 pop() 方法使用数组提供的先进后出栈的功能。

提示和注释

注释:该方法会改变数组的长度。

提示:要想数组的开头添加一个或多个元素,请使用 unshift() 方法。

pop() 方法

定义和用法

pop() 方法用于删除并返回数组的最后一个元素。

语法

arrayObject.pop()

返回值

arrayObject 的最后一个元素。

说明

pop() 方法将删除 arrayObject 的最后一个元素,把数组长度减 1,并且返回它删除的元素的值。如果数组已经为空,则 pop() 不改变数组,并返回 undefined 值。

unshift() 方法

定义和用法

unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。

语法

arrayObject.unshift(newelement1,newelement2,....,newelementX)
参数 描述
newelement1 必需。向数组添加的第一个元素。
newelement2 可选。向数组添加的第二个元素。
newelementX 可选。可添加若干个元素。

返回值

arrayObject 的新长度。

说明

unshift() 方法将把它的参数插入 arrayObject 的头部,并将已经存在的元素顺次地移到较高的下标处,以便留出空间。该方法的第一个参数将成为数组的新元素 0,如果还有第二个参数,它将成为新的元素 1,以此类推。

请注意,unshift() 方法不创建新的创建,而是直接修改原有的数组。

提示和注释

注释:该方法会改变数组的长度。

注释:unshift() 方法无法在 Internet Explorer 中正确地工作!

提示:要把一个或多个元素添加到数组的尾部,请使用 push() 方法。

shift() 方法

定义和用法

shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。

语法

arrayObject.shift()

返回值

数组原来的第一个元素的值。

提示和注释

注释:该方法会改变数组的长度。

提示:要删除并返回数组的最后一个元素,请使用 pop() 方法。

randomValueFromArray()

可用于随机生成一个数组中的元素。

条件语句

switch语句

if...else 语句能够很好地实现条件代码,但是它们不是没有缺点。 它们主要适用于您只有几个选择的情况,每个都需要相当数量的代码来运行,和/或 的条件很复杂的情况(例如多个逻辑运算符)。 对于只想将变量设置一系列为特定值的选项或根据条件打印特定语句的情况,语法可能会很麻烦,特别是如果您有大量选择。

switch 语句在这里是您的朋友 - 他们以单个表达式/值作为输入,然后查看多个选项,直到找到与该值相匹配的选项,执行与之相关的代码。 这里有一些伪代码,可以给你一点灵感:

switch (expression) {
  case choice1:
    run this code
    break;

  case choice2:
    run this code instead
    break;

  // include as many cases as you like

  default:
    actually, just run this code
}

这里我们得到:

  1. 关键字 switch, 后跟一组括号.
  2. 括号内的表达式或值.
  3. 关键字 case, 后跟一个选项的表达式/值,后面跟一个冒号.
  4. 如果选择与表达式匹配,则运行一些代码.
  5. 一个 break 语句, 分号结尾. 如果先前的选择与表达式/值匹配,则浏览器在此停止执行代码块,并执行switch语句之后的代码.
  6. 你可以添加任意的 case 选项(选项3-5).
  7. 关键字 default, 后面跟随和 case 完全相同的代码模式 (选项 3–5), except that default 之后不需要再有选项, 并且您不需要 break 语句, 因为之后没有任何运行代码. 如果之前没有选项匹配,则运行default选项.

Note: default 部分不是必须的 - 如果表达式不可能存在未知值,则可以安全地省略它。 如果有机会,您需要包括它来处理未知的情况。

三元运算符

在我们举一些例子之前,我们要介绍一下最后一句语法。三元或条件运算符是一个语法的小点,用于测试一个条件,并返回一个值/表达,如果它是true,另一个是false-这种情况下是有用的,并且可以占用比if...else块较少的代码块。如果你只有两个通过true/ false条件选择。伪代码看起来像这样:

( condition ) ? run this code : run this code instead

所以我们来看一个简单的例子:

var greeting = ( isBirthday ) ? 'Happy birthday Mrs. Smith — we hope you have a great day!' : 'Good morning Mrs. Smith.';

在这里我们有一个变量叫做isBirthday- 如果它是true,我们给客人一个生日快乐的消息; 如果不是,我们给她标准的每日问候。

循环语句

for循环

有以下语法:

for (initializer; exit-condition; final-expression) {
  // code to run
}

我们有:

  1. 关键字for,后跟一些括号。
  2. 在括号内,我们有三个项目,以分号分隔:
    1. 一个初始化器 - 这通常是一个设置为一个数字的变量,它被递增来计算循环运行的次数。它也有时被称为计数变量
    2. 一个退出条件 -如前面提到的,这个定义循环何时停止循环。这通常是一个表现为比较运算符的表达式,用于查看退出条件是否已满足的测试。
    3. 一个最终条件 -这总是被判断(或运行),每个循环已经通过一个完整的迭代消失时间。它通常用于增加(或在某些情况下递减)计数器变量,使其更接近退出条件值。
  3. 一些包含代码块的花括号 - 每次循环迭代时都会运行这个代码。

使用break退出循环

使用continue跳过迭代

continue语句以类似的方式工作,而不是完全跳出循环,而是跳过循环进入到下一个循环。

while语句和do ... while语句

while循环。 这个循环的语法如下所示:

initializer
while (exit-condition) {
  // code to run

  final-expression
}

除了在循环之前设置初始化器变量,并且在运行代码之后,循环中包含final-expression,而不是这两个项目被包含在括号中,这与以前的for循环非常类似。 退出条件包含在括号内,前面是while关键字而不是for。

同样的三个项目仍然存在,它们仍然以与for循环中相同的顺序定义 - 这是有道理的,因为您必须先定义一个初始化器,然后才能检查它是否已到达退出条件; 在循环中的代码运行(迭代已经完成)之后,运行最后的条件,这只有在尚未达到退出条件时才会发生。

do...while循环非常类似但在while后提供了终止条件:

initializer
do {
  // code to run

  final-expression
} while (exit-condition)

在这种情况下,在循环开始之前,初始化程序先重新开始。 do关键字直接在包含要运行的代码的花括号和终止条件之前。

这里的区别在于退出条件是一切都包含在括号中,而后面是一个while关键字。 在do ... while循环中,花括号中的代码总是在检查之前运行一次,以查看是否应该再次执行(在while和for中,检查首先出现,因此代码可能永远不会执行)。

函数

严格说来,内置浏览器函数并不是函数——它们是方法。 函数和方法在很大程度上是可互换的,至少在我们的学习阶段是这样的。

二者区别在于方法是在对象内定义的函数。内置浏览器功能(方法)和变量(称为属性)存储在结构化对象内,以使代码更加高效,易于处理。

匿名函数

您可以创建一个没有名称的函数:

function() {
  alert('hello');
}

这个函数叫做匿名函数 — 它没有函数名! I它也不会自己做任何事情。 你通常使用匿名函数以及事件处理程序, 例如,如果单击相关按钮,以下操作将在函数内运行代码:

var myButton = document.querySelector('button');

myButton.onclick = function() {
  alert('hello');
}

上述示例将要求`` 在页面上提供可用于选择并单击的元素。您在整个课程中已经看到过这种结构了几次,您将在下一篇文章中了解更多信息并在其中使用。

你还可以将匿名函数分配为变量的值,例如:

var myGreeting = function() {
  alert('hello');
}

现在可以使用以下方式调用此函数:

myGreeting();

有效地给变量一个名字;还可以将该函数分配为多个变量的值,例如:

var anotherGreeting = function() {
  alert('hello');
}

现在可以使用以下任一方法调用此函数

myGreeting();
anotherGreeting();

但这只会令人费解,所以不要这样做!创建功能时,最好只要坚持下列形式:

function myGreeting() {
  alert('hello');
}

您将主要使用匿名函数来运行负载的代码以响应事件触发(如点击按钮) - 使用事件处理程序。再次,这看起来像这样:

myButton.onclick = function() {
  alert('hello');
  // I can put as much code
  // inside here as I want
}

匿名函数也称为函数表达式。函数表达式与函数声明有一些区别。函数声明会进行声明提升(declaration hoisting),而函数表达式不会。

函数作用域和冲突

我们来谈一谈 scope即作用域 — 处理函数时一个非常重要的概念。当你创建一个函数时,函数内定义的变量和其他东西都在它们自己的单独的范围内, 意味着它们被锁在自己独立的隔间中, 不能从函数外代码其它函数内访问。

所有函数的最外层被称为全局作用域。 在全局作用域内定义的值可以在任意地方访问。

JavaScript由于各种原因而建立,但主要是由于安全性和组织性。有时您不希望变量可以在代码中的任何地方访问 - 您从其他地方调用的外部脚本可能会开始搞乱您的代码并导致问题,因为它们恰好与代码的其他部分使用了相同的变量名称,造成冲突。这可能是恶意的,或者是偶然的。

例如,假设您有一个HTML文件,它调用两个外部JavaScript文件,并且它们都有一个使用相同名称定义的变量和函数:

<!-- Excerpt from my HTML -->
<script src="first.js"></script>
<script src="second.js"></script>
<script>
  greeting();
</script>
// first.js
var name = 'Chris';
function greeting() {
  alert('Hello ' + name + ': welcome to our company.');
}
// second.js
var name = 'Zaptec';
function greeting() {
  alert('Our company is called ' + name + '.');
}

这两个函数都使用 greeting() 形式调用,但是你只能访问到 second.js文件的greeting()函数。second.js 在源代码中后应用到HTML中,所以它的变量和函数覆盖了 first.js 中的。

函数内部的函数

请记住,您可以从任何地方调用函数,甚至可以在另一个函数中调用函数。这通常被用作保持代码整理的方式 - 如果您有一个复杂的函数,如果将其分解成几个子函数,它更容易理解:

function myBigFunction() {
  var myValue;

  subFunction1();
  subFunction2();
  subFunction3();
}

function subFunction1() {
  console.log(myValue);
}

function subFunction2() {
  console.log(myValue);
}

function subFunction3() {
  console.log(myValue);
}

只需确保在函数内使用的值正确的范围. 上面的例子会抛出一个错误ReferenceError:MyValue没有被定义,因为myValue变量与函数调用的范围相同, 函数定义内没有定义 - 调用函数时运行的实际代码。为了使这个工作,你必须将值作为参数传递给函数,如下所示:

function myBigFunction() {
  var myValue = 1;

  subFunction1(myValue);
  subFunction2(myValue);
  subFunction3(myValue);
}

function subFunction1(value) {
  console.log(value);
}

function subFunction2(value) {
  console.log(value);
}

function subFunction3(value) {
  console.log(value);
}

调用函数

  1. JavaScript中的函数不管是先定义后调用还是先调用后定义都行。

  2. "按钮弹出带叉号的消息盒子"例子内容。

    btn.onclick = displayMessage;
    

    你会想“怎么函数名后面没有括号呢?”. 这是因为我们不想直接调用这个函数 — 而是只有当按钮被点击的时候才调用这个函数。 试试把代码改成这样:

    btn.onclick = displayMessage();
    

    保存刷新, 你会发现按钮都还没点击提示框就出来了! 在函数名后面的这个括号叫做“函数调用运算符”。就事论事,你只有在想直接调用函数的地方才这么写。 同样要重视的是, 匿名函数里面的代码也不是直接运行的, 只要代码在函数体内。

事件

事件是您在编程时系统内的发生的动作或者发生的事情,系统通过它来告诉您在您愿意的情况下您可以以某种方式对它做出回应。 例如点击按钮,加载页面或播放视频,我们可以调用代码来响应。

在Web中, 事件在浏览器窗口中被触发并且通常被绑定到窗口内部的特定部分 — 可能是一个元素、一系列元素、被加载到这个窗口的 HTML 代码或者是整个浏览器窗口。举几个可能发生的不同事件:

  • 用户在某个元素上点击鼠标或悬停光标。
  • 用户在键盘中按下某个按键。
  • 用户调整浏览器的大小或者关闭浏览器窗口。
  • 一个网页停止加载。
  • 提交表单。
  • 播放、暂停、关闭视频。
  • 发生错误。

每个可用的事件都会有一个事件处理器,也就是事件触发时会运行的代码块。当我们定义了一个用来回应事件被激发的代码块的时候,我们说我们注册了一个事件处理器。注意事件处理器有时候被叫做事件监听器——从我们的用意来看这两个名字是相同的,尽管严格地说来这块代码既监听也处理事件。监听器留意事件是否发生,然后处理器就是对事件发生做出的回应。

侦听事件发生的构造方法称为事件监听器,响应事件触发而运行的代码块被称为事件处理器。

注意,当函数作为事件监听方法的参数时,函数名后不应带括号。

注: 网络事件不是 JavaScript 语言的核心——它们被定义成内置于浏览器的JavaScript APIs。

这不仅应用在网页上

值得注意的是并不是只有 JavaScript 使用事件——大多的编程语言都有这种机制,并且它们的工作方式不同于 JavaScript。实际上,JavaScript 网页上的事件机制不同于在其他环境中的事件机制。

比如, Node.js 是一种非常流行的允许开发者使用 JavaScript 来建造网络和服务器端应用的运行环境。Node.js event model 依赖定期监听事件的监听器和定期处理事件的处理器——虽然听起来好像差不多,但是实现两者的代码是非常不同的,Node.js 使用像 on ( ) 这样的函数来注册一个事件监听器,使用 once ( ) 这样函数来注册一个在运行一次之后注销的监听器。 HTTP connect event docs 提供了很多例子。

另外一个例子:您可以使用 JavaScript 来开发跨浏览器的插件(使用 WebExtensions 开发技术。事件模型和网站的事件模型是相似的,仅有一点点不同——事件监听属性是大驼峰的(如onMessage而不是onmessage),还需要与 addListener 函数结合, 参见 runtime.onMessage page 上的一个例子。

使用网页事件的方式

您可以通过多种不同的方法将事件侦听器代码添加到网页,以便在关联的事件被触发时运行它。

事件处理器属性

这些是我们的课程中最常见到的代码 - 存在于事件处理程序过程的属性中。回到上面的例子:

var btn = document.querySelector('button');

btn.onclick = function() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

这个 onclick 是被用在这个情景下的事件处理器的属性,它就像 button 其他的属性(如 btn.textContent, or btn.style), 但是有一个特别的地方——当您将一些代码赋值给它的时候,只要事件触发代码就会运行。

您也可以将一个有名字的函数赋值给事件处理参数(正如我们在 Build your own function 中看到的),下面的代码也是这样工作的:

var btn = document.querySelector('button');

function bgChange() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

btn.onclick = bgChange;

有很多事件处理参数可供选择,我们来做一个实验。

首先将 random-color-eventhandlerproperty.html 复制到本地,然后用浏览器打开。将btn.onclick 依次换成其他值,在浏览器中观察效果。a

  • btn.onfocusbtn.onblur — 颜色将于按钮被置于焦点或解除焦点时改变(尝试使用Tab移动至按钮上,然后再移开)。这些通常用于显示有关如何在置于焦点时填写表单字段的信息,或者如果表单字段刚刚填入不正确的值,则显示错误消息。
  • btn.ondblclick — 颜色将仅于按钮被双击时改变。
  • window.onkeypress, window.onkeydown, window.onkeyup — 当按钮被按下时颜色会发生改变. keypress 指的是通俗意义上的按下按钮 (按下并松开), 而 keydownkeyup 指的是按键动作的一部分,分别指按下和松开. 注意如果你将事件处理器添加到按钮本身,它将不会工作 — 我们只能将它添加到代表整个浏览器窗口的 window对象中。
  • btn.onmouseoverbtn.onmouseout — 颜色将会在鼠标移入按钮上方时发生改变, 或者当它从按钮移出时.

一些事件非常通用,几乎在任何地方都可以用(比如 onclick 几乎可以用在几乎每一个元素上),然而另一些元素就只能在特定场景下使用,比如我们只能在 video 元素上使用 onplay

行内事件处理器 - 请勿使用

你也许在你的代码中看到过这么一种写法:

<button onclick="bgChange()">Press me</button>
function bgChange() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

在Web上注册事件处理程序的最早方法是类似于上面所示的事件处理程序HTML属性(也称为内联事件处理程序)—属性值实际上是当事件发生时要运行的JavaScript代码。上面的例子中调用一个在``元素在同一个页面上,但也可以直接在属性内插入JavaScript,例如:

<button onclick="alert('Hello, this is my old-fashioned event handler!');">Press me</button>

你会发现HTML属性等价于对许多事件处理程序的属性;但是,你不应该使用这些—他们被认为是不好的做法。使用一个事件处理属性似乎看起来很简单,如果你只是在做一些非常快的事情,但很快就变得难以管理和效率低下。

一开始,您不应该混用 HTML 和 JavaScript,因为这样文档很难解析——最好的办法是只在一块地方写 JavaScript 代码。

即使在单一文件中,内置事件处理器也不是一个好主意。一个按钮看起来还好,但是如果有一百个按钮呢?您得在文件中加上100个属性。这很快就会成为维护人员的噩梦。使用 Java Script,您可以给网页中的 button 都加上事件处理器。就像下面这样:

var buttons = document.querySelectorAll('button');

for (var i = 0; i < buttons.length; i++) {
  buttons[i].onclick = bgChange;
}

注释: 将您的编程逻辑与内容分离也会使您的站点对搜索引擎更加友好。

addEventListener()和removeEventListener()

新的事件触发机制被定义在 Document Object Model (DOM) Level 2 Events Specification, 这个细则给浏览器提供了一个函数 — addEventListener()。这个函数和事件处理属性是类似的,但是语法略有不同。

在addEventListener() 函数中, 我们具体化了两个参数——我们想要将处理器应用上去的事件名称,和包含我们用来回应事件的函数的代码。注意将这些代码全部放到一个匿名函数中是可行的:

btn.addEventListener('click', function() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
});

这个机制带来了一些相较于旧方式的优点。有一个相对应的方法,removeEventListener(),这个方法移除事件监听器。例如,下面的代码将会移除上个代码块中的事件监听器:

btn.removeEventListener('click', bgChange);

这在简单个的、小型的项目中可能不是很有用,但是在大型的、复杂的项目中就非常有用了,可以非常高效地清除不用的事件处理器,另外在其他的一些场景中也非常有效——比如您需要在不同环境下运行不同的事件处理器,您只需要恰当地删除或者添加事件处理器即可。

您也可以给同一个监听器注册多个处理器,下面这种方式不能实现这一点:

myElement.onclick = functionA;
myElement.onclick = functionB;

第二行会覆盖第一行,但是下面这种方式就会正常工作了:

myElement.addEventListener('click', functionA);
myElement.addEventListener('click', functionB);

当元素被点击时两个函数都会工作。

我该使用哪种机制?

在三种机制中,您绝对不应该使用HTML事件处理程序属性 - 这些属性已经过时了,而且也是不好的做法,如上所述.

另外两种是相对可互换的,至少对于简单的用途:

  • 事件处理程序属性功能和选项会更少,但是具有更好的跨浏览器兼容性(在Internet Explorer 8的支持下),您应该从这些开始学起。
  • DOM Level 2 Events (addEventListener(), etc.) 更强大,但也可以变得更加复杂,并且支持不足(只支持到Internet Explorer 9)。 但是您也应该尝试这个方法,并尽可能地使用它们。

第三种机制(DOM Level 2 Events (addEventListener(), etc.))的主要优点是,如果需要的话,可以使用removeEventListener()删除事件处理程序代码,而且如果有需要,您可以向同一类型的元素添加多个监听器。例如,您可以在一个元素上多次调用

1条评论