【前端|Javascript第1篇】一文搞懂Javascript的基本语法

在这里插入图片描述

欢迎来到JavaScript的奇妙世界!作为前端开发的基石,JavaScript为网页增色不少,赋予了静态页面活力与交互性。如果你是一名前端小白,对编程一无所知,或者只是听说过JavaScript却从未涉足过,那么你来对了地方!本篇博客将带领你逐步进入JavaScript的大门,一步一步地探索这门语言的基本语法和应用。无论你是想为网页添加动态效果,或是构建一个全功能的Web应用程序,学习JavaScript都是你迈向成功的必经之路。准备好了吗?让我们一起踏上这段令人兴奋的学习旅程吧!

1. 变量

在编程语言中,变量用于存储数据值。

JavaScript 使用关键字 var 来定义变量, 使用等号来为变量赋值。

变量初始化

  1. var是一个JS关键字,用来声明变量(variable变量的意思)。使用该关键字声明变量后,计算机会自动为变量分配内存空间。
  2. age 是程序员定义的变量名,我们要通过变量名来访问内存中分配的空间。
//声明变量同时赋值为18
var age = 18; 
//同时声明多个变量时,只需要写一个 var, 多个变量名之间使用英文逗号隔开。

var age = 18, address ='火影村',salary = 15000;

2. 词法语法

ECMAScript 源码文本会被从左到右扫描,并被转换为一系列的输入元素,包括标识符、控制符、行终止符、注释和空白符。

同样地,ECMAScript 也定义了一些关键字、字面量以及行尾分号补全的规则。

区分大小写

JavaScript 是区分大小写的语言,也就是说,关键字、变量、函数名和所有的标识符(Identifier)都必须采取一致的大小写的形式。但是需要注意的是,HTML 和 CSS 并不区分大小写(尽管 XHTML 区分大小写),也就是说如果我们在用 JavaScript 控制 HTML 属性的时候对 HTML 来说 id 和 ID 没区别,但是 JavaScript 有区别。

代码示例:

abc、Abc、aBc、abC、ABC 是五个不同的变量名。

var abc = 1;
var Abc = 2;
var aBc = 3;
var abC = 4;
var ABC = 5;

console.log(abc, Abc, aBc, abC, ABC); // 1 2 3 4 5

注释

JavaScript 不会执行注释。

我们可以添加注释来对 JavaScript 进行解释,或者提高代码的可读性。

单行注释

单行注释以两个斜杠开头
快捷键:ctrl + /

// let a;

多行注释

多行注释又叫块级注释,以 /* 开头,以 */ 结尾
快捷键:shift + alt + a

/*
以下代码为
声明变量并
赋值
*/
let a;
a = 1;

块级注释 /**/ 可以跨行书写,但不能嵌套,否则会报错。

// Error

/*
注释1
/*
注释1.1
 */
 */

块级注释 /**/ 中的那些字符也可能出现在正则表达式字面量里,所以块级注释对于被注释的代码块来说是不安全的。

/*
    var rm_a = /a*/.match(s);
*/

直接量

JavaScript 数据直接量:直接量(Literals),又名字面量,就是可以在程序中直接使用的数据。

主要有以下几种直接量:

空直接量

null;

布尔直接量

true;
false;

数值直接量

// 十进制
1234567890;

字符串直接量

'foo';
'bar';

对象直接量

var o = { a: 'foo', b: 'bar', c: 42 };

// ES6中的简略表示方法
var a = 'foo',
  b = 'bar',
  c = 42;
var o = { a, b, c };

// 不需要这样
var o = { a: a, b: b, c: c };

数组直接量

[1954, 1974, 1990, 2014];

标识符

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

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

按照惯例,ECMAScript 标识符采用驼峰大小写格式,也就是第一个字母小写,剩下的每个单词的首字母大写。

const firstSecond = 123;

const myCar = 'Toyota';

const doSomethingImportant = function () {};

关键字和保留字

和其他任何编程语言一样,JavaScript 保留了一些标识符为自己所用。这些保留字不能用做普通的标识符。由于好多参考书的误导,貌似保留字和关键字是分开的,其实并不是,关键字只是保留字的一部分。

保留字包括关键字未来保留字空字面量布尔值字面量

保留字

  • 关键字 Keyword
  • 未来保留字 FutureReservedWord
  • 空字面量 NullLiteral
  • 布尔值字面量 BooleanLiteral

关键字

以下关键字 ES6 规范中已实现

break      do         instanceof  typeof
case       else       new         var
catch      finally    return      void
continue   for        switch      while
debugger   function   this        with
default    if         throw       delete
in         try        class       extends
const      export     import

未来保留字

以上是 ECMAScript6 的保留字,但在 ECMAScript3 版本中的保留字并不一样,若希望代码能在基于 ECMAScript3 实现的解释器上运行的话,应该避免使用以下保留字作为标识符。

