攻下《JavaScript高级程序设计》——第三章 基本概念 (上)

如果说前两章都是前戏铺垫的话,本章就正式进入JavaScript的学习啦。第三章主要内容围绕JavaScript的基本概念展开,比如语法,操作符,数据类型,内置功能等等。下面我们就开始吧~~

1.语法

1.1 标识符

标识符就是指变量、函数、属性的名字,或者函数的参数。标识符按照以下规则组合:

  1. 第一个字符必须是字母、下划线或者美元符号;

  2. 除第一个字符外,其他字符可以是字母,下划线,美元符号,或数字。

由此可知,除了不能把字母放在第一位,标识符可以由字母,下划线,美元符号,或数字随机组合而产生。

有几点需要注意:

  1. 标识符的字母也可以包含扩展的ASCII和Unicode字母字符,但是不推荐这么做;

  2. 按照惯例,ECMAScript标识符采用驼峰大小写格式。这一点在开发时容易被忽略,虽然没有强制要求,但是为了和ECMAScript内置函数和对象名保持一致,可以当做一种最佳实践;

  3. ECMAScript中的一切都区分大小写,所以在命名是要注意,test和Test是两个变量哦;

  4. 不能把关键字、保留字、true、false、null作为标识符。关于关键字等内容,下一节介绍。

1.2 注释

ECMAScript使用c语言的注释风格,如下:

  1. 单行注释
// 这是单行注释内容
复制代码
  1. 块级注释
/*
 * 这是块级注释内容 第一行
 * 这是块级注释内容 第二行
 */
复制代码

1.3 严格模式

严格模式为JavaScript定义了一种不同的解析与执行模型,这一模式的概念在ECMAScript5引入,在该模式下,ECMAScript3中的一些不确定的行为将得到处理。启用严格模式,可在顶部添加:

"use strict";
复制代码

上面这段代码是个编译指令,加上它就是告诉支持的JavaScript引擎切换到严格模式。在某一函数上方加上这段代码,可以指定函数在严格模式下执行,例如:

function testStrict() {
    "use strict";
}
复制代码

1.4 语句

  1. 分号结束一句语句。ECMAScript中的语句以分号结尾,以便解析器解析。虽然不加分号也是可以的,但是建议任何时候都不要省略它,以免解析时发生错误;

  2. 代码块被{}包围。虽然条件控制语句只在执行多条语句的情况下才要求使用代码块,但最佳实践是始终在控制语句中使用代码块。如下:

if (condition) console.log('yes')
// 下面是最佳实践(即使用代码块)
if (condition) {
    console.log('yes');
}
复制代码

2. 关键字和保留字

2.1 关键字

ECMAScript有一组具有特定用途的关键字,这些关键字可用于表示控制语句的开始或结束,或者用于执行特定操作等。ECMAScript的所有关键字如下:

注意,第五版新增了一个debugger关键字。

上面已经说过了,关键字不能用作标识符。

2.2 保留字

另外,ECMAScript还有一组保留字,保留字是这门语言中还没有特定作用,但将来有可能被用作关键字的一组特殊存在。同样,他们也不能被当作标识符。ECMAScript保留字的完整列表如下(* 标记的关键字是 ECMAScript5 中新添加的):

3. 变量

ECMAScript的变量是松散类型的,也就是可以用来保存任何类型的数据。

定义变量时,要用var关键字,后面跟变量名,如下:

var test;
复制代码

上面这段代码定义了一个名为test的变量,它可以用来保存任意类型的值,现在这种情况,变量test只被初始化而未被我们代码赋值,会默认保存一个特殊的值——undefined。

当然,我们也可以初始化的时候就给变量赋值,如下:

var test =  'great';
复制代码

这时,test变量中就保存了个字符串'great'。

也可以这样,用逗号将每个变量分开,一块声明。注意,换行和缩进不是必须的,但是可以提高代码可读性:

var test =  'great',
    name = 'Joe',
    age = '30'
复制代码

