【精读笔记】JavaScript高级程序设计 第3章 语言基础

本文详细介绍了JavaScript的基础知识,包括语言的基本构成、数据类型、操作符、语句和函数等内容。通过阅读本文,读者可以全面了解ECMAScript标准下JavaScript的核心概念和技术细节。

在这里插入图片描述

第三章 语言基础

​ ECMA-262以一个名为ECMAScript的伪语言的形式,定义了JavaScript在最基本的层面上如何工作,涉及语法、操作符、数据类型以及内置功能,在此基础之上才可以构建复杂的解决方案。

3.1 语法

3.1.1 区分大小写

ECMAScript中一切都区分大小写。无论是变量、函数名还是操作符,都区分大小写。

3.1.2 标识符

ECMAScript标识符使用驼峰大小写形式,即第一个单词的首字母小写,后面每个单词的首字母大写。

firstSecond
myCar
doSomethingImportant

3.1.3 注释

//单行注释

/*这是
多行
注释*/

3.1.4 严格模式

​ **ECMAScript 5增加了严格模式(strict mode)的概念。**严格模式是一种不同的JavaScript解析和执行模型,ECMAScript 3的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。要对整个脚本启用严格模式,在脚本开头加上这一行:

"use strict";

​ 虽然看起来像个没有赋值给任何变量的字符串,但它其实是一个预处理指令也可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头即可:

function doSomething() {
  "use strict";
  //函数体
}

3.1.5 语句

​ ECMAScript中的语句以分号结尾。

3.2 关键字与保留字

​ 按照规定,保留的关键字不能用作标识符或属性名。规范中也描述了一组未来的保留字,同样不能用作标识符或属性名。虽然保留字在语言中没有特定用途,但它们是保留给将来做关键字用的。

3.3 变量

ECMAScript变量是松散类型的意思是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符**。有3个关键字可以声明变量:var、const和let。其中,var在ECMAScript的所有版本中都可以使用,而const和let只能在ECMAScript 6及更晚的版本中使用

3.3.1 var关键字

var messgae;

​ 这行代码定义了一个名为message的变量,可以用它保存任何类型的值。(不初始化的情况下,变量会保存一个特殊值undefined)ECMAScript实现变量初始化,因此可以同时定义变量并设置它的值:

var message = "hi";
  1. var声明作用域

    使用var操作符定义的变量会成为包含它的函数的局部变量。比如,使用var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁。不过,在函数内定义变量时省略var操作符,可以创建一个全局变量。虽然可以通过省略var操作符定义全局变量,但不推荐这么做。在局部作用域中定义的全局变量很难维护,也会造成困惑。这是因为不能一下子断定省略var是不是有意而为之。在严格模式下,如果像这样未给声明的变量赋值,则会导致抛出Re-ferenceError.

    定义多个变量,可以再一条语句中用逗号分隔每个变量

    var message = "hi",
        found = false,
        age = 29;
    
  2. var声明提升

    使用var时,下面的代码不会报错,这是因为使用这个关键字声明的变量会自动提升到函数作用域顶部

    function foo() {
      console.log(age);
      var age = 26;
    }
    foo();  // undefined
    

    之所以不会报错,是因为ECMAScript运行时把它看成等价于如下代码:

    function foo() {
      var age;  //这就是所谓的提升,也就是把所有变量声明都拉到函数作用域的顶部。
      console.log(age);
      age = 26;
    }
    foo();  // undefined
    

3.3.2 let声明

​ let跟var的作用差不多,但有着非常重要的区别。最明显的区别是,let声明的范围是块作用域,而var声明的范围是函数作用域。

if (true) {
  var name = 'Matt';
  console.log(name); // Matt
}
console.log(name);   // Matt

if (true) {
  let age = 26;
  console.log(age);   // 26
}
console.log(age);     // ReferenceError: age没有定义

age变量之所以不能在if块外部被引用,是因为它的作用域仅限于该块内部。块作用域是函数作用域的子集,因此适用于var的作用域限制同样也适用于let。

​ let也不允许同一个块作用域中出现冗余声明。这样会导致报错。