abstract    boolean     byte        char
constdouble enum        final       float
goto        implements  int         interfacelong
native      package     private     protected
public      short       static      super
throw       transient   volatile    synchronized

预定义变量和函数

此外,JavaScript 预定义了很多全局变量和函数,应该避免把它们的名字用做标识符名。

String      Number      Boolean      Array
Date        Function    Math         Object
RegExp      Error       EvalError    JSON
Infinity    NaN         isNaN        isFinite
undefined   arguments   parseInt     parseFloat
eval        decodeURI   encodeURI    decodeURIComponent
encodeURIComponent      RangeError   ReferenceError
TypeError   URIError    SyntaxError

分号

JavaScript 使用分号 ; 将语句分隔开,这对增强代码的可读性和整洁性是非常重要的。

有些地方可以省略分号,有些地方则不能省略分号。

两条语句用两行书写,第一个分号可以省略

a = 3;
b = 4;

两条语句用一行书写,第一个分号不能省略

a = 3;
b = 4;

但 JavaScript 并不是在所有换行处都填补分号,只有在缺少了分号无法正确解析代码时,JavaScript 才会填补分号。换句话说,如果当前语句和随后的非空格字符不能当成一个整体来解析的话,JavaScript 就在当前语句行结束处填补分号。

代码示例:

var a;
a = 3;
console.log(a);

JavaScript 将其解析为:

var a;
a = 3;
console.log(a);

3. 数据类型

JavaScript 是一种 弱类型语言 或者说 动态语言。这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。

这也意味着你可以使用同个相同名称的变量保存不同类型的数据:

var foo = 42;
// foo is a Number now

var foo = 'bar';
// foo is a String now

var foo = true;
// foo is a Boolean now

ECMAScript 标准定义了原始数据类型引用数据类型,共七种内置类型:

  • 原始数据类型(基本类型):按值访问,可以操作保存在变量中实际的值。
    • 空值(null)
    • 未定义(undefined)
    • 布尔值(boolean)
    • 数字(number)
    • 字符串(string)
    • 符号(symbol)
  • 引用类型(复杂数据类型):引用类型的值是保存在内存中的对象。
    • 对象(Object)
      • 布尔对象(Boolean)
      • 数字对象(Number)
      • 字符串对象(String)
      • 函数对象(Function)
      • 数组对象(Array)
      • 日期对象(Date)
      • 正则对象(RegExp)
      • 错误对象(Error)

原始数据类型

空值

空值 null 是一个字面量,它不像 undefined 是全局对象的一个属性。

null 是表示缺少的标识,指示变量未指向任何对象。把 null 作为尚未创建的对象,也许更好理解。

代码示例

foo 不存在,它从来没有被定义过或者是初始化过。

foo;
> "ReferenceError: foo is not defined"

foo 现在已经是知存在的,但是它没有类型或者是值。

var foo = null;
foo;
> null

未定义值

未定义值 undefined 是全局对象的一个属性。也就是说,它是全局作用域的一个变量。undefined 的最初值就是原始数据类型 undefined

var foo;


console.log(foo);
// undefined

布尔值

布尔类型表示一个逻辑实体,可以有两个值:truefalse

数字

进制数

  • 十进制:JavaScript 中默认的进制数
  • 八进制:第一位必须是 0,然后是 0-7 的数字组成
  • 十六进制:前两位必须是 0x,然后是 0-9 及 A-F(字母不区分大小写)
// 十进制
var num1 = 10;


// 八进制的56
var num2 = 070;


// 十进制,因为有数字超过了7,这里是79
var num3 = 079;


// 十六进制的31
var num4 = 0x1f;

浮点数

var num = 0.1 + 0.2;
var sum = '2.3' * 100;


console.log(num);
// 0.30000000000000000004


console.log(sum);
// 229.99999999999997

上面例子表达的就是 JavaScript 的浮点型数据在计算时容易丢失精度,这一点并不仅在 JavaScript 存在,建议处理这方面问题使用专用的数字处理类,比如 Java 里的 BigDecima 类来处理。

数字的范围

JavaScript 中数值的范围是有效位数的,基本上够我们使用,我们仅需要知道以下几个知识点:

  • Number.MIN_VALUENumber.NEGATIVE_INFINITY:表示 JavaScript 中的最小值
  • Number.MAX_VALUENumber.POSITIVE_INFINITY:表示 JavaScript 中的最大值
  • Infinity:表示无穷大
  • -Infinity:表示无穷小

NaN

NaN (Not a number)的含义是本该返回数值的操作未返回数值,返回了 NaN 就不会抛出异常影响语句流畅性。

NaN 属性的初始值就是 NaN,和 Number.NaN 的值一样。

