Awe JavaScript [1] 基本概念

前言

本文是 Awesome JavaScript 系列文章的第一篇,本系列文章主要为 JavaScript 的一些常见知识点,是我在 JavaScript 学习过程中的一些笔记。

JavaScript 简介

JavaScript 诞生于 1995 年,和博主同年哈哈。当时,它的主要目的是处理以前由服务器端语言(如 Perl)负责的一些输入验证操作。JavaScript 从一个简单的输入验证器发展为一门强大的编程语言,完全出乎人们的预料。应该说,它既是一门非常简单的语言,又是一门非常复杂的语言。

1997 年,以 JavaScript 1.1 为蓝本的建议被提交给了欧洲计算机制造商协会(ECMA,European Computer Manufacturers Association)。不久后,该协会定义了一种名为 ECMAScript 的新脚本语言的标准,即 ECMA-262。

1998 年,ISO/IEC(International Organization for Standardization and International Electrotechnical Commission,国标标准化组织和国际电工委员会)也采用了 ECMAScript 作为标准(即 ISO/IEC-16262)。自此以后,浏览器开发商就开始致力于将 ECMAScript 作为各自 JavaScript 实现的基础,也在不同程度上取得了成功。

到现在呢,我们可以说 JavaScript 是一门专为与网页交互而设计的脚本语言,他其实由以下三部分组成:

  • ECMAScript,由 ECMA-262 定义,提供核心语言功能。
  • 文档对象模型(DOM,Document Object Model),针对 XML 但经过扩展用于 HTML 的应用程序编程接口(API,Application Programming Interface)。提供访问和操作网页内容的方法和接口。
  • 浏览器对象模型(BOM,Browser Object Model),提供与浏览器交互的方法和接口。


在 HTML 中使用 JavaScript

JavaScript 放置位置

传统的做法是将所有的 <script> 元素都放到页面的 <head> 元素中。这样做的目的是将所有的外部文件(包括 CSS 文件和 JavaScript 文件)的引用都放在相同的地方。但是这样就意味着必须等到全部 JavaScript 代码都被下载、解析和执行完成后,才能开始呈现页面的内容(浏览器在遇到 <body> 标签时才开始呈现内容)。这种情况下的用户体验就非常的不好。

其实我们可以将所有的 <script> 元素都放到页面的 <body> 元素中页面内容的后面,即 </body> 前。这样在解析包含 JavaScript 代码之前,页面的内容将完全呈现在浏览器中。而用户也会因为浏览器窗口显示空白页面的时间缩短而感到打开页面的速度加快了。

noscript元素

早期浏览器都面临一个特殊的问题,即当浏览器不支持 JavaScript 时如何让页面平稳退化。对这个问题的最终解决方案就是创造一个 <noscript> 元素,用以在不支持 JavaScript 的浏览器中显示替代的内容。这个元素可以包含能够出现在文档 <body> 中的任何 HTML 元素(<script> 除外)。包含在 <noscript> 元素中的内容只有在浏览器不支持脚本或浏览器支持脚本但是脚本被禁用的情况下才会显示出来。

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head>
<title>Example HTML Page</title>
<script type="text/javascript" defer="defer" src="example1.js"></script>
<script type="text/javascript" defer="defer" src="example1.js"></script>
<head>
<body>
<noscript>
<p>本页面需要在浏览器支持(启用)JavaScript。</p>
</noscript>
</body>
</html>


基本概念

标识符

所谓标识符就是指变量、函数、属性的名字,或者函数的参数。标识符可以是按照下列格式规则组合起来的一或多个字符:

  • 第一个字符必须是一个字母、下划线 _ 或一个美元符号 $
  • 其他字符可以是字母、下划线、美元符号或数字;
  • 标识符中的字母可以包含扩展的 ASCIIUnicode 字母字符,但不推荐这样做。

注释

注释推荐像下面这样写:

1
2
3
4
5
6
// 单行注释

/*
* 这是一个多行
* (块级)注释
*/

严格模式

ECMAScript 5 引入了严格模式的概念,严格模式是为 JavaScript 定义了一种不同的解析与操作模型。严格模式启用方法如下:

1
2
3
4
function doSomething() {
"use strict";
//函数体
}