​ 对声明冗余报错不会因混用let和var而受影响。这两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在。

  1. 暂时性死区

    let与var的另一个重要的区别,就是let声明的变量不会在作用域中被提升。

    // name会被提升
    console.log(name); // undefined
    var name = 'Matt';
    
    // age不会被提升
    console.log(age); // ReferenceError:age没有定义
    let age = 26;
    

    在let声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出ReferenceError。

  2. 全局声明

    与var关键字不同,使用let在全局作用域中声明的变量不会成为window对象的属性(var声明的变量则会)。

    var name = 'Matt';
    console.log(window.name); // 'Matt'
    
    let age = 26;
    console.log(window.age);  // undefined
    
  3. 条件声明

    在使用var声明变量时,由于声明会被提升,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明。因为let的作用域是块,所以不可能检查前面是否已经使用let声明过同名变量,同时也就不可能在没有声明的情况下声明它

    <script>
      var name = 'Nicholas';
      let age = 26;
    </script>
    
    <script>
      //假设脚本不确定页面中是否已经声明了同名变量
      //那它可以假设还没有声明过
    
      var name = 'Matt';
      //这里没问题,因为可以被作为一个提升声明来处理
      //不需要检查之前是否声明过同名变量
      let age = 36;
      //如果age之前声明过,这里会报错
      </script>
    

    使用try/catch语句或typeof操作符也不能解决,因为条件块中let声明的作用域仅限于该块。

    <script>
      let name = 'Nicholas';
      let age = 36;
    </script>
    
    <script>
      //假设脚本不确定页面中是否已经声明了同名变量
      //那它可以假设还没有声明过
    
      if (typeof name === 'undefined') {
        let name;
      }
      // name被限制在if {}块的作用域内
      //因此这个赋值形同全局赋值
      name = 'Matt';    //z这个赋值,没有var let,所以是全局赋值
      try (age) {
        //如果age没有声明过,则会报错
      }
      catch(error) {
        let age;
      }
      // age被限制在catch {}块的作用域内
      //因此这个赋值形同全局赋值
      age = 26;
      </script>
    

    为此,对于let这个新的ES6声明关键字,不能依赖条件声明模式。(因为很可能就不知不觉变成了全局声明)。

  4. for循环中的let声明

    在let出现之前,for循环定义的迭代变量会渗透到循环体外部:

    for (var i = 0; i < 5; ++i) {
      //循环逻辑
    }
    console.log(i); // 5
    

    改成使用let之后,这个问题就消失了,因为迭代变量的作用域仅限于for循环块内部

    for (let i = 0; i < 5; ++i) {
      //循环逻辑
    }
    console.log(i); // ReferenceError: i没有定义
    

3.3.3 const声明

​ const的行为与let基本相同,唯一一个重要的区别是==用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误==。

const age = 26;
age = 36; // TypeError:给常量赋值
// const也不允许重复声明
const name = 'Matt';
const name = 'Nicholas'; // SyntaxError

// const声明的作用域也是块
const name = 'Matt';
if (true) {
  const name = 'Nicholas';
}
console.log(name); // Matt

const声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制

const person = {};
person.name = 'Matt';  // ok

​ JavaScript引擎会为for循环中的let声明分别创建独立的变量实例,虽然const变量跟let变量很相似,但是不能用const来声明迭代变量(因为迭代变量会自增):

for (const i = 0; i < 10; ++i) {} // TypeError:给常量赋值

3.3.4 声明风格及最佳实践

​ **ECMAScript 6增加let和const从客观上为这门语言更精确地声明作用域和语义提供了更好的支持。**行为怪异的var所造成的各种问题,已经让JavaScript社区为之苦恼了很多年。随着这两个新关键字的出现,新的有助于提升代码质量的最佳实践也逐渐显现。

  1. 不使用var

    有了let和const,大多数开发者会发现自己不再需要var了。限制自己只使用let和const有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值

  2. const优先,let次之

    使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用const来声明变量,只在提前知道未来会有修改时,再使用let。这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为

3.4 数据类型

​ ECMAScript有6种简单数据类型(也称为原始类型):

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Symbol(新增)

​ Symbol(符号)是ECMAScript 6新增的。还有一种复杂数据类型叫Object(对象)。Object是一种无序名值对的集合。因为在ECMAScript中不能定义自己的数据类型,所有值都可以用上述7种数据类型之一来表示。

3.4.1 typeof操作符

​ 因为ECMAScript的类型系统是松散的,所以需要一种手段来确定任意变量的数据类型。typeof操作符就是为此而生的。对一个值使用typeof操作符会返回下列字符串之一:

  • "undefined"表示值未定义;
  • "boolean"表示值为布尔值;
  • "string"表示值为字符串;
  • "number"表示值为数值;
  • "object"表示值为对象(而不是函数)或null;
  • "function"表示值为函数;
  • "symbol"表示值为符号。