虽说在ECMAScript中,变量可以被赋予各种类型的值,但是这样的写法是不建议的:

var test =  'great';
test = 1;  // 有效,但不推荐
复制代码

关于变量,不得不提的就是它的作用域。变量论作用域划分被分为局部变量和全局变量。而用var操作符定义的变量将成为定义该变量作用域的局部变量,什么意思呢,看下面代码:

function operation() {
    var test = 'good'; // test是局部变量
}
test();
console.log(test); // 报错
复制代码

也就是说,如果在函数中使用了var定义变量,那么这个变量就是局部变量,它在函数退出后就会被销毁。

但若像这样,在函数中不使用即省略var定义变量,会默认创建一个全局变量,如下:

function operation() {
    test = 'good'; // test是全局变量
}
test();
console.log(test); // 'good'
复制代码

但上述的做法并不被建议,因为这样会很难维护,而且这一做法(不声明变量)在严格模式中会抛出错误。

4. 数据类型

数据类型应该算是前端面试必考题了,让我们来看看吧。 ECMAScript一共有6种数据类型,包括5种基本数据类型(即简单数据类型),分别是:String,Number,Boolean,Null,Undefined和1种复杂数据类型——Object。我们一个个来分析,先从简单数据类型说起吧。

4.1 Undefined 类型

Undefined类型就只有undefined这一个值,回想一下我们在写代码的时候什么地方会出现undefined,是不是下面两种情况:

var a;
console.log(a); // undefined
var b = {
    c: 'Ross',
    d: 'Joy'
 };
console.log(b.f); // undefined
复制代码

即声明了变量但未对其初始化时,变量的值就是undefined;还有就是查找某一对象中并未存在的属性时,也会出现undefined。 在这里要注意一点的是,这个值是undefined,是一种数据类型,而不是"undefined",这是字符串。所以有如下等式:

var f = undefined;
console.log(f == undefined); // true
console.log(f === undefined); // true
复制代码

这里还要注意一个问题哦,变量未初始化和未声明是两个概念,只有声明了而未初始化的变量值才会为undefined,未声明的变量压根不存在,所以找不到它就会报错。如下图:

var a;
console(a); // undefined;
console(b); // 报错:Uncaught ReferenceError: b is not defined
复制代码

4.2 Null类型

同样的,Null类型也只有一个值,就是null。从逻辑角度来看,null值表示一个空对象指针。所以,如果定义的变量准备在以后用于保存对象,那么最好将变量初始化为null。这样一来,只用直接检查null值就知道相应的变量是否已经保存了一个对象的引用。如下图:

if (test !== null) {
   // 执行其他操作
}
复制代码

有一点可能大家不知道,实际上,undefined是null的派生值,所以能看到以下诡异的等式:

console.log(undefined == null); // true
复制代码

4.3 Boolean 类型

Boolean类型是ECMAScript中使用得最多的一种类型,该类型只有两个字面量值:false和true。值得注意的一点是,Boolean类型的字面量false和true是区分大小写的,也就是说,True和False只是标识符不是Boolean类型。刚刚说Boolean类型是ECMAScript中使用得最多的一种类型,我理解的是,因为ECMAScript中所有类型的值都有与这两个Boolean值等价的值,如:

var trans = 'test';
var transAsBoolean = Boolean(trans);
console.log(transAsBoolean); // true
复制代码

如上图,对任何数据类型的值,调用Boolean()函数,总会返回一个布尔值。下面为各数据类型对应的转换规则:

我们每次使用条件控制语句时(比如if),会自动执行对应的Boolean转换,如下:

var trans = "test";
if (trans) {
    console.log("it's true");
}
复制代码

4.4 Number类型

ECMAScript使用IEEE754(IEEE二进位浮点数算术标准)格式来表示整数和浮点数。为了支持各种数值类型,ECMA-262定义了不同是数值字面量格式。最基本的就是十进制整数了,如下:

vat intNum = 22; // 十进制整数
复制代码