在现代浏览器中(ES5 环境), NaN 属性是一个不可配置(non-configurable)、不可写(non-writable)的属性。但在 ES3 中,这个属性的值是可以被更改的,但是也应该避免覆盖。

编码中很少直接使用到 NaN。通常都是在计算失败时,作为 Math 的某个方法的返回值出现的(例如:Math.sqrt(-1))或者尝试将一个字符串解析成数字但失败了的时候(例如:parseInt("blabla"))。

字符串

JavaScript 的字符串类型用于表示文本数据。它是一组 16 位的无符号整数值的元素。在字符串中的每个元素占据了字符串的位置。第一个元素的索引为 0,下一个是索引 1,依此类推。字符串的长度是它的元素的数量。

'foo';
'bar';
'1234';
'one line n another line';
"John's cat";

符号

符号(Symbols)是 ECMAScript 第 6 版新定义的。该类型的性质在于这个类型的值可以用来创建匿名的对象属性。该数据类型通常被用作一个对象属性的键值,当这个属性是用于类或对象类型的内部使用的时候。

var myPrivateMethod = Symbol();


this[myPrivateMethod] = function () {
  // ...
};

引用数据类型

引用类型通常叫做类(Class),也就是说,遇到引用值,所处理的就是对象。

在 ECMA-262 标准中根本没有出现 这个词,而是定义了 对象定义,逻辑上等价于其他程序设计语言中的类。

对象是由 new 运算符加上要实例化的对象的名字创建的。

例如,下面的代码创建 Object 对象的实例:

var o = new Object();

这种语法与 Java 语言的相似,不过当有不止一个参数时,ECMAScript 要求使用括号。

如果没有参数,如以下代码所示,括号可以省略:

var o = new Object();

尽管括号不是必需的,但是为了避免混乱,最好使用括号。

类型检测

类型检测的方法:

  1. typeof
  2. instanceof
  3. Object.prototype.toString
  4. constructor

typeof

typeof 操作符返回一个字符串,表示未经计算的操作数的类型。

typeof undefined;
// "undefined"


typeof null;
// "object"


typeof 100;
// "number"


typeof NaN;
// "number"


typeof true;
// "boolean"


typeof 'foo';
// "string"


typeof function () {};
// "function"


typeof [1, 2];
// "object"


typeof new Object();
// "object"

typeof 操作符适合对 基本类型(除 null 之外)及 function 的检测使用,而对引用数据类型(如 Array)等不适合使用。

更详细信息请查阅 typeof 操作符

instanceof

instanceof 运算符用于检测一个对象在其 原型链 中是否存在一个构造函数的 prototype 属性。

左操作数为对象,不是就返回 false,右操作数必须是 函数对象 或者 函数构造器,不是就返回 TypeError 异常。

obj instanceof constr;
function Person() {}
function Student() {}
Student.prototype = new Person();
Student.prototype.constructor = Student;


const ben = new Student();
ben instanceof Student;
// true


const one = new Person();
one instanceof Person;
// true
one instanceof Student;
// false
ben instanceof Person;
// true

任何一个构造函数都有一个 prototype 对象属性,这个对象属性将用作 new 实例化对象的原型对象。

? instanceof 适合用于判断对象是否属于 Array、Date 和 RegExp 等内置对象。

? 不同 window 或 iframe 之间的对象类型检测无法使用 instanceof 检测。

更详细信息请查阅 instanceof

Object.prototype.toString

可以通过 toString() 来获取每个对象的类型。

为了 每个对象 都能通过 Object.prototype.toString 来检测,需要以 Function.prototype.call 或者 Function.prototype.apply 的形式来调用,传递要检查的对象作为第一个参数。

Obejct.prototype.toString.call(undefined)//  "[object Undefined]"


Obejct.prototype.toString.call(null)//  "[object Null]"


Obejct.prototype.toString.call(true)//  "[object Boolean]"


Obejct.prototype.toString.call('')/// "[object String]"


Obejct.prototype.toString.call(123)//  "[object Number]"


Obejct.prototype.toString.call([])//  "[object Array]"


Obejct.prototype.toString.call({})//  "[object Object]"

? 使用 Object.prototype.toString 方法能精准地判断出值的数据类型。

⚠️ 注意事项

  • 方法重写Object.prototype.toString 属于 Object 的原型方法,而 Array 或 Function 等类型作为 Object 的实例,都重写了 toString 方法。因此,不同对象类型调用 toString 方法时,调用的是重写后的 toString 方法,而非 Object 上原型 toString 方法,所以采用 xxx.toString() 不能得到其对象类型,只能将 xxx 转换成字符串类型。

constructor

任何对象都有 constructor 属性,继承自原型对象,constructor 会指向构造这个对象的构造器或构造函数。

Student.prototype.constructor === Student;
//  true

数组检测

ECMAScript5 将 Array.isArray() 正式引入 JavaScript,该方法能准确检测一个变量是否为数组类型。