"use strict"; 这行代码其实是一个编译指示(pragma),用于高速支持的 JavaScript 引擎切换到严格模式。

变量

用 var 操作符定义的变量将成为定义该变量的作用域中的局部变量。也就是说,如果在函数中使用 var 定义一个变量,那么这个变量在函数退出后就会被销毁。

1
2
3
4
5
function test() {
var message = "hi"; //局部变量
}
test();
alert(message); //错误!
1
2
3
4
5
function test() {
message = "hi"; //全局变量
}
test();
alert(message); //"hi"

不推荐滥用全局变量,因为在局部作用域中定义的全局变量很难维护。而且给未经声明的变量赋值在严格模式下会导致抛出 ReferenceError 的错误。

可以使用一条语句定义多个变量,只要把每个变量(初始化与否均可)用逗号分隔开即可:

1
2
3
var message = "hi",
found = false,
age = 29;

因为 ECMAScript 是松散类型的,因而使用不同类型初始化变量的操作可以放在一条语句中完成。

注意,在严格模式下不能定义名为 evalarguments 的变量,否则会导致语法错误。

数据类型

ECMAScript 中有五种简单数据类型(基本数据类型):UndefinedNullBooleanNumberString。还有一种复杂数据类型 – Object,在本质上,Object 是一组无序的明值对组成的。乍一看这几种数据类型不足以表示所有数据,但是 ECMAScript 数据类型具有动态性,所以没有必要再定义其他类型的数据了。

typeof 操作符

返回值 含义
“undefined” 这个值未定义
“boolean” 这个值是布尔值
“string” 这个值是字符串
“number” 这个值是数值
“object” 这个值是对象或 null
“function” 这个值是函数
1
2
3
4
var message = "some string";
alert(typeof message); //"string"
alert(typeof (message)); //"string"
alert(typeof 95); //"number"

上面几个例子说明,typeof 操作符的操作数可以是变量(message),也可以是数值字面量。注 – typeof 是一个操作符。

在 JavaScript 中,null 是一个 object,即 typeof null; 返回 object。这是设计的缺陷,在最初,使用标记位来区分对象类型和原始类型,对象型用 0 标识,原始型用 1 标识。导致了全零的 null 被识别为 objectnull 被认为是一个空的对象引用,也就是一个空的对象指针。这也正是使用 typeof 操作符检测 null 值时会返回 object 的原因。

在技术上讲,函数在 ECMAScript 中是对象,不是一种数据类型。然而函数确实也有一些特殊的属性,因此通过 typeof 操作符来区分函数和其他对象是有必要的。

Undefined 类型

在 JavaScript 中,包含 undefined 值的变量与尚未定义的变量还是不一样的。

1
2
3
var message;      //
alert(message); //
alert(age);
1
2
3
var message;
alert(typeof message); //"undefined"
alert(typeof age); //"undefined"

即便未初始化的变量会自动被赋予 undefined 值,但显示的初始化变量依然是明智的选择。如果做到这一点,那么当 typeof 操作符返回 undefined 值时,我们就知道被检测的变量是没有被声明还是尚未初始化。

对于未声明的变量,只能执行一项操作即用 typeof 操作符检测其数据类型(未声明的变量调用 delet 不会报错,但没意义,而且在严格模式下也会报错)。

未初始化和未声明的变量的区别就是,在用 typeof 操作符检测其数据类型时都显示 undefined,但是在除此之外调用未声明的变量时就会报错。

因为在 JavaScript 中未定义和未声明的变量用 typeof 操作符检测其数据类型时都显示 undefined,所以 DOM 相关函数都是返回 null,从 API 设计角度来讲是合理的。

无论什么情况下,都没有必要将一个变量的值显示的设置为 undefined

Null 类型

如果定义的变量准备在将来保存对象,那么最好将该变量初始化为 null 而不是其他值。这样只要检查 null 值就可以知道相应的变量是否已经保存了一个对象的引用,如下所示:

1
2
3
if(car != null) {
//对 car 对象执行某些操作
}

所以只要意在保存对象的变量还没有真正保存对象,就应该明确地让该变量保存 null 值。

实际上,undefined 值是派生自 null 值的,因此 ECMA-262 规定对他们的相等性测试要返回 true

1
alert(null == undefined);      //true