当然,整数还可以通过八进制或者十六进制的字面值来表示。关于八进制,有以下几点需要注意:

  1. 八进制字面值的第一位必须是0,如:
var octaNum1 = 070; // 八进制的56
复制代码
  1. 八进制的数字序列是1~7,如果字面值的某一位数值超过了0~7的范围,那么该字面值会直接被当做十进制数值解析(忽略前导零),如下:
var octaNum2 = 079; // 9超出范围,无效的八进制数值——解析为79
var octaNum3 = 08; // 8超出范围,无效的八进制数值——解析为8
复制代码
  1. 八进制字面量在严格模式下是无效的。

至于十六进制:十六进制字面量值的前两位必须是0x,后跟任何十六进制数字(0~9和A~F,字母可大写可小写),如:

var hexNum1 = 0xA; // 十六进制的10
var hexNum2 = 0x1f; // 十六进制的31(1 * 16 + 15 = 31)
复制代码

在进行算术运算时,所有八进制和十六进制所表示的数值都会被转换成十进制。

4.4.1 浮点数值

所谓浮点数,就是数值中包含一个小数点,并且小数点后面必须至少有一位数字的数。注意,从上述所得,若小数点前面没有整数也是可以的,如:

var floatNum1 = 1.1;
var floatNum2 = 0.1;
var floatNum3 = .1; // 有效,但不推荐
复制代码

如果小数点后面没有跟任何数字,那么这个数值会作为整数值来保存;同样的,如果浮点数值本身就是整数(如1.0),那么该值也会被保存为整数,如下:

var floatNum4 = 1.; // 小数点后无数字,解析为1
var floatNum5 = 10.0; // 整数,解析为10
复制代码

由上可知,ECMAScript会不失时机地将浮点数值转换为整数数值,这是因为,保存浮点数值需要的内存空间是保存整数值的两倍

对于极大或极小的值,我们也可以用e表示法(科学计数法)。用e表示法表示的数值的等于e前面的数值乘以10的指数次幂(e后面的数值),如下:

 var floatMaxNum = 3.14e7; // 等于314000 (3.14 * 10^7 = 31400000)极大
 var floatMinNum = 3e-8; // 等于0.00000003(3 / (10^7) = 0.00000003)极小
复制代码

在默认情况下,ECMAScript会将那些小数点后面带6个零以上的浮点数都转换为e表示法表示的数值。

浮点数值的最高精度是17位小数,但是在进行算术运算时其精确度远远不如整数。例如,0.1加0.2不等于0.3,而是0.30000000000000004,所以不要做这样的测试:

var a = 0.1, b = 0.2;
if (a + b == 0.3) {
    console.log('success'); // 永远打印不出来这句话
}
复制代码

这个问题是基于IEEE754数值的浮点计算的通病,若换成0.15加0.15,或0.05+0.25都可以得到结果0.3,唯独0.1加0.2不行。

4.4.2 数值范围

由于内存限制,ECMAScript并不能保存所有的数值,它有一个能保存的最大值和最小值,分别存放在Number.MIN_VALUE和Number.MAXVALUE中,在大多数浏览器中,Number.MIN_VALUE中的值是5e-324,Number.MIN_VALUE中的值是1.7976931348623157e+308。如果某次计算超出了这个数值范围,如果是负数,那么该数值会被自动转换为-Infinity(负无穷),如果该值是正数,就会被转换为Infinity(正无穷)。有一点还需要注意的是,Infinity是不能够参与计算的。若在计算前想判断一个值是不是有穷的,可以使用isFinite()函数。

4.4.3 NaN

NaN,即Not a Number,翻译过来就是非数值,这是一个特殊的数值。这个值用于表示一个本来要返回数值的操作数未返回数值的情况(为了避免抛出错误)。有点绕口哈,我们来看个栗子:

var a = 10;
var b = 'world';
console.log(a/b); // NaN
复制代码

上面这段代码放在其他语言中,会导致错误,从而停止代码执行。但是在ECMAScript中,任何数值除以非数值都会返回NaN。