let message = "some string";
console.log(typeof message);    // "string"
console.log(typeof(message));   // "string"
console.log(typeof 95);         // "number"

​ 因为typeof是一个操作符而不是函数,所以不需要参数(但可以使用参数)

注意:调用typeof null返回的是"object"。这是因为特殊值null被认为是一个对空对象的引用。

3.4.2 Undefined类型

​ Undefined类型只有一个值,就是特殊值undefined。当使用var或let声明了变量但没有初始化时,就相当于给变量赋予了undefined值

let message;
console.log(message == undefined); // true

注意:控制台输出未定义的值是直接报错,控制台输出声明但未赋值的变量是undefined

let message;    //这个变量被声明了,只是值为undefined

//确保没有声明过这个变量
// let age

console.log(message); // "undefined"
console.log(age);     //报错

但是,无论是声明还是未声明,typeof返回的都是字符串“undefine"。

let message; //这个变量被声明了,只是值为undefined

//确保没有声明过这个变量
// let age

console.log(typeof message); // "undefined"
console.log(typeof age);     // "undefined"

3.4.3 Null类型

​ Null类型同样只有一个值,即特殊值null。逻辑上讲,null值表示一个空对象指针,这也是给typeof传一个null会返回"object"的原因:

let car = null;
console.log(typeof car);  // "object"

在定义将来要保存对象值的变量时,建议使用null来初始化,不要使用其他值。这样,只要检查这个变量的值是不是null就可以知道这个变量是否在后来被重新赋予了一个对象的引用

​ undefined值是由null值派生而来的,因此ECMA-262将它们定义为表面上相等,如下面的例子所示:

console.log(null == undefined);  // true

3.4.4 Boolean类型

​ Boolean(布尔值)类型是ECMAScript中使用最频繁的类型之一,有两个字面值:true和false。这两个布尔值不同于数值,因此true不等于1,false不等于0

所有其他ECMAScript类型的值都有相应布尔值的等价形式。要将一个其他类型的值转换为布尔值,可以调用特定的Boolean()转型函数(Boolean()转型函数可以在任意类型的数据上调用,而且始终返回一个布尔值。):

let message = "Hello world!";
let messageAsBoolean = Boolean(message);

3.4.5 Number类型

​ Number类型使用IEEE 754格式表示整数和浮点值(在某些语言中也叫双精度值)。不同的数值类型相应地也有不同的数值字面量格式。

注意:八进制字面量在严格模式下是无效的,会导致JavaScript引擎抛出语法错误。

NaN

​ 意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。

  • 任何涉及NaN的操作始终返回NaN(如NaN/10)
  • NaN不等于包括NaN在内的任何值。例如,下面的比较操作会返回false:console.log(NaN == NaN); // false

ECMAScript提供了isNaN()函数。该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。把一个值传给isNaN()后,该函数会尝试把它转换为数值。某些非数值的值可以直接转换成数值,如字符串"10"或布尔值。任何不能转换为数值的值都会导致这个函数返回true。

数值转换

​ 有3个函数可以将非数值转换为数值Number()、parseInt()和parseFloat()Number()是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值

​ Number()函数基于如下规则执行转换

  • 布尔值,true转换为1,false转换为0。

  • 数值,直接返回。

  • null,返回0.

  • undefined,返回NaN。

  • 字符串

    • 十六进制转换为对应的十进制
    • 空字符串,返回0
  • 对象,调用valueOf()方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString方法,再按照转换字符串的规则转换。

  • let num1 = Number("Hello world!");  // NaN
    let num2 = Number("");              // 0
    let num3 = Number("000011");        // 11
    let num4 = Number(true);            // 1
    

    parseInt()函数

​ parseInt()函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换如果第一个字符不是数值字符、加号或减号,parseInt()立即返回NaN。这意味着空字符串也会返回NaN(这一点跟Number()不一样,它返回0)。

let num1 = parseInt("1234blue");  // 1234
let num2 = parseInt("");          // NaN
let num3 = parseInt("0xA");       // 10,解释为十六进制整数
let num4 = parseInt(22.5);        // 22
let num5 = parseInt("70");        // 70,解释为十进制值
let num6 = parseInt("0xf");       // 15,解释为十六进制整数
parseInt()函数**也可以接受第二个参数,用于指定底数(进制数)**。
let num = parseInt("0xAF", 16); // 175
//如果提供了十六进制参数,那么字符串前面的"0x"可以省掉:
let num1 = parseInt("AF", 16);  // 175
let num2 = parseInt("AF");      // NaN,没有传第二个参数,也没有加前缀0x,所以第一个检测到的字符就是非数值字符,随即自动停止并返回NaN。
let num1 = parseInt("10", 2);   // 2,按二进制解析
let num2 = parseInt("10", 8);   // 8,按八进制解析
let num3 = parseInt("10", 10);  // 10,按十进制解析
let num4 = parseInt("10", 16);  // 16,按十六进制解析