Number 类型

  • 因为保存浮点数值需要的内存空间是保存整数值的两倍,所以 ECMAScript 会不失时机的将浮点数值转换为整数值。

  • ECMAScript 能够表示的数的范围为 Number.MIN_VALUE ~ Number.MAX_VALUE,在大多数浏览器中为 5e-324 ~ 1.7976931348623157e+308。当程序执行时,数值超过正负范围时会被分别转化为 Infinity-Infinity。想确定一个数是否超出 JavaScript 数值范围,可以用 isInfinite() 函数。

1
2
var result = Number.MIN_VALUE + Number.MIN_VALUE;
alert(isFinite(result)); //false
  • NaN 即非数值(Not a Number)是一个特殊值。用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)。其有两个特点,首先任何涉及 NaN 操作都会返回 NaN,这一点在多步计算中可能会导致问题。其次, NaN 与任何值都不相等,包括其本身。ECMAScript 也定义了 isNaN(); 函数。这个函数接收一个参数,这个参数可以是任何类型的,而函数会帮我们确定这个参数是否 不是数值。函数检查过程是 `isNaN(); => valueOf(); => toString();

  • 有三个可以把非数值转化为数值的函数:Number()parseInt()parseFloat()。在使用 parseInt() 转换数据类型时,为了避免错误解析,建议无论何时都要明确指定基数。多数情况下我们要解析的都是是进制数,因此始终将 10 作为第二个参数是十分必要的。

1
2
3
4
5
6
7
8
9
var num1 = Number("Hello world!");  //NaN
var num2 = Number(""); //0
var num3 = Number("000011"); //11
var num4 = Number(true); //1

alert(num1);
alert(num2);
alert(num3);
alert(num4);
1
2
3
4
var num1 = praseInt("10", 2);       //2   (按二进制解析)
var num1 = praseInt("10", 8); //8 (按八进制解析)
var num1 = praseInt("10", 10); //10 (按十进制解析)
var num1 = praseInt("10", 16); //16 (按十六进制解析)

parseFloat() 只解析十进制值,所以其没有第二个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
var num1 = parseFloat("1234blue");    //1234 - integer
var num2 = parseFloat("0xA"); //0
var num3 = parseFloat("22.5"); //22.5
var num4 = parseFloat("22.34.5"); //22.34
var num5 = parseFloat("0908.5"); //908.5
var num6 = parseFloat("3.125e7"); //31250000

alert(num1);
alert(num2);
alert(num3);
alert(num4);
alert(num5);
alert(num6);

String 类型

字符串由双引号或单引号表示都可以,在 ECMAScript 中的这两种语言形式没有什么区别。

任何字符串的长度都可以通过访问其 length 属性取得,如果字符串中包含双字节字符,那么 length 属性可能不会精确的返回字符串中的字符数目。

1
2
var text = "This is the letter sigma: \u030a.";
alert(text.length); //输出 28

ECMAScript 中的字符串是不可变的,如果要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量,这个过程是在后台完成的,这也就是某些旧版本浏览器在拼接字符串的时候速度很慢的原因了。

要把一个值转换为字符串有两种方法,第一种是 toString() 方法。数值、布尔值、对象和字符串值都有相应的 toString() 方法,但是 nullundefined 值没有。一般调用 toString() 方法时不用传递参数,但是他也可以传递参数。

1
2
3
4
5
6
7
8
9
var age = 11;
var ageAsString = age.toString(); //the string "11"
var found = true;
var foundAsString = found.toString(); //the string "true"

alert(ageAsString);
alert(typeof ageAsString);
alert(foundAsString);
alert(typeof foundAsString);
1
2
3
4
5
6
var num = 10;
alert(num.toString()); //"10"
alert(num.toString(2)); //"1010"
alert(num.toString(8)); //"12"
alert(num.toString(10)); //"10"
alert(num.toString(16)); //"a"

在不知道要转换的值是不是 nullundefined 的情况下可以使用第二种方法:转型函数 String()。使用这种方法时,如果值有 toString() 方法则会调用该方法,没有的话就按本方法规则执行。

1
2
3
4
5
6
7
8
9
var value1 = 10;
var value2 = true;
var value3 = null;
var value4;

alert(String(value1)); //"10"
alert(String(value2)); //"true"
alert(String(value3)); //"null"
alert(String(value4)); //"undefined"

Object 类型

ECMAScript 中的对象其实就是一组数据和功能的集合。

1
var o = new Object();


操作符

在 ECMAScript 中,当对数值应用位操作符时,后台发生如下的转换过程:64 位的数值被转换为 32 位数值,然后执行位操作,最后再将 32 位的结果转换回 64 位数值。但是这个转换过程会导致特殊的 NaN 和 Infinity 值应用位操作时,这两个值会被当成 0 来处理。对非数值可以先使用 Number() 函数将该值转换为一个数值,然后再应用位操作。

1
2
3
var num1 = 25;             //binary 00000000000000000000000000011001
var num2 = ~num1; //binary 11111111111111111111111111100110
alert(num2); //-26

按位非操作的本质就是操作数的负值减一。

左移操作:左移操作符为 <<,左移不会影响操作数的符号位。

1
2
3
var oldValue = 2;             //equal to binary 10
var newValue = oldValue << 5; //equal to binary 1000000 which is decimal 64
alert(newValue); //64

右移操作分为有符号 >> 和无符号 >>> 两种。对于正数来说,这两种方法的结果一样。但对于负数来说,无符号右移是以 0 填充空位,而不是像有符号右移那样以符号位的值来填充空位。

1
2
3
var oldValue = -64;              //equal to binary 11111111111111111111111111000000
var newValue = oldValue >>> 5; //equal to decimal 134217726
alert(newValue); //134217726

我们可以利用逻辑或的行为特性来避免为变量赋 nullundefined 值。例如:

1
var myObject = preferredObject || backupObject;

上面这段代码,如果 preferredObject 的值不是 null,那么它的值将被赋给 myObject;如果是 null,则将 backupObject 的值赋给 myObject。ECMAScript 程序的赋值语句常用这种模式。

加性操作符有以下特性:+0+0 结果为 +0-0-0 结果为 -0+0-0 结果为 +0。如果两个操作数都是字符串,则将第二个操作数与第一个操作数拼接起来。如果只有一个操作数是字符串,则将另一个操作数转换为字符串,然后再将两个字符串拼接起来。

1
2
3
4
var result1 = 5 + 5;     //two numbers
alert(result1); //10
var result2 = 5 + "5"; //a number and a string
alert(result2); //"55"
1
2
3
4
var num1 = 5;
var num2 = 10;
var message = "The sum of 5 and 10 is " + num1 + num2;
alert(message); //"The sum of 5 and 10 is 510"
1
2
3
4
var num1 = 5;
var num2 = 10;
var message = "The sum of 5 and 10 is " + (num1 + num2);
alert(message); //"The sum of 5 and 10 is 15"

减性操作符有以下特性:+0+0 结果为 +0-0+0 结果为 -0-0+0 结果为 +0

1
2
3
4
5
6
var result1 = 5 - true;    //4 because true is converted to 1
var result2 = NaN - 1; //NaN
var result3 = 5 - 3; //2
var result4 = 5 - ""; //5 because "" is converted to 0
var result5 = 5 - "2"; //3 because "2" is converted to 2
var result6 = 5 - null; //5 because null is converted to 0

相等操作符有相等 == 和不相等 !=、全等 === 和不全等 !== 两种。前者先转换再比较,后者仅比较不转换。除此之外无区别。转换指转换成数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var result1 = ("55" == 55);    //true ?equal because of conversion
var result2 = ("55" === 55); //false ?not equal because different data types

var result3 = ("55" != 55); //false ?equal because of conversion
var result4 = ("55" !== 55); //true ?not equal because different data types

alert(null == undefined); //true
alert(null === undefined); //false

alert("NaN" == NaN); //false
alert("NaN" === NaN); //false
alert(NaN == NaN); //false
alert(NaN === NaN); //false
alert(NaN != NaN); //true
alert(NaN !== NaN); //true

alert(false == 0); //true
alert(false === 0); //false
alert(true == 1); //true
alert(true === 1); //false

alert(null == 0); //false
alert(undefined == 0); //false

alert(5 == "5"); //true
alert(5 === "5"); //false

注意:null == undefined 会返回 true,而 null === undefined 会返回 false,因为他们是不同类型的值。由于相等和不相等操作符存在类型转换问题,而为了保持代码中数据类型的完整性,我们推荐使用全等和不全等操作符。

条件操作符:

1
var max = (num1 > num2) ? num1 : num2;


语句

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

  • 因为 ECMAScript 中不存在块级作用域,因此在循环内部定义的变量也可以在外部访问到。例如:

1
2
3
4
5
var count = 10;
for (var i=0; i < count; i++){
alert(i);
}
alert(i); //10
  • for-in语句是一种精准的迭代语句,可以用来枚举对象属性。用法是:
1
for (property in wxpression) statement

下面是一个示例:这个例子循环显示 BOM 中 window 对象的所有属性。

1
2
3
for (var propName in window) {
document.wright(propName);
}

如果要迭代的对象的变量值为 nullundefinedfor-in 语句会抛出错误。ECMAScript 5 已经更正了这一行为,对这种情况不再抛出错误,而只是不执行循环体。为了保证最大限度兼容性,建议在使用 for-in 循环之前先检查确认该对象的值不是 nullundefined

  • lable 语句可以在代码中添加标签,以便将来使用。语法为:
1
lable: statement;

代码示例:这个例子中定义的 start 标签可以在将来由 breakcontinue 语句引用。加标签的语句一般都要与 for 语句等循环语句配合使用。

1
2
start: for (var - = 0; i < count; i++) {
}

下面这段代码使得 break 语句不仅会退出内部的 for 语句,而且也会退出外部的 for 语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
var num = 0;

outermost:
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) {
if (i == 5 && j == 5) {
break outermost;
}
num++;
}
}

alert(num); //55
1
2
3
4
5
6
7
8
9
10
11
12
13
var num = 0;

outermost:
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) {
if (i == 5 && j == 5) {
continue outermost;
}
num++;
}
}

alert(num); //95
  • with 语句的作用是将代码的作用域设置到一个特定的对象中,语法如下:
1
with (expression) statement;

定义 with 语句的目的是简化多次编写同一个对象的工作,如下面的例子所示:

1
2
3
var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href;

with 语句写的话就可以简化成下面这样:

1
2
3
4
5
with (location) {
var qs = search.substring(1);
var hostName = hostname;
var url = href;
}

注意:严格模式下不允许使用 with 语句,否则将视为语法错误。同时,大量使用这种语句会导致性能下降,同时也会给调试代码造成困难,因此在开发大型应用程序时不建议使用 with 语句。

  • switch 语句,虽然 ECMAScript 的 switch 语句是借鉴其他语言的,但是也有其特色。可以在 ECMAScript 的 switch 语句中使用任何数据类型。其次,每一个 case 的值不一定是常量,可以是变量,也可以是表达式。
1
2
3
4
5
6
7
8
9
10
switch ("hello world") {
case "hello" + " world":
alert("Greeting was found.");
break;
case "goodbye":
alert("Closing was found.");
break;
default:
alert("Unexpected message was found.");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var num = 25;
switch (true) {
case num < 0:
alert("Less than 0.");
break;
case num >= 0 && num <= 10:
alert("Between 0 and 10.");
break;
case num > 10 && num <= 20:
alert("Between 10 and 20.");
break;
default:
alert("More than 20.");
}


函数

对于函数的返回值,推荐的做法是要么让函数始终都返回一个值,要么永远都不要返回值,否则,如果函数有时候返回值,有时候不返回值,会给调试代码带来不便。

ECMAScript 不介意你传递的参数个数和参数的数据类型,因为 ECMAScript 中的参数只在内部是一个数组来表示的。实际上在函数体内可以通过 arguments 对象来访问这个参数数组,从而获得传递给函数的每一个参数。arguments 对象只是与数组类似但并不是 Array 的实例。

没有传递值的命名参数将自动被赋予 undefined 值,这就和定义变量但为初始化类似。

在 ECMAScript 中,定义了两个名字相同的函数,则该名字只属于后定义的函数。


欢迎大家在评论区留下你的想法和感受!

欢迎大家关注知乎专栏:全栈成长之路

也欢迎大家加入学习交流QQ群:637481811

LeviDing wechat
欢迎扫描上方微信公众号,订阅博客获得实时动态!
坚持原创技术分享,您的支持将支持我更好的创作!
0%