NaN有两个非同寻常的特点:

  1. 任何涉及NaN的操作,都会返回NaN
  2. NaN和任何值都不相等,包括NaN

举例说明:

console.log(NaN == NaN); // false
console.log(NaN !== NaN); // true
var test = NaN + 10;
console.log(test); // NaN
复制代码

要想知道某个数值是不是NaN,可以使用isNaN()函数,如下:

console.log(isNaN(NaN)); // true
console.log(isNaN(10)); // false (10是一个数值)
console.log(isNaN("10")); // false ("10"可以转换成数值10)
console.log(isNaN(true)); // false (true可以转换成数值1)
console.log(isNaN("false")); // true ("false"不能转换成数值)
复制代码

从上面的代码我们应该可以推测出函数isNaN的工作原理。isNaN()在接受一个值后,会尝试将这个值转换为数值,不能转换的都会导致这个函数返回true。isNaN()接收的这个参数可以是任何类型,所以也可以接收对象。在接收对象调用isNaN()函数时,会先调用对象的ValueOf()方法,然后确定该方法的返回值是否可以转换为数值,若不行,返回值再调用toString()方法,再测试返回值。

4.4.3 数值转换

前面介绍过Boolean类型的数值转换,同样的Number类型也是有数值转换的。有三个函数可以将非数值转换为数值:Number()、parseInt()、和parseFloat()。其中,转型函数Number()可以用于任何数据类型,而另两个函数则专门用于把字符串转换为数值。这三个函数对于同样的输入会有不同的返回结果。

Number()函数

转换规则如下:

  1. 如果是Boolean值,Number(true) => 1,Number(false) => 0;
  2. 如果是数字,不做改变;
  3. 如果是null,返回0
  4. 如果是undefined,返回null
  5. 如果是字符串,有以下规则:
  • 字符串中只包含数字,将其转换为十进制数。Number("123") => 123,Number("018") => 18;
  • 字符串中包含有效的浮点格式,将其转换成对应浮点数。Number("1.8") => 1.8;
  • 字符串包含有效的十六进制格式,将其转换成相同大小的十进制数值;
  • 字符串为空,转换为0;
  • 除上述之外的字符串,都转换为NaN。
  1. 如果是对象,会先调用对象的ValueOf()方法,然后确定该方法的返回值是否可以转换为数值,若返回结果为NaN,返回值再调用toString()方法,再测试返回值。细心的小伙伴就会发现,这里和isNaN()接收对象的工作原理一毛一样。

理论撸完,来上栗子:

var num1 = Number(false); // 0
var num2 = Number(123); // 123
var num3 = Number(null); // 0
var num4 = Number(undefined); // null
var num5 = Number('0123'); // 123
var num6 = Number('0.123'); // 0.123
var num7 = Number(''); // 0
var num8 = Number('hello world'); // NaN
复制代码

由此可见,Number()是一股脑地把你丢进来的参数全部都做个处理,但是parseInt()就不一样了,parseInt()的转换相比于Number()要更合理一些。下面我们来看看。

parseInt()函数

它的工作原理是这样的:

  1. 第一步:从左到右解析字符串,忽略字符串前面的空格,直到找到第一个非空格字符;
  2. 第二步:分析第一个字符,如果第一个字符不是数字字符或者负号,parseInt()就会返回NaN,也不继续往下了;
  3. 如果第一个字符是数字字符,解析第二个字符,就这么继续下去,直到解析完所有字符或者后面遇到一个非数字字符结束。

在解析过程中,有两点需要注意:

  1. 如果字符以"0x"开头且后面跟数字字符,parseInt()会将其当做一个十六进制数解析;
  2. 如果字符以"0"开头且后面跟数字字符,parseInt()会将其当做一个八进制数解析;

言下之意就是,如果第一个字符是数字字符,那么parseInt()也能够识别出十进制、八进制、十六进制等各种整数格式。来上栗子:

var num1 = parseInt("1234hahaha"); // 1234
var num2 = parseInt(""); // NaN
var num3 = parseInt("0xa"); // 10 (十六进制)
var num4 = parseInt("18.0"); // 18 (因为"."不是有效的数字字符)
var num5 = parseInt('070'); // 56 (八进制)
复制代码

但是,对于八进制格式的解析,在ECMAScript 3 JavaScript引擎中和ECMAScript 5 JavaScript引擎中存在分歧。所以为了保证正确的结果,我们在转换十六进制或八进制时,最好在函数后面再加一个参数——基数(即使用多少进制),如下:

var num1 = parseInt("AF", 16); // 175(十六进制)
var num2 = parseInt("AF"); // NaN
var num3 = parseInt("10", 2); // 2 (二进制)
var num4 = parseInt("10", 8); // 8 (八进制)
var num5 = parseInt('10', 10); // 10 (十进制)
复制代码

为了避免错误,建议在使用parseInt()时还是带上基数。因为大多数我们都是转换成十进制,那么最好还是将10作为第二个参数。

parseFloat()函数

parseFloat()的工作原理和parseInt()类似,都是从左到右识别,它可以识别所有有效的浮点数值格式。但是有一点不同的是,十六进制格式的字符串始终会被转换为0,因为parseFloat()只解析十进制值。如果要解析的是整数值,则parseFloat()会原封不动地返回整数值。如下:

var num1 = parseFloat("1234hahaha"); // 1234
var num2 = parseFloat("0AF"); // 0
var num3 = parseFloat("18.8"); // 18.8
var num4 = parseFloat("0.113.445"); // 0.113
var num5 = parseFloat("0338.11"); // 338.11
var num6 = parseFloat("3.125e7"); // 31250000
复制代码

4.5 String类型

String类型表示用于表示由零或多个16位Unicode字符组成的字符序列,即字符串。字符串可以由双引号("")或者单引号('')表示,这两种表示方法完全相同,没有区别。不过,以什么样的引号开头就要以什么样的引号结尾,不然会导致语法错误。

4.5.1 字符字面量

String类型包含下面一些特殊的字符字面量,也叫转义序列,用于表示非打印字符或者具有其他用途的字符,如下表(来自W3CSchool):

这些字符字面量可以出现在字符串的任意位置。其实\可以理解为转义字符。如下:

关于字符串的长度,任何字符串都能通过访问其length属性获得。你知道下面代码里面的变量text一共有几个字符吗?

var text = "\u03a3 is the first letter.";
console.log(text.length); // 22
复制代码

答案是22个字符,你数对了吗~有四点需要注意的:

  1. 上表中这些特殊的字符字面量,即转义序列,只表示1个字符,也就是上面代码中的\u03a3只是1个字符;

  2. 1个空格表示1个字符;

  3. 字符串头尾的引号不算在字符数中,它只是字符串类型的一种表现形式。

  4. 通过访问length属性获得的字符数,其实是字节长度,它只包括16位字符的数目。在ECMAScript中,如果字符串中包含双字节字符,那么length属性则不能返回正确的长度。若想解决该问题,可以使用chatCodeAt() 和 toString()。先用chatCodeAt()方法将双字节字符转换为字符的 Unicode 编码,然后再使用toString(16)将其转换成16位字符字符串,最后再用length属性获取其长度。

4.5.2 字符串的特点

ECMAScript中的字符串是不可变的,也就是说,字符串一旦创建,它的值不能改变。 要改变某个变量中保存的字符串,首先要销毁原来的字符串(注意是销毁字符串),然后再用另一个包含新值的字符串填充该变量。

讲真,在重新系统看书的时候我才知道字符串有这么一特点……上段代码来分析:

var text = "hello";
text = text + " World!"
复制代码

看上面这段代码,是不是很简单很容易理解。但是在后台运行时却没这么简单。在这段简单的字符串拼接中,在后台运行时,有这么几个步骤:

  1. 创建一个能容纳12个字符的新字符串;

  2. 在这个字符串中填充"hello"和" World!";

  3. 销毁原来的字符串"hello"和字符串" World!"。