Array.isArray(variable);

4. 表达式

字面量

字面量(Literal),又名直接量,即程序中直接使用的数据值。

// Null 字面量
const n = null;

// Undefined 字面量
const u = undefined;

// Boolean 布尔值字面量
const b1 = true;
const b2 = false;

// Number 数值字面量
const num = 1;
const nan = NaN;

// String 字符串字面量
const hello = 'hello';
const world = 'world';

// Regexp 正则字面量
const reg = /pattern/;

// Template Literal 模版字面量
const temp = `hello, ${world}`

数组初始化表达式

数组初始化表达式 是通过一对方括号和其内由逗号隔开的列表构成的。初始化的结果是一个新创建的数组。

逗号分隔

数组的元素是 逗号分隔 的表达式的值。

空数组:[] 内留空即表示该数组没有任何元素

拥有两个元素的数组:第一个是 3,第二个是 7。

[1 + 2, 3 + 4]

嵌套数组

数组初始化表达式中的元素初始化表达式也可以是数组的初始化表达式。也就是说,这些表达式是可以嵌套的。

var matrix = [[1, 2, 3],[4, 5, 6],[7, 8, 9]]

可省略元素

数组直接量中的列表逗号之间的元素可以省略,这时省略的空位会填充值不存在。

var sparseArray = [1, , , , 5]


// 相当于
// [1, empty x 3, 5]

数组直接量的元素列表结尾处可以留下单个逗号,这时并不会创建一个新的值为 undefined 的值。

const arr = [1, 2, 3, ]


console.log(arr)
// [1, 2, 3]

索引赋值

通过数组索引赋值,只会给指定索引所在位置赋值,而其中未赋值的索引位置则表示为空 empty,而非定义为 undefined.

let arr = [0, 1]


arr[10] = 10


console.log(arr);
// [0, 1, empty x 8, 10]


console.log(arr.length);
// 11


ary.filter(x => x === undefined);
// []

对象初始化表达式

对象和数组初始化表达式实际上是一个新创建的对象和数组。这些初始化表达式有时称作 对象直接量数组直接量。然而和布尔值直接量不同,它们实际上不是原始表达式,因为它们所包含的成员或者元素都是子表达式。

对象初始化表达式和数组初始化表达式非常相似,只是方括号被花括号代替,并且每个子表达式都包含一个属性名和一个冒号作为前缀。

// 一个拥有两个属性成员的对象
var p = {
  x: 2.3,
  y: -1.2,
};


// 一个空对象
var q = {};


// q 的属性成员和 p 的一样
q.x = 2.3;
q.y = -1.2;

对象直接量也可以嵌套。

var rectangle = {
  upperLeft: { x: 2, y: 2 },
  lowRight: { x: 4, y: 5 },
};

JavaScript 求对象初始化表达式的值的时候,对象表达式也都会各自计算一次,并且它们不必包含常数值:它们可以是任意 JavaScript 表达式。

同样,对喜爱那个直接量中的属性名称可以是字符串而不是标识符(这在那些只能使用保留字或一些非法标识符作为属性名的地方非常有用)

var side = 1;
var square = {
  upperLeft: {
    x: p.x,
    y: p.y,
  },
  lowerRight: {
    x: p.x + side,
    y: p.y + side,
  },
};

属性访问器

属性访问表达式运算得到一个对象属性或一个数组元素的值。

JavaScript 为属性访问定义了两种语法。

语法

  • 第一种写法是一个表达式后跟随一个句点和标识符。表达式指定对象,标识符则指定需要访问的属性的名称。
expression.identifiler
  • 第二种写法是使用方括号,方括号内是另一个表达式(这种方法适用于对象和数组)。第二个表达式指定要访问的属性的名称或代表要访问数组元素的索引。
expression[expression]

不管使用哪种形式的属性访问器,在句点和左方括号之前的表达式总是会首先计算。

  • 如果计算结果是 nullundefined,表达式会抛出一个类型错误异常,因为这两个值都不能包含任意属性
  • 如果计算结果不是对象,JavaScript 会将其转换为对象
  • 如果对象表达式后跟随句点和标识符,则会查找由这个标识符指定的属性值,并将其作为整个表达式的值返回
  • 如果对象表达式后跟随一对方括号,则会计算方括号内的表达式的值并将其转换为字符串

不论哪种情况,如果命名的属性不存在,那么整个属性访问表达式的值就是 undefined

示例

var a = {x: 1, y: {z: 3}};
// 一个示例对象


var b = [a, 4, [5, 6]];
// 一个包含这个对象的示例数组


console.log(a.x);
// 1


console.log(a.y.z);
// 3


console.log(a["x"]);
// 1


console.log(b[1]);
// 4


console.log(b[2]["1"]);
// 6


console.log(b[0].x);
// 1

5. 运算符