parseFloat()函数

​ 工作方式跟parseInt()函数类似,都是从位置0开始检测每个字符。同样,它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略

​ parseFloat()函数的另一个不同之处在于,它始终忽略字符串开头的零。这个函数能识别前面讨论的所有浮点格式,以及十进制格式(开头的零始终被忽略)。十六进制数值始终会返回0。因为parseFloat()只解析十进制值,因此不能指定底数如果字符串表示整数(没有小数点或者小数点后面只有一个零),则parseFloat()返回整数

let num1 = parseFloat("1234blue");  // 1234,按整数解析
let num2 = parseFloat("0xA");       // 0
let num3 = parseFloat("22.5");      // 22.5
let num4 = parseFloat("22.34.5");   // 22.34
let num5 = parseFloat("0908.5");    // 908.5
let num6 = parseFloat("3.125e7");   // 31250000

3.4.6 String类型

​ 字符串可以使用双引号(")、单引号(’)或反引号(`)标示。转义序列表示一个字符,所以只算一个字符。字符串的长度可以通过其length属性获取。

  1. 字符串的特点

​ ECMAScript中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量。如

let lang = "Java";  //变量lang一开始包含字符串"java"
lang = lang + "Script";  
//首先会分配一个足够容纳10个字符的空间,然后填充上Java和Scripy。最后销毁原始字符串Java和Script。
//之所以一些早期浏览器在拼接字符串非常慢
  1. 转换为字符串

    • toString()方法。几乎所有值都有的toString()方法。这个方法唯一的用途就是返回当前值的字符串等价物。
    let age = 11;
    let ageAsString = age.toString();      //字符串"11"
    let found = true;
    let foundAsString = found.toString();  //字符串"true"
    

    ​ toString()方法可见于数值、布尔值、对象和字符串值。(没错,字符串值也有toString()方法,该方法只是简单地返回自身的一个副本。)null和undefined值没有toString()方法

    ​ 多数情况,toString()不接受任何参数。不过,在对数值调用这个方法时,**toString()可以接收一个底数参数,即以什么底数来输出数值的字符串表示。**通过传入参数,可以得到数值的二进制、八进制、十六进制,或者其他任何有效基数的字符串表示。

    let 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"
    
    • String()转型函数。如果你不确定一个值是不是null或undefined,可以使用此方法,它始终会返回表示相应类型值的字符串。String()函数遵循如下规则。
      • 如果值有toString()方法,则调用该方法(不传参数)并返回结果。
      • 如果值是null,返回"null"
      • 如果值是undefined,返回"undefined"。
  2. 模板字面量

    ​ ECMAScript 6新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串它用反引号(`)标识

    let myMultiLineString = 'first line\nsecond line';
    let myMultiLineTemplateLiteral = `first line
    second line`;
    console.log(myMultiLineString);
    // first line
    // second line"
    
    console.log(myMultiLineTemplateLiteral);
    // first line
    // second line
    
    console.log(myMultiLineString === myMultiLinetemplateLiteral); // t
    

    ​ 由于模板字面量会保持反引号内部的空格,因此在使用时要格外注意。格式正确的模板字符串可能会看起来缩进不当。

  3. 字符串插值

    ​ 模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。技术上讲,模板字面量不是字符串,而是一种特殊的JavaScript句法表达式,只不过求值后得到的是字符串,变量也会从它们最接近的作用域中取值。

    字符串插值通过在${}中使用一个JavaScript表达式实现

    let value = 5;
    let exponent = 'second';
    //以前,字符串插值是这样实现的:
    let interpolatedString =
      value + ' to the ' + exponent + ' power is ' + (value * value);
    
    //现在,可以用模板字面量这样实现:
    let interpolatedTemplateLiteral =
      `${ value } to the ${ exponent } power is ${ value * value }`;
    
    console.log(interpolatedString);           // 5 to the second power is 25
    console.log(interpolatedTemplateLiteral);  // 5 to the second power is 2
    

    所有插入的值都会使用toString()强制转型为字符串,而且任何JavaScript表达式都可以用于插值。嵌套的模板字符串无须转义。

  4. 模板字面量标签函数

    ​ 模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果。

  5. 原始字符串

    ​ 使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或Unicode字符)。为此,可以使用String.raw标签函数

    console.log(`\u00A9`);            // ©
    console.log(String.raw`\u00A9`);  // \u00A9
    
    //换行符示例
    console.log(`first line\nsecond line`);
    // first line
    // second line
    
    console.log(String.raw`first line\nsecond line`); // "first line\nsecond line"
    
    

    ​ 也可以通过标签函数的第一个参数,即字符串数组的.raw属性,取得每个字符串的原始内容:

    这里没太看明白
    

3.4.7 Symbol类型

​ Symbol(符号)是ECMAScript 6新增的数据类型符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。

  1. 符号的基本用法

    ​ 符号需要使用Symbol()函数初始化。因为符号本身是原始类型,所以typeof操作符对符号返回symbol

    let sym = Symbol();
    console.log(typeof sym); // symbol
    

    ​ 调用Symbol()函数时,也可以传入一个字符串参数作为对符号的描述(description),将来可以通过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关

    let genericSymbol = Symbol();
    let otherGenericSymbol = Symbol();
    
    let fooSymbol = Symbol('foo');
    let otherFooSymbol = Symbol('foo');
    
    console.log(genericSymbol == otherGenericSymbol);  // false
    console.log(fooSymbol == otherFooSymbol);          // false
    

    符号没有字面量语法,这也是它们发挥作用的关键。按照规范,你只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。

    ​ Symbol()函数不能用作构造函数,与new关键字一起使用。

  2. 使用全局符号注册表

    ​ 如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号。

    ​ 略过一下,有点太高深了。

  3. 使用符号作为属性

  4. 常用内置符号

  5. Symbol.asyncItrator

  6. Symbol.hasInstance

  7. Symbol.isConcatSpreadable

  8. Symbol.iterator

  9. Symbol.match

  10. Symbol.replace

  11. Symbol.search

  12. Symbol.species

  13. Symbol.split

  14. Symbol.toPrimitive

  15. Symbol.toStringTag

  16. Symbol.unscopables

3.4.8 Object类型

ECMAScript中的对象其实就是一组数据和功能的集合对象通过new操作符后跟对象类型的名称来创建。开发者可以通过创建Object类型的实例来创建自己的对象,然后再给对象添加属性和方法

let o = new Object();

​ 每个Object实例都有如下属性和方法:

  • constructor:用于创建当前对象的函数。在前面的例子中,这个属性的值就是Object()函数。
  • hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如o.hasOwnProperty(“name”))或符号。
  • isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型。
  • propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用(本章稍后讨论的)for-in语句枚举。与hasOwnProperty()一样,属性名必须是字符串。
  • toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
  • toString():返回对象的字符串表示。
  • valueOf():返回对象对应的字符串、数值或布尔值表示。通常与toString()的返回值相同。

3.5 操作符

3.5.1 一元操作符

​ 只操作一个值的操作符叫一元操作符。

  • ​ 递增/递减操作符

    操作符可以作用于任何值,意思是不限于整数——字符串、布尔值、浮点值,甚至对象都可以。递增和递减操作符遵循如下规则。

    • 对于字符串,如果是有效的数值,则转换为数值再应用改变。变量类型从字符串变成数值。
    • 对于字符串,如果不是有效的数值形式,则将变量的值设置为NaN。变量类型从字符串变成数值。
    • 对于布尔值,如果是false,则转换为0再应用改变。变量类型从布尔值变成数值。
    • 对于布尔值,如果是true,则转换为1再应用改变。变量类型从布尔值变成数值。
    • 对于浮点值,加一或减一
    • 如果是对象,则调用其valueOf()方法取得可以操作的值,对得到的值应用上述规则。如果是NaN,则调用toString()并再次应用其他
  • 一元加和减

    ​ 如果将一元加应用到非数值,则会执行与使用Number()转型函数一样的类型转换:布尔值faluse和true转换为0和1,字符串根据特殊规则进行解析,对象会调用它们的valueOf()和/或toString()方法以得到可以转换的值。

3.5.2 位操作符

位操作符用于数值的底层操作,也就是操作内存中表示数据的比特(位)ECMAScript中的所有数值都以IEEE 754 64位格式存储,但位操作并不直接应用到64位表示,而是先把值转换为32位整数,再进行位操作,之后再把结果转换为64位。

  1. 按位非

    按位非操作符用波浪符(~)表示,它的作用是返回数值的一补数。按位非的最终效果是对数值取反并减1。

    let num1 = 25;      //二进制00000000000000000000000000011001
    let num2 = ~num1;   //二进制11111111111111111111111111100110
    console.log(num2);  // -26
    
  2. 按位与

    按位与操作符用和号(&)表示,有两个操作数。按位与操作在两个位都是1时返回1,在任何一位是0时返回0。我们对数值25和3求与操作,如下所示:

    let result = 25 & 3;
    console.log(result); // 1
    
    25 = 0000 0000 0000 0000 0000 0000 0001 1001
      3 = 0000 0000 0000 0000 0000 0000 0000 0011
    ---------------------------------------------
    AND = 0000 0000 0000 0000 0000 0000 0000 0001
    如上所示,25和3的二进制表示中,只有第0位上的两个数都是1。于是结果数值的所有其他位都会以0填充,因此结果就是1。
    
  3. 按位或

    按位或操作符用管道符(|)表示,同样有两个操作数。按位或操作在至少一位是1时返回1,两位都是0时返回0。如果对25和3执行按位或,代码如下所示:

    let result = 25 | 3;
    console.log(result); // 27
    可见25和3的按位或操作的结果是27:
    
     25 = 0000 0000 0000 0000 0000 0000 0001 1001
      3 = 0000 0000 0000 0000 0000 0000 0000 0011
    ---------------------------------------------
     OR = 0000 0000 0000 0000 0000 0000 0001 1011
    
  4. 按位异或

    按位异或用脱字符(^)表示,同样有两个操作数。它只在一位上是1的时候返回1(两位都是1或0,则返回0)。对数值25和3执行按位异或操作:

    let result = 25 ^ 3;
    console.log(result); // 26
    可见,25和3的按位异或操作结果为26,如下所示:
    
     25 = 0000 0000 0000 0000 0000 0000 0001 1001
      3 = 0000 0000 0000 0000 0000 0000 0000 0011
    ---------------------------------------------
    XOR = 0000 0000 0000 0000 0000 0000 0001 1010
    
  5. 左移

    ​ 左移操作符用两个小于号(<<)表示,会按照指定的位数将数值的所有位向左移动。。比如,如果数值2(二进制10)向左移5位,就会得到64(二进制1000000),如下所示:

    let oldValue = 2;              //等于二进制10
    let newValue = oldValue << 5;  //等于二进制1000000,即十进制64
    

    ​ 在移位后,数值右端会空出5位。左移会以0填充这些空位,让结果是完整的32位数值(见图3-2)。

  6. 有符号右移

    ​ 有符号右移由两个大于号(>>)表示,会将数值的所有32位都向右移,同时保留符号(正或负)。

  7. 无符号右移

    ​ 无符号右移用3个大于号表示(>>>),会将数值的所有32位都向右移。对于正数,无符号右移与有符号右移结果相同。对于负数,有时候差异会非常大。与有符号右移不同,无符号右移会给空位补0,而不管符号位是什么。

3.5.3 布尔操作符

​ 布尔操作符一共有3个:逻辑非、逻辑与和逻辑或

  1. 逻辑非

    ​ 逻辑非操作符由一个叹号(!)表示。这个操作符始终返回布尔值,无论应用到的是什么数据类型。逻辑非操作符首先将操作数转换为布尔值,然后再对其取反。逻辑非操作符会遵循如下规则:

    • 如果操作数是对象,则返回false。
    • 如果操作数是空字符串,则返回true。
    • 如果操作数是非空字符串,则返回flase。
    • 如果操作数是数值0,则返回true。
    • 如果操作数是非0数值(包括Infinity),则返回false。
    • 如果操作数是null,则返回true。
    • 如果操作数是NaN,则返回true。
    • 如果操作数是undefined,则返回true。
  2. 逻辑与

    ​ 逻辑与操作符由两个和号(&&)表示,应用到两个值。如果有操作数不是布尔值,则逻辑与并不一定会返回布尔值,而是遵循如下规则:

    • 如果第一个操作数是对象,则返回第二个操作数。
    • 如果第二个操作数是对象,则只有第一个操作数求值为true才返回该对象。
    • 如果两个操作数都是对象,则返回第二个操作数。
    • 如果有一个操作数是null,则返回Null。
    • 如果有一个操作数是NaN,则返回NaN。
    • 如果有一个操作数是undefined,则返回undefined。

    逻辑与操作符是一种短路操作符,意思就是如果第一个操作数决定了结果,那么永远不会对第二个操作数求值。对逻辑与操作符来说,如果第一个操作数是false,那么无论第二个操作数是什么值,结果也不可能等于true

  3. 逻辑或

    ​ 逻辑或由两个管道符(||)表示,与逻辑与类似,如果有一个操作数不是布尔值,那么逻辑或操作符也不一定返回布尔值。逻辑或操作符也具有短路的特性。只不过对逻辑或而言,第一个操作数求值为true,第二个操作数就不会再被求值了

3.5.4 乘性操作符

​ ECMAScript定义了3个乘性操作符:乘法、除法和取模

  1. 乘法操作符

    乘法操作符由一个星号(*)表示,可以用于计算两个数值的乘积。

  2. 除法操作符

    除法操作符由一个斜杠(/)表示,用于计算第一个操作数除以第二个操作数的商。

  3. 取模操作符

    取模(余数)操作符由一个百分比符号(%)表示。

3.5.5 指数操作符

​ ECMAScript 7新增了指数操作符,Math.pow()现在有了自己的操作符**,结果是一样的:

console.log(Math.pow(3, 2);    // 9
console.log(3 ** 2);           // 9

console.log(Math.pow(16, 0.5); // 4
console.log(16** 0.5);         // 4

​ 不仅如此,指数操作符也有自己的指数赋值操作符=**,该操作符执行指数运算和结果的赋值操作:

let squared = 3;
squared **= 2;
console.log(squared); // 9

let sqrt = 16;
sqrt **= 0.5;
console.log(sqrt); // 4

3.5.6 加性操作符

  1. 加法操作符

​ 如果有一个操作数是字符串,则要应用如下规则:

  • 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面;

  • 如果只有一个操作数是字符串,则**将另一个操作数转换为字符串,再将两个字符串拼接到一起。**如果有任一操作数使对象、数值或布尔值,则调用它们的toString()方法以获取字符串,然后再应用前面的关于字符串的规则。对于undefined和null,则调用String()函数,分别获取"undefined"和"null"。

    let result1 = 5 + 5;        //两个数值
    console.log(result1);       // 10
    let result2 = 5 + "5";      //一个数值和一个字符串
    console.log(result2);       // "55"
    
  1. 减法操作符

3.5.7 关系操作符

​ 关系操作符执行比较两个值的操作,包括小于(<)、大于(>)、小于等于(<=)和大于等于(>=)。这几个操作符都返回布尔值

3.5.8 相等操作符

ECMAScript提供了两组操作符。第一组是等于和不等于它们在比较之前执行转换。第二组是全等和不全等它们在比较之前不执行转换

  1. 等于和不等于 (== !=)

    注意:

    • null和undefined相等
    • null和undefined不可以转换为其他类型的值再进行比较
    • **如果有任一操作数是NaN,则相等操作符返回false,不相等操作符返回true。**即使两个操作数都是NaN,相等操作符也返回false。
    • 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true。
  2. 全等和不全等(!== ===)

    ​ 全等和不全等在比较时不转换操作数,只有两个操作数在不转换的前提下相等才返回true。

    let result1 = ("55" == 55);   // true,转换后相等
    let result2 = ("55" === 55);  // false,不相等,因为数据类型不同
    

    ​ 另外,null === undefined是false,因为它们不是相同的数据类型。

3.5.9 条件操作符

variable = boolean_expression ? true_value : false_value;

3.5.10 赋值操作符

​ 简单赋值用等于号表示。

3.5.11 逗号操作符

​ 逗号操作符可以用来在一条语句中执行多个操作,在一条语句中同时声明多个变量是逗号操作符最常用的场景,在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值

let num = (5, 1, 4, 8, 0); // num的值为0

3.6 语句

3.6.1 if语句

if (condition) statement1 else statement2
if (condition1) statement1 else if (condition2) statement2 else statement3

3.6.2 do-while语句

​ do-while语句是一种后测试循环语句,即循环体中的代码执行后才会对退出条件进行求值。循环体内的代码至少执行一次。

do {
  statement
} while (expression);

3.6.3 while语句

​ while语句是一种先测试循环语句。

while(expression) statement

3.6.4 for语句

​ for语句也是先测试语句,只不过增加了进入循环之前的初始化代码,以及循环执行后要执行的表达式,语法如下:

for (initialization; expression; post-loop-expression) statement

无法通过while循环实现的逻辑,同样也无法使用for循环实现。因此for循环只是将循环相关的代码封装在了一起而已。

3.6.5 for-in语句

for-in语句是一种严格的迭代语句,用于枚举对象中的非符号键属性

for (property in expression) statement
例
for (const propName in window) {
  document.write(propName);
}
//使用for-in循环显示了BOM对象window的所有属性。每次执行循环,都会给变量propName赋予一个window对象的属性作为值,直到window的所有属性都被枚举一遍。
//这里控制语句中的const也不是必需的。但为了确保这个局部变量不被修改,推荐使用const。

ECMAScript中对象的属性是无序的,因此for-in语句不能保证返回对象属性的顺序。换句话说,所有可枚举的属性都会被返回一次,但返回的顺序会因浏览器而异。

3.6.6 for-of语句

​ for-of语句是一种严格的迭代语句,用于遍历可迭代对象的元素:

for (property of expression) statement
例
for (const el of [2,4,6,8]) {
  document.write(el);
}
//我们使用for-of语句显示了一个包含四个元素的数组中的所有元素。循环会一直持续到将所有元素都迭代完。
//for-of循环会按照可迭代对象的next()方法产生值的顺序迭代元素。

3.6.7 标签语句

​ 标签语句用于给语句加标签,语法如下:

label: statement
例
start: for (let i = 0; i < count; i++) {
  console.log(i);
}
//start是一个标签,可以在后面通过break或continue语句引用。

​ 标签语句的典型应用场景是嵌套循环。

3.6.8 break和continue语句

break语句用于立即退出循环,强制执行循环后的下一条语句。continue语句也用于立即退出循环,但会再次从循环顶部开始执行。

3.6.9 with语句

​ with语句的用途是将代码作用域设置为特定的对象,其语法是:

with(expression)statement;

使用with语句的主要场景是针对一个对象反复操作,这时候将代码作用域设置为该对象能提供便利,如下面的例子所示:

let qs = location.search.substring(1);
let hostName = location.hostname;
let url = location.href;

//利用with语句,可以优化成
with(location) {
  let qs = search.substring(1);
  let hostName = hostname;
  let url = href;
}
//with语句用于连接Location对象。这意味着在这个语句内部,每个变量首先会被认为是一个局部变量。

严格模式不允许使用with语句,否则会抛出错误。由于with语句影响性能且难于调试其中的代码,通常不推荐在产品代码中使用with语句。

3.6.10 switch语句

switch (expression) {
  case value1:
    statement
    break;
  case value2:
    statement
    break;
  case value3:
    statement
    break;
  case value4:
    statement
    break;
  default:
    statement
}

​ 每个case(条件/分支)相当于:“如果表达式等于后面的值,则执行下面的语句。”break关键字会导致代码执行跳出switch语句。如果没有break,则代码会继续匹配下一个条件。default关键字用于在任何条件都没有满足时指定默认执行的语句。

​ 在ECMAScripit中,又给switch语句赋予了一些独有的特性。首先,switch语句可以用于所有数据类型,其次,条件的值不需要是常量,也可以是变量或表达式

switch ("hello world") {
  case "hello" + " world":
    console.log("Greeting was found.");
    break;
  case "goodbye":
    console.log("Closing was found.");
    break;
  default:
    console.log("Unexpected message was found.");
}

3.7 函数

​ 函数对任何语言来说都是核心组件,因为它们可以封装语句,然后在任何地方、任何时间执行ECMAScript中的函数使用function关键字声明,后跟一组参数,然后是函数体。

function functionName(arg0, arg1,...,argN) {
  statements
}

ECMAScript中的函数不需要指定是否返回值。任何函数在任何时间都可以使用return语句来返回函数的值,用法是后跟要返回的值。只要碰到return语句,函数就会立即停止执行并退出。因此,return语句后面的代码不会被执行

return语句也可以不带返回值。这时候,函数会立即停止执行并返回undefined。这种用法最常用于提前终止函数执行,并不是为了返回值。

3.8 小结

  1. ECMAScript中的基本数据类型包括Undefined、Null、Boolean、Number、String和Symbol
  2. 与其他语言不同,ECMAScript不区分整数和浮点值,只有Number一种数值数据类型。
  3. Object是一种复杂数据类型,它是这门语言中所有对象的基类。
  4. 严格模式为这门语言中某些容易出错的部分施加了限制。
  5. ECMAScript中的函数与其他语言中的函数不一样。
    • 不需要指定函数的返回值,因为任何函数可以在任何时候返回任何值。
    • 不指定返回值的函数实际上会返回特殊值undefined。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值