这就是为什么某些旧版本浏览器拼接字符串时速度会很慢。

4.5.3 转换为字符串

要把一个值转换为字符串有两种方式,一种是toString()方法,还有一种是利用String()函数。下面我们一个个的来说。

1.toString()方法

除了null和undefined没有toString()方法外,其他的,数值、布尔值、对象和字符串都有toString()方法。

调用toString()方法允许调用时传递一个参数,尽管多数情况下并不需要传递这个参数。这个参数是输出值的基数,通过传递基数,toString()可以返回一个你需要的格式数值,它可以是二进制、八进制、十六进制乃至其他任意有效进制格式表示的字符串值,下面举个栗子:

var flag = true;
var flagString = flag.toString(); // "true"
var num = 10;
console.log(num.toString()); // "10"
console.log(num.toString(2)); // "1010"
console.log(num.toString(8)); // "12"
console.log(num.toString(10)); // "10"
console.log(num.toString(16)); // "a"
复制代码

这里,toString()不传参数时输出的值与基数为10所输出的值相同。

但是null和undefined并没有toString()方法,那我们在不知道变量是什么类型的情况下,怎么将其转换成字符串呢?String()函数会帮我们解决这个问题。

2.String()函数

String()函数遵循以下规则:

  1. 如果值有toString()方法,则调用该方法(没有参数),并返回相应结果
  2. 如果值是null,则返回"null"
  3. 如果值是undefined,则返回"undefined"

再来看看栗子:

var value1 = 10;
var value2 = false;
var value3 = null;
var value4;

console.log(String(value1)); // "10"
console.log(String(value2)); // "false"
console.log(String(value3)); // "null"
console.log(String(value4)); // "undefined"
复制代码

4.5 Object类型

ECMAScript中的对象其实就是一组数据和功能的集合,也有叫做属性和方法的集合。对象的创建方式如下:

var obj = new Object();
复制代码

如上代码所示,通过执行new操作符,后面跟要创建的对象类型名称(这里是Object)来创建。创建后,就可以为实例obj添加属性和方法了。

在ECMAScript中,若不给构造函数传递参数,则可以省略后面的圆括号。虽然这种写法是允许的,但这并不是推荐的做法,写法如下:

var obj = new Object; // 有效,但不推荐
复制代码

在ECMAScript中,Object类型有一个很重要的思想:Object类型是它的所有实例的基础。也就是说,Object类型所具有的任何属性和方法都同样存在于更具体的对象中。首先看看Object的每个实例都具有的属性和方法:

  1. constructor:保存着用于创建当前对象的函数。就上面的例子而言,构造函数就是Object();
  2. hasOwnProperty(propertyName):用于检查给定的属性是否在当前对象实例中。其中,作为参数的属性名(propertyName)必须以字符串形式指定,用法如下:
var obj = new Object();
obj.name = "Joy";
obj.age = 18;
console.log(obj.hasOwnProperty("name")); // true
复制代码
  1. isPrototypeOf(object):用于检查传入的对象是否是当前对象的原型(这个放在后面讲,原型是个很庞大的知识体系);
  2. propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用for-in语句来枚举(至于for-in,也放到后面讲)。它的传参方式与hasOwnProperty一样。
  3. toLocaleString():返回对象的字符串表示,该字符串与执行的地区对应。
  4. toString():返回对象的字符串表示。
  5. valueOf():返回对象的字符串、数值或布尔值表示。通常与toString()方法的返回值相同。

由上我们知道,Object是所有对象的基础,所以所有对象都具有上述的属性和方法。这在后面也会详细再介绍的。但是有一点需要注意的是,这只是ECMAScript中的对象,它的行为并不适合于JavaScript中的其他对象。在前面我们也了解到了,JavaScript还有BOM和DOM,它们中的对象可就不继承Object了。 第三章后面还有操作符的介绍,请大家移步到下一篇吧~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值