in

in 运算符用于判断属性是否存在于对象中。

语法

key in obj;

参数

参数 说明
key 一个字符串类型或者 Symbol 类型的属性名或者数组索引(非 Symbol 类型将会强制转为字符串)。
object 检查(或其原型链)是否包含具有指定名称的属性的对象。

示例

var cars = new Array('Toyota', 'Nissan', 'Mercedes', 'Buick', 'Porsche');
0 in cars;
// true

1 in cars;
// true

'PI' in Math;
//  true

var myCar = { make: 'Honda', model: 'Accord', year: '1998' };
'make' in myCar;
'model' in myCar;

instanceof

instanceof 运算符用于测试构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。

代码示例:

target instanceof constructor;

检测类型

instanceof 可以检测某个对象是否是另一个对象的 实例

const Person = function () {};
const student = new Person();


console.log(student instanceof Person);
// true

instanceof 可以检测父类型。

function Person() {}
function Student() {}


const p = new Person();


// 继承原型
Student.prototype = p;


const s = new Student();


console.log(s instanceof Student);
// true
console.log(s instanceof Person);
// true

typeof

typeof 操作符返回一个字符串,表示未经计算的操作数的类型。

语法

typeof operand;

参数

参数 说明
perand 是一个表达式,表示对象或原始值,其类型将被返回

返回值

下表总结了 typeof 可能的返回值。

有关类型和原始值的更多信息,可查看 JavaScript 数据结构 页面。

类型 结果
Undefined 'undefined'
Null 'object'
Boolean 'boolean'
Number 'number'
String 'string'
Symbol 'symbol'
宿主对象 Implementation-dependent
函数对象 'function'
任何其他对象 'object'

算术运算符

算术运算符使用数值(字面量或者变量)作为操作数进行运算并返回一个数值。

标准的算术运算符就是加减乘除 +-*/

当操作数是浮点数时,这些运算符表现得跟它们在大多数编程语言中一样(特殊要注意的是,除零会产生 Infinity )。

运算符 描述 示例
+ 加法 1 + 1 = 2
- 减法 2 - 1 = 1
* 乘法 3 * 3 = 9
/ 除法 10 / 5 = 2
% 求余,返回相除后余值 12 % 5 = 2
++ 自增(更新运算符),分为前自增和后自增 具体参考 更新运算符
-- 自减(更新运算符),分为前自减和后自减 具体参考 更新运算符
- 一元负值符,返回操作数的负值 -foo
+ 一元正值符,若操作数在操作前非数字类型,将试图将其转换成数字类型 +foo
** 指数运算符,计算 base(底数) 的 exponent(指数)次方 2 ** 3 = 8

实践示例

console.log(-9 % 2);
// -1


console.log(1 + -+(+(+-+1)));
// 2

实现指数运算符

function calculateExponent(base, exponent) {
  if (exponent === 1) {
    return base;
  } else {
    return base * calculateExponent(base, exponent - 1);
  }
}

更新表达式

更新表达式包括 前自增/自减运算符后自增/自减运算符

示例

前自增

前自增:先自增,再赋值

let n = 10;


// 前自增
const res = ++n;


console.log(n);
// 11
console.log(res);
// 11

前自减

前自减:先自减,再赋值

let n = 10;


// 前自减
const res = --n;


console.log(res);
// 9
console.log(n);
// 9

后自增

后自增:先赋值,再自增

let n = 10;


// 后自增
const res = n++;


console.log(res);
// 10
console.log(n);
// 11

后自减

后自减:先赋值,再自减

let n = 10;


// 后自减
const res = n--;


console.log(res);
// 10
console.log(n);
// 9

赋值运算符

一个 赋值运算符(assignment operator)将它右边操作数的值赋给它左边的操作数。

下列为 ECMAScript 标准规范的 Assignment Operator:

* = /= %= += -= <<= >>= >>>= &= ^= |= **=
运算名称 简写的操作符 分解含义 符号
赋值 x = y x = y =
加法赋值 x += y x = x + y +=
减法赋值 x -= y x = x - y -=
乘法赋值 x *= y x = x * y *=
除法赋值 x /= y x = x / y /=
求余赋值 x %= y x = x % y %=
求幂赋值 x ** y x = x ** y **
左移位赋值 x <<= y x = x << y <<=
右移位赋值 x >>= y x = x >> y >>=
无符号右移位赋值 x >>>= y x = x >>> y >>>=
按位与赋值 x & y x = x & y &
按位异赋值 x ^= y x = x ^ y ^=
按位或赋值 `x = y` `x = x

比较运算符

比较运算符比较它的操作数并返回一个基于表达式是否为 true 的逻辑值。

比较运算符分为关系运算符(Relational Operators)**和**等值运算符(Equality Operators)

  • 操作数可以是数字,字符串,逻辑,对象值。
  • 字符串比较是基于标准的字典顺序,使用 Unicode 值。
  • 在多数情况下,如果两个操作数不是相同的类型, JavaScript 会尝试转换它们为恰当的类型来比较。这种行为通常发生在数字作为操作数的比较。
  • 类型转换的例外是使用 ===!== 操作符,它们会执行严格的相等和不相等比较。这些运算符不会在检查相等之前转换操作数的类型。下面的表格描述了该示例代码中的各比较运算符。

关系运算符

运算符 描述 返回 true 的示例
大于 > 左边的操作数大于右边的操作数返回 true b > a
大于等于 >= 左边的操作数大于或等于右边的操作数返回 true b >= a a >= 1
小于 < 左边的操作数小于右边的操作数返回 true a < b 1 < 2
小于等于 <= 左边的操作数小于或等于右边的操作数返回 true a <= b b <= 5

等值运算符

运算符 描述 返回 true 的示例
等于 == 如果两边操作数相等时返回 true a == 1 '1' == 2 1 == '1'
不等于 != 如果两边操作数不相等时返回 true a != 2 b != '1'
全等 === 两边操作数相等且类型相同时返回 true a === 1
不全等 !== 两边操作数不相等或类型不同时返回 true a !== '1' 1 !== '1'

抽象相等比较算法

  1. 若 Type(x) 与 Type(y) 相同,则
    1. 若 Type(x) 为 Undefined,返回 true
    2. 若 Type(x) 为 Null,返回 true
    3. 若 Type(x) 为 Number,则
      1. 若 x 为 NaN,返回 false
      2. 若 y 为 NaN,返回 false
      3. 若 x 与 y 为相等数值,返回 true
      4. 若 x 为 +0 且 y 为 -0,返回 true
      5. 若 x 为 -0 且 y 为 +0,返回 true
      6. 返回 false
    4. 若 Type(x) 为 String
      1. 当 x 和 y 为完全相同的字符序列(长度相等且相同字符在相同位置)时返回 true
      2. 否则,返回 false
    5. 若 Type(x) 为 Boolean
      1. 当 x 和 y 为同为 true 或者同为 false 时返回 true
      2. 否则,返回 false
    6. 当 x 和 y 为引用用一对象时返回 true。否则,返回 false
  2. 若 x 为 null 且 y 为 undefined,返回 true
  3. 若 x 为 undefined 且 y 为 null,返回 true
  4. 若 Type(x) 为 Number 且 Type(y) 为 String,返回比较 x == ToNumber(y) 的结果
  5. 若 Type(x) 为 String 且 Type(y) 为 Number,返回比较 ToNumber(x) == y 的结果
  6. 若 Type(x) 为 Boolean,返回比较 ToNumber(x) == y 的结果
  7. 若 Type(y) 为 Boolean,返回比较 x == ToNumber(y) 的结果
  8. 若 Type(x) 为 String 或 Number,且 Type(y) 为 Object,返回比较 x == ToPrimitive(y) 的结果
  9. 若 Type(x) 为 Object 且 Type(y) 为 String 或 Number,返回比较 ToPrimitive(x) == y 的结果
  10. 返回 false

按以上相等之定义:

  • 字符串比较可以按这种方式强制执行:'' + a == '' + b
  • 数值比较可以按这种方式强制执行:+a == +b
  • 布尔值比较可以按这种方式强制执行:!a == !b

等值比较操作保证以下不变:

  • A !== B 等价于 !(A == B)
  • A == B 等价于 B == A,除了 A 与 B 的执行顺序。

相等运算符不总是传递的。例如,两个不同的 String 对象,都表示相同的字符串值;== 运算符认为每个 String 对象都与字符串值相等,但是两个字符串对象互不相等。

  • new String('a') == 'a''a' == new String('a') 皆为 true
  • new String('a') == new String('a')false

字符串比较使用的方式是简单地检测字符编码单元序列是否相同。不会做更复杂的、基于语义的字符或者字符串相等的定义以及 Unicode 规范中定义的 Collating Order。所以 Unicode 标准中认为相等的 String 值可能被检测为不等。实际上这一算法认为两个字符串已经是经过规范化的形式。

引用数据类型间比较

const a = function() {};
const b = function() {};


console.log(a === b);
// false


console.log([] === []);
// false


console.log({} === {});
// false

当我们访问引用数据类型(对象、数组、函数等等)的值时,首先从栈中获得该对象的 地址指针,然后再从 堆内存 中取得所需的数据。

变量 a 实际保存的是指向堆内存中对象的一个指针,而变量 b 保存的是指向堆内存中的另一个对象的指针,虽然这两个对象的值时一样的,但它们是独立的两个对象,占了两份内存空间,所以它们互不相等。

而当将一个为引用数据类型的值的变量赋值给另一个变量时,即拷贝了前者的内存空间的地址指针,因此它们都指向堆内存中同一个对象。

let x = {}


let y = x


console.log(x === y)
// true

条件运算符

**条件运算符(Conditional Operator)*是 JavaScript 中唯一的一个*三元运算符(三个操作数),有时直接称做三元运算符。

variable = boolean_expression ? true_value : false_value;

本质上,这就是基于对 boolean_expression 求值的结果,决定给变量 variable 赋什么值。如果求值结果是 true ,则给变量 variable 赋值true_value;如果求值结果是 false,则给变量 variable 赋值false_value

逻辑运算符

逻辑运算符常用于对操作数进行布尔运算,经常和关系运算符一样配合使用。逻辑运算符将多个关系表达式组合起来组成一个更复杂的表达式。逻辑运算符分为逻辑与 && 、逻辑或 || 、逻辑非 ! 三种。

逻辑与

逻辑与运算符 由两个和号 && 表示,有两个操作数,只有在两个操作数都为 true 时,结果才返回 true,否则返回 false

逻辑与的真值表

第一个操作数 第二个操作数 结果
true true true
true false false
false true false
false false false

逻辑或

逻辑与运算符 由两个和号 && 表示,有两个操作数,只有在两个操作数都为 true 时,结果才返回 true,否则返回 false

逻辑与的真值表

第一个操作数 第二个操作数 结果
true true true
true false false
false true false
false false false

逻辑非

逻辑非操作符由一个叹号( ! )表示,可以应用于 ECMAScript 中的任何值。无论这个值是什么数据类型,这个操作符都会返回一个布尔值。逻辑非操作符首先会将它的操作数转换成一个布尔值,然后再对其求反。

console.log(!null);
// t null


console.log(!undefined);
// t NaN


console.log(!0);
// t 数值0


console.log(!NaN);
// t NaN


console.log(!'');
// t 空字符串


console.log(!'123');
// f 非空字符串


console.log(!Infinity);
// f 任意非0数值(包括Infinity)


console.log(!{ a: 1 });
// f 对象

6. 语句

if 语句

条件语句用于基于不同的条件来执行不同的动作。

在 JavaScript 中,我们可使用以下条件语句:

  • if 语句 - 只有当指定条件为 true 时,使用该语句来执行代码
  • if…else 语句 - 当条件为 true 时执行代码,当条件为 false 时执行其他代码
  • if…else if…else 语句 - 使用该语句来选择多个代码块之一来执行
  • switch 语句 - 使用该语句来选择多个代码块之一来执行

当一个逻辑条件为真,用 if 语句执行一个语句。当这个条件为假,使用可选择的 else 从句来执行这个语句。

单层条件判断

if (condition) {
  statement_1;
}
[else {
  statement_2;
}] //推荐使用严格的语句块模式,语句else可选
参数 说明
condition 为任何返回结果(若非 boolean 类型会被 ECMAScrpt 转换)为 truefalse 的表达式。如果条件式为 truestatement1 会被执行;否则 statement2 会被执行
statement1(2) 为任意语句(代码块),甚至可以将另一个 if 语句嵌套七种

多层条件判断

if (condition_1) {
  statement_1;
} [else if (condition_2) {
  statement_2;
}]
...
[else if (condition_n_1) {
  statement_n_1;
}] [else {
  statement_n;
}]

要执行多个语句,可以使用语句块 ({ … }) 来分组这些语句。

for 语句

for 语句 也是一种前测试循环语句,但它具有在执行循环之前初始化变量和定义循环后要执行的代码的能力。

语法

for (initialization; expression; post - loop - expression) {
  // statement
}

参数:

  • initialization 初始化表达式:表达式通常会初始化一个或多个循环计数器(变量),但语法上是允许一个任意复杂度的表达式,通常为一条声明赋值语句(只在循环开始之前执行一次)。
  • expression 循环条件判断:执行循环语句前的判断语句(通常为比较表达式),若为 true 则执行循环语句,否则则不执行循环语句,并跳出循环语句。
  • post-loop-expression 计数器变量更新:循环执行语句执行后执行的计数器变量更新表达式,更新循环计数器(变量),以进入下一次循环条件判断。
  • statement 循环执行语句:当循环条件满足时所执行的语句,执行完毕后执行计数器变量更新语句(利用 breakcontinue 语句除外)。

最佳实践

代码示例

var count = 10;


for (let i = 0; i < count; i++) {
  console.log(i);
}

从尾部向前循环

位数的整倍循环

// 五位数的数字
const num = 99999;


for (let i = 1; i < num; i *= 10) {
  // 被除数 num
  // 除数
  const divisor = i * 10;
  // 整除部分
  const divided = Math.floor(num / divisor);
  // 余数
  const remainder = num % divisor;


  console.log(i, divisor);
  //    i       divisor
  // 1. 1       10
  // 2. 10      100
  // 3. 100     1000
  // 4. 1000    10000
  // 5. 10000   100000
}

涉及多个变量的循环

for (let i = 0, j = 10; i < 10; i++, j--) {
  sum += i * j;
}

若在循环中一次迭代改变多个变量,则必须使用到逗号运算符,它将初始化表达式和自增表达式合并入一个表达式中以用于 for 循环。

while 语句

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

语法

while (expression) statement;

参数

参数 描述
expression 条件表达式,在每次循环前被求值。如果求值为 truestatement就会被执行。如果求值为 false,则跳出 while 循环执行后面的语句。
statement 只要条件表达式求值为 true,该语句就会一直被执行。要在循环中执行多条语句,可以使用块语句({ ... })包住多条语句。

注意:使用 break 语句在 expression 计算结果为真之前停止循环。

示例

代码示例

var i = 0;
while (i < 10) {
  i += 2;
}
var cars = ['BMW', 'Volvo', 'Saab', 'Ford'];
var text = '';
var i = 0;
while (i < cars.length) {
  text += cars[i] + '<br>';
  i++;
}

switch 语句

switch 语句允许一个程序求一个表达式的值并且尝试去匹配表达式的值到一个 case 标签。如果匹配成功,这个程序执行相关的语句。

语法

switch (expression) {
   case value_1:
      statements_1
      [break;]
   case value_2:
      statements_2
      [break;]
   ...
   default:
      statements_def
      [break;]
}

工作原理:首先设置表达式 expression(通常是一个变量)。随后表达式的值会与结构中的每个 case 的值做比较。如果存在匹配,则与该 case 关联的代码块会被执行。请使用 break 来阻止代码自动地向下一个 case 运行。

参数

参数 说明
expression 用于比较的表达式
value_(n) expression 比较的值
statement(n) 执行语句

关键词

  • case:表示一种情况,如果 expression 等于 value ,就执行 statement
  • break:会使代码跳出 switch 语句,如果没有关键词 break,代码执行就会继续进入下一个 case
  • default:说明了表达式的结果不等于任何一种情况时的操作(事实上,它相对于 else 从句)。

示例

var myCar = 'Porsche'
switch (myCar) {
  case 'Nissan': alert("My car is Nissan");
    break;
  case 'Honda': alert("My car is Honda");
    break;
  case 'Porsche': alert("My car is Porsche");
    break;
  default: alert("I have no car");
}

continue 语句

continue 语句用于结束当前(或标签)的循环语句的本次迭代,并继续执行循环的下一次迭代。

示例

for 语句中使用 continue

var num = 0;
for (var i = 1; i < 10; i++) {
  if (i % 5 == 0) {
    continue;
  }
  num++;
}
console.log(num);
// 8

break 语句

break 语句用于立即退出最内层的循环或 switch 语句。

示例

var num = 0;
for (var i = 1; i < 10; i++) {
  if (i % 5 == 0) {
    break;
  }
  num++;
}
console.log(num); // 4

try-catch 语句

catch

catch 子句包含 try 块中抛出异常时要执行的语句。也就是,你想让try 语句中的执行操作成功,如果没成功,你想控制接下来发生的事情,这时你可以在 catch 语句中实现。

如果有在 try 块中有任何一个语句(或者从 try 块中调用的函数)抛出异常,控制立即转向 catch 子句。如果在 try 块中没有异常抛出,会跳过 catch 子句。

示例:

try {
  console.log('1: start');


  throw 'this is a error';


  console.log('2: end');
} catch (err) {
  console.log('3:', err);
}


// 输出顺序:
// 1:start
// 3:this is a error

catch 块指定一个标识符(在上面的示例中为 err),该标识符保存由 throw 语句指定的值。catch 块是唯一的,因为当输入catch 块时,JavaScript 会创建此标识符,并将其添加到当前作用域;标识符仅在 catch 块执行时存在;catch 块执行完成后,标识符不再可用。

从结果可以得知,如果在 try 块中任何一个语句(或者从 try 块中调用的和你熟)抛出异常,控制立即转向 catch 子句。

总结

在本篇博客中,我们一起探索了JavaScript的基本语法。从变量和数据类型的定义开始,你已经了解了如何在JavaScript中处理数字、字符串、布尔值等不同类型的数据。接着,我们介绍了JavaScript中的运算符和表达式,使你能够进行数值计算和逻辑判断。条件语句和循环结构让你学会了根据不同的情况执行不同的代码,以及重复执行一段代码块。
JavaScript是一门非常强大且广泛应用的编程语言。掌握了这些基本的语法和概念,你已经具备了入门JavaScript编程的基础。未来,你将能够创建更加交互性的网页,实现更多惊艳的动态效果,甚至搭建出属于自己的Web应用。但这只是一个开始,还有许多更深入的主题等待你去探索。