JavaScript语法(笔记及抄录《JavaScript》高级程序设计(第4版))

  • 区分大小写
    • js中一切都区分大小写。无论是变量、函数名还是操作系统,都区分大小写。例如,变量Test和变量test是两个不同的变量。类似typeof不能作为函数名,因为它是一个关键字。但是Typeof可以是变量名。
  • 标识符
    • 标识符,就是变量、函数、属性或函数参数的名称。标识符可以由一或多个下列字符组成:
      • 第一个字符必须是一个字母、下划线_或美元符号$;
      • 剩下的其他字符可以是字母、下划线、美元符号或数字
    • 标识符中的字母可以是扩展ASCII中的字母,也可以是Unicode的字符,但是一些偏僻的字符不推荐使用
    • js中标识符使用驼峰大小写形式,即第一个单词的首字母小写,后面每个单词的首字母大写,如:
      • firstSecond
      • myCar
      • doSomethingImportant
      • 这样的写法并不是强制性的。但是这种形式跟js内置函数和对象的命名方式一致,所以算是最佳方案。
    • 注意:关键字、保留字、true、false和null不能作为标识符
  • 注释
    • // 单行注释
    • /* 多行注释 */
  • 严格模式
    • es5增加了严格模式(strict mode)的概念。严格模式是一种不同的JavaScript解析和之幸福模式,es3的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。要对整个脚本启用严格模式,在脚本开头加上这一行:"use strict"
    • 虽然看起来像个没有赋值给任何变量的字符串,但它其实是一个预处理指令。任何支持的JavaScript引擎看到它都会切换到严格模式。选择这种羽凡的目的是不破坏es3语法。也可以指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头即可:
      function doSomething(){
      "use strict";
      ​}
  • 语句
    • js中的语句以分号结尾。省略分号意味着由解析器确定语句在哪里结尾,如:
      let sum = a + b // 没有分号也有效,但不推荐
      ​let diff = a - b; // 加分号有效,推荐
    • 即使语句末尾的分号不是必需的,也应该加上。加分号有助于防止省略造成的问题,比如可以避免输入内容不完整。此外,加分号也便于我们通过删除空行来压缩代码(如果没有结尾的分号,只删除空行,则会导致语法错误)。加分号也有助于在某些情况下提升性能,因为解析器会尝试在合适的位置补上分号以纠正语法错误。
    • 多条语句可以合并到一个C语言风格的代码块中。代码块由一个左花括号({)标识开始,一个右花括号(})标识结束:
      if (test) {
      ​ test = false;
      console.log(test);
      ​}
    • if之类的控制语句只在执行多条语句时要求必须由代码块。不过,最佳方案是始终在控制语句中使用代码块,即使要执行的只有一条语句。
  • 关键字与保留字
    • ECMA-262描述了一组保留的关键字,这些都有特殊用途。按照规定,保留的关键字不能用作标识符或属性名。入下:
      break do in typeof case else instanceof var catch export new void class estends return while const finally super with continue for switch yield debugger function this default if throw delete import try
    • 规范中也描述了一组未来的保留字,同样不能用作标识符或属性名。
      • 始终保留
        enum
      • 严格模式下保留
        implements package public interface protected static let private
      • 模块代码中保留
        await
      • 这些词汇不能用作标识符,但现在还可以用作对象的属性名。一般来说,最好还是不要使用关键字和保留字作为标识符和属性名,以确保兼容其他es版本。
  • 变量
    • js变量是松散类型的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符。有 3 个关键字可以生命变量:var、const和let。其中,var在es的所有版本都可以使用,而const和let只能在es6及更晚的版本中使用。
    • var关键字
      • 要定义变量,可以使用var操作符,后面个变量名:
        var messgae;
      • 这行代码定义了一个名为message的变量,可以用它保存任何类型的值。定义变量并对其赋值:
        var message = "hi";
      • 这里,message被定义为一个保存字符串值hi的变量。像这样初始化变量不会将它标识为字符串类型,只是一个简单的赋值。更改保存的值:
        var message = "hi";
        ​message = 100; // 合法,但不推荐
      • 变量message首先被定义为一个保存字符串值hi 的变量,然后又被重写为保存了数值100。随后,不仅可以改变保存的值,也可以改变值的类型。
      • var声明作用域
        • 使用var操作符定义的变量会成为包含它的函数的局部变量。比如,使用var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:
          function test(){
          var message = "hi"; // 局部变量
          ​}
          ​test();
          ​console.log(message); // 报错
        • 这里,message变量是在函数内部使用var定义的。函数叫test(),调用它会创建这个变量并给它赋值。调用之后变量即被销毁,因此最后一行会导致报错。不过,在函数内定义变量法是省略var操作符,可以创建一个全局变量:
          function test(){
          message = "hi"; // 局部变量
          ​}
          ​test();
          ​console.log(message); // "hi"
        • 去掉之前的var操作符之后,message就变成了全局变量。只要调用一个函数test(),就会定义这个变量,并且可以在函数外部访问到。但是博主不推荐这么做,因为这样会很难维护,也会使其他人在看代码时会造成困惑,不能确定省略var是不是有意为之。
        • 定义多个变量:
          var message = "hi",
          ​ found = false,
          ​ age = 29;
        • 这里定义并初始化了3个变量。因为js是松散类型的,所以使用不同数据类型初始化变量可以用一条语句来声明。插入换行和空格缩进不是必需的,但这样有利于阅读。
      • var声明提升
        function foo(){
        console.log(age);
        ​ var age = 26;
        ​}
        ​foo(); // undefined
        • 使用var时,上面的代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域顶部。之所以不会报错,是因为js运行时把它看成等价与如下代码:
          function foo(){
          ​ var age;
          console.log(age);
          ​ age = 26;
          ​}
          ​foo(); // undefined
        • 这就是所谓的“提升”(hoist),也就是把所有变量声明都拉到函数作用域的顶部。此外,反复多次使用var声明同一个变量也没有问题。
    • let声明
      • let跟var的作用差不多,但有着非常重要的区别。最明显的区别是,let声明的范围是块作用域,而var声明的范围是函数作用域。
        if (true){
        var name = 'Matt';
        ​ console.log(name); // Matt
        ​}
        ​console.log(name); // Matt

        ​if (true){
        let name = 'Matt';
        ​ console.log(name); // Matt
        ​}
        ​console.log(name); // ReferenceError:age没有定义
      • 在此,age变量之所以不能再if块外部被引用,是因为它的作用域仅限于该块内部。块作用域是函数作用域的子集,因此适用于var的作用域限制同样也适用于let。
      • let也不允许同一个块作用域中出现冗余声明。这样会导致报错:
        var name;
        ​var name;

        ​let age;
        ​let age; // SyntaxError:标识符age已经声明过了
         
      • 当然,js引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同的标识符不会报错,而这是因为同一个块中没有重复声明:
        var name = 'chuanzi';
        ​console.log(name); // chuanzi
        ​if(true){
        var name = 'Matt';
        ​ console.log(name); // Matt
        ​}

        ​let age = 30;
        ​console.log(age); //30
        ​if(true){
        let age = 26;
        ​console.log(age); //26
        ​}
      • 对声明冗余报错不会因混用let和var而受影响。这两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在。
        var name;
        ​let name; // SyntaxError

        let age;
        ​var age; // SyntaxError
      • 暂时性死区
        • let与var的另一个重要的区别,就是let声明的变量不会在作用域中被提升。
          console.log(name); // undefined
          ​var name = 'Matt';

          ​console.log(age); // ReferenceError: age 没有定义
          ​let age = 26;
        • 在解析代码时,js引擎也会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用未声明的变量。在let声明之前的执行瞬间被称为“暂时性死区”,在此阶段引用任何后面才声明的变量都会抛出ReferenceError。
      • 全局声明
        • 与var关键字不同,使用let在全局作用域中声明的变量不会成为window对象的属性(var声明的变量则会)。
          var name = 'Matt';
          console.log(window.name); // 'Matt'

          ​let age = 26;
          ​console.log(window.age); // undefined
        • 不过,let声明仍然时在全局作用域中发生的,相应变量会在页面的生命周期内存续。因此,为了避免SyntaxError,必须确保页面不会重复声明同一个变量。
      • 条件声明
        • 在使用var声明变量时,由于声明会被提升,js引擎会自动将多余的声明在作用域顶部合并为一个声明。因为let的作用域是块,所以不可能检查前面是否已经使用let声明过同名变量,同时也就不可能在没有声明的情况下声明它。
          <script>
          var name = 'Nicholas';
          ​ let age = 26;
          ​</script>

          <script>
          ​ // 假设脚本不确定页面中是否已经声明了同名变量
          ​ // 那它可以假设还没有声明过

          var name = 'Matt';
          ​ // 这里没问题,因为可以被作为一个提升声明来处理
          ​ // 不需要检查之前是否声明过同名变量

          ​ let age = 26;
          ​ // 如果age之前声明过,这里会报错
          ​</script>
        • 使用try/catch语句或者typeof操作符也不能解决,因为条件块中let声明的作用域仅限与该块。
          <script>
          let name = 'Nicholas';
          ​ let age = 26;
          ​</script>

          <script>
          ​ // 假设脚本不确定页面中是否已经声明了同名变量
          ​ // 那它可以假设还没有声明过
          ​if(typeof name === 'undefined'){
          ​ let name;
          ​}
          ​// name被限制在if{}块的作用域内
          ​// 因此这个赋值形同全局赋值
          name = 'Matt';

          ​try{
          console.log(age); // 如果age没有声明过,则会报错
          ​}
          ​catch(error){
          ​ let age;
          ​}
          ​// age被限制在catch{}块的作用域内
          ​// 因此这个赋值形同全局赋值

          ​ age = 26;
          ​</script>
        • 为此,对于let这个新的es6声明关键字,不能依赖条件声明模式。
      • 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 没有定义
        • 在使用var的时候,最常见的问题就是对迭代变量的奇特声明和修改:
          for(var i=0;i<5;i++){
          setTimeour(()=>{
          ​ console.log(i);
          },0)
          ​}

          ​// 你可能以为会输出 0、1、2、3、4
          ​// 实际上会输出 5、5、5、5、5
        • 之所以会这样,是因为在退出循环时,迭代变量保存的时导致循环退出的值:5.在之后执行超时逻辑时,所有的i都是同一个变量,因而输出的都是同一个最终值。而在使用let声明迭代变量时,js引擎在后台会为每个迭代循环声明一个新的迭代变量。每个setTimeout引用的都是不同的变量实例,所以console.log输出的时我们期望的值,也就是循环执行过程中每个迭代变量的值。
          for(let i=0;i<5;i++){
          setTimeout(()=> console.log(i),0);
          ​}
          ​// 会输出 0、1、2、3、4
        • 这种每次迭代声明一个独立变量实例的行为设用于所有风格的for循环,包括for-in和for-of循环
    • const声明
      • const的行为与let基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且不可修改。
        const age = 36;
        ​age = 26; // TypeError: 给常量赋值
      • const也不循序重复声明。
      • const声明的作用域也是块。
      • const声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制。
        const person = {};
        person.name = 'Matt'; // ok
      • 即使js引擎会为for循环中的let声明分别创建独立的变量实例,而且const变量跟let变量很相似,也不能用const来声明迭代变量(因为迭代变量会自增):
        for(const i = 0;i<5;i++){} // TypeError: 给常量赋值
      • 如果只想用const声明一个不会被修改的for循环变量,那也是可以的。每次迭代只是创建一个新变量,这对for-of和for-in循环特别有意义:
        let i=0;
        ​for(const j=7;i<5;++i){
        constle.log(j);
        ​}
        ​// 7 7 7 7 7

        ​for(const key in {a:1,b:2}){
        console.log(key);
        ​}
        ​// a,b

        ​for(const value of [1,2,3,4,5]){
        console.log(value);
        ​}
        ​// 1 2 3 4 5
    • 声明风格及最佳方案
      • 不适用var
        • 有了let和const,大多数开发者会发现自己不再需要var了。限制自己只用let和const有助于提升代码质量,因为变量有了明确的作用域、声明位置、以及不变的值。
      • const优先,let次之,
        • 使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此很多开发者认为应该优先使用const来声明变量,只再提前知道未来会有修改时,再使用let。这样可以让开发者更有信心的推断某些变量的值永远不会变,同时也能迅速发现因为意外复制导致的非预期行为。
  • 数据类型
    • js有6中简单数据类型(也成为原始类型):Undefined、Null、Boolean、Number、String和Symbol。Symbol(符号)是es6新增的。还有一种复杂数据类型叫Object(对象)。Object是一种无序名值对的集合。因为在js中不能定义自己的数据类型,所以值都可以用上面7种数据类型之一来表示。
    • typeof操作符
      • typeof用来确定变量的数据类型。对一个值使用typeof操作符会返回的数据类型:
        • undefined:表示值未定义;
        • boolean:表示值为布尔值;
        • string:表示值为字符串;
        • number:表示值为数值;
        • object:表示值为对象(而不是函数)或null;
        • function:表示值为函数;
        • symbol:表示值为符号。
      • 使用方法:
        let name = 'Matt';
        ​console.log(typeof name); // "string"
        ​console.log(typeof(name)); // "string"
        ​console.log(typeof 95); // "number"
      • 在上面示例中,我们把变量和一个数值字面量传给了typeof操作符。注意,因为typeof是一个操作符而不是函数,所以不需要参数(但可以使用参数)。
      • 注意:调用typeof null 返回的是”object“。这是因为特殊值null被认为是一个对空对象的引用。
    • Undefined类型
      • undefined类型只有一个值,就是特殊值undefined。当使用var或let声明了变量但没有初始化时,就相当于给变量赋值了undefined值:
        let message;
        ​console.log(message); // undefined
      • 注意:没有定义值的时候使用typeof时返回的是undefined。
    • Null类型
      • null类型同样只有一个值,即特殊值null。逻辑上讲,null值表示一个空对象指针,这也是给typeof穿一个null会返回“object” 的原因。
      • 在定义将来要保存对象值的变量时,建议使用null来初始化,不要使用其他值。这样,只要检查这个变量的值是不是null就可以知道这个变量是否在后来被重新赋予了一个对象的引用。
      • undefined是由null派生而来的,因此ECMA-262将它们定义为表面上相等,如:
        console.log(null == undefined); // true
      • 用等于操作符比较null和undefined始终返回true。
      • 即使null和undefined有关系,它们的用途也是完全不一样的。永远不必显示地将变量设置为undefined。但null不是这样的。任何时候,只要变量要保存对象,而当时又没有哪个对象可保存,就要用null来填充该变量。这样就可以保持null是空对象指针的语义,并进一步将其与undefined区分开来。
    • Boolean类型
      • boolean(布尔值)类型是js中使用最频繁的类型之一,有两个字面值:true和false。这两个布尔值不同于数值,因此true不等于1,false不等于0.
      • 注意布尔值字面量true和false是区分大小写的,因此True和False(及其他大小混写形式)是有效的标识符,但不是布尔值。
      • 虽然布尔值只有两个,但所有其他js类型的值都有相应布尔值的等价形式。要将一个其他类型的值转换为布尔值,可以调用特定的Boolean()转型函数,下表转换规则:

         

    • Number类型
      • Number类型使用IEEE 754格式表示整数和浮点数(在某些语言中也叫双精度值)。不同的数值类型相应地也有不同的数值字面量格式。
        • 十进制
          • 直接写出来
            let intNum = 55;
        • 八进制
          • 第一个数字必须是0,然后是相应的八进制数字(数值0~7)。如果字面量中包含的数字超出了应有的范围,就会忽略前缀的0,后面的数字序列会被当成十进制数,
            let num1 = 070; // 八进制的56
            ​let num2 = 079; // 无效的八进制值,当成79处理
            ​let num3 = 08; // 无效的八进制值,当成8处理
        • 十六进制
          • 前面加上0x
      • 浮点值
        • 要定义浮点值,数值中必须包含小数点。
        • 科学计数法
          • 要求是一个数值后跟一个大写或者小写的字母e,再加上一个要乘的10的多少次幂。
            let floatNum = 3.125e7; //等于31250000
          • 如果值是非常小的值,例如 0.00000000000000003。用科学计数法可以表示为3e-17。
      • 值的范围
        • js可以表示的最小的数值保存再Number.MIN_VALUE中,这个值再多数浏览器中是5e-324;可以表示的最大的数值保存再Number.MAX_VALUE中,这个值再多数浏览器中是1.7976931348623157e+308。如果某个计算得到的值超出了js可以表示的范围,那么这个数值会被自动转换为一个特殊的Infinity(无穷)值。任何无法表示的负值以-Infinity(负无穷大)表示,正数为Infinity(正无穷大)。
        • 如果计算返回正或负 Infinity,则该值将不能再进一步用于任何计算。要确定一个值是不是有限大,可以使用isFinite()函数,
      • NaN
        • 这事一个特殊的数值,意思是“不是数值”,用于表示本来要返回数值的操作失败了。如:0、+0或-0相除会返回NaN。
        • 如果分子是非0值,分母是由符号0或无符号0,会返回Infinity或-Infinity
        • NaN有几个独特的属性。首先,任何设计NaN的操作始终返回NaN。
        • 判断一个值是不是NaN可以使用isNaN().
      • 数值转换
        • 有3个函数可以将非数值转换为数值:Number()、parseInt()、parseFloat()。Number()是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。Number()函数基于如下规则执行转换:
          • 布尔值,true转换为1,false转换为0
          • 数值,直接返回
          • null,返回0
          • undefined,返回NaN
          • 字符串
            • 如果字符串都是数值字符,返回数值字符,包括加减号
            • 如果字符串是浮点值格式,那么会转换为相应的浮点值
            • 如果字符串是0x开头的值,并且是每个值不超过f,那么会转为16禁止相应的十进制数值
            • 如果是空字符串,返回0
            • 如果字符串中有其他字符,返回NaN
        • parseInt可以指定转换的进制数,parseInt(要转换的值,指定转换的进制数)
    • String类型
      • String(字符串)数据类型表示零或多个16位Unicode字符序列。字符串可以使用双引号"、单引号'或反引号`表示,但是,结尾的符号必须和开头的符号一致,要注意,字符串中不能有和开头的符号,除非用 \ 格式化。
      • 字符字面量

         

      • 字符串的特点
        • 字符串是不可变的,一旦创建,它们的值就不能变了,只能销毁原始的字符,然后将新值保存到该变量。
      • 转换为字符串
        • 使用toString()方法,返回当前值的字符串等价物。toString()方法可见于数值、布尔值、对象和字符串值。null和undefined没有toString()方法。toString()可以接受一个参数,即以什么进制来表示输出数值的字符串,默认是十进制。如果不确定一个值是不是null或者undefined,可以使用String()转型函数,它始终会返回表示相应类型值的字符串。String()函数遵循如下规则。
          • 如果值由toString()方法,则调用该方法并返回结果
          • 如果值是null,返回null
          • 如果值是undefined,返回undefined
      • 模板字面量
        • es6新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串:
          let str = 'first line\nseconbd line';
          ​let str2 `first line
          ​second line`;

          ​console.log(str);
          ​// first line
          ​// second line

          ​console.log(str2);
          ​// first line
          ​// second line

          ​console.log(str === str2); // true
        • 模板字面量在定义模板时特别有用,如:
          let pageHtml = `
          <div>
          <a href="#">
          ​ <span>Jake</span>
          ​ </a>
          ​</div>
          ​`;
      • 字符串插值
        • 模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。技术上讲,模板字面量不是字符串,而是一种特殊的js句法表达式,只不过求值后得到的是字符串。模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最接近的作用域取值。
        • 字符串插值通过在${ }中使用一个js表达式实现:
          let value = 5;
          ​let exponent = 'second';

          ​let str = `${value} to the ${exponse} power is ${ value * value}`;
          ​console.log(str); // 5 to the second power is 25
        • 所有插入的值都会使用toString()强制转型为字符串。
      • 模板字面量标签函数
        • 模板字面量也支持定义标签函数,而通过标签函数可以自定义插值行为。标签函数会接受被插值记号分隔后的模板和对每个表达式求值的结果。标签函数本身是一个常规函数,通过前缀到模板字面量来应用自定义行为。
      • 原始字符串
        • 使用模板字面量也可以直接获取原始的模板字面量内容,而不是被转换后的字符表示。为此,可以使用默认的String.raw()标签函数:
          // 换行符示例
          ​console.log(`first line\nsecond line`);
          ​// first line
          ​// second line

          ​console.log(String.raw(`first line\nsecond line`));
          ​// "`first line\nsecond line`"
    • Symbol类型
      • Symbol(符号)是es6新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。符号的涌入是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
      • 符号的基本用法
        • 符号需要使用Symbol()函数初始化。因为符号本身是原始类型,所以typeof操作符对符号返回symbol。
          let sym = Symbol();
          ​console.log(typeof sym); // symbol
        • 调用Symbol()函数时,也可以传入一个字符串参数作为对符号的描述,将来可以通过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关:
          let sym1 = Symbol();
          ​let otherSym = Symbol();

          ​let sym2 = Symbol('foo');
          ​let otherSym2 = Symbol('foo');

          ​console.log(sym1===otherSym); // false
          ​console.log(sym2===otherSym2); // false
        • 符号没有字面量语法,这也是它们发挥作用的关键。按照规范,你只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论时符号属性还是字符串属性。
          let foo = Symbol('foo');
          ​console.log(foo); //Symbol(foo)
        • 最重要的是,Symbol()函数不能用作构造函数。这样做是为了避免创建符号包装对象,但是可以借用Object()函数:
          let sym = Symbol();
          ​let mySym = Object(sym);
          ​console.log(typeof mySym); // "object"
      • 使用全局符号注册表
        • 如果运行时的不同部分需要共享和宠用符号实例,那么可以用一个字符串作为建,在全局符号注册表中创建并宠用符号。
        • 为此,需要使用Symbol.for()方法:
          let sym = Symbol.for('foo');
          ​console.log(sym); // symbol
        • Symbol.for()对每个字符串键都执行幂等操作。第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例。
          let sym1 = Symbol.for('foo); // 创建新符号
          ​let sym2 = Symbol.for('foo'); // 重用已有符号

          ​console.log(sym1 === sym2); // true
        • 即使采用相同的符号描述,在全剧以注册表中定义的符号跟使用Symbol()定义的符号也并不相同:
          let sym1 = Symbol('foo');
          ​let sym2 = Symbol.for('foo');
          ​console.log(sym1 === sym2); // false
        • 全局注册表中的符号必须使用字符串键来创建,因此作为参数传给Symbol.for()的任何值都会被转换为字符串。此外,注册表中使用的键同时也会被用作符号描述。
          let sym1 = Symbol.for();
          ​console.log(sym1); // Symbol(undefined)
        • 还可以使用Symbol.keyFor()来查询全局注册表,这个方法接收符号,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回undefined。
          // 创建全局符号
          ​let s = Symbol.for('foo');
          ​console.log(Symbol.keyFor(s)); // foo

          ​// 创建普通符号
          ​let s2 = Symbol('bar');
          ​console.log(Symbol.keyFor(s2)); // undefined
        • 如果传给Symbol.keyFor()的不是符号,则该方法抛出TypeError
      • 使用符号作为属性
        • 凡是可以使用字符串或数值左外属性的地方,都可以使用符号。这句包括了对象字面量属性和Object.defineProperty()/Object.definedProperties()定义的属性。对象字面量只能在计算属性语法中使用符号作为属性。
        • 类似于Object.getOwnPropertyNames()返回对象实例的常规属性数组,Object.getOwnPropertySymbols()返回对象实例的符号属性数组。这两个方法的返回值彼此互斥。Object.getOwnPropertyDescriptors()会返回同时包含常规和符号属性描述符的对象。Reflect.ownKeys()会返回两种类型的键:
          let s1 = Symbol('foo'),
          ​ s2 = Symbol('bar');

          ​let o = {
          [s1]:'foo val',
          ​ [s2]:'bar var',
          ​ baz:'baz val';
          ​ qux:'qux val'
          ​};

          ​console.log(Object.getOwnPropertySymbols(o));
          ​// [Symbol(foo), Symbol(bar)]

          ​console.log(Object.getOwnPropertyNames(o));
          ​// ['baz','qux']

          ​console.log(Object.getOwnPertyDescriptors(o));
          ​// {baz:{...},qux:{...},Symbol(foo):{...},Symbol(bar):{...}}
        • 因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失。但是,如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键:
          let o = {
          [Symbol('foo')]:'foo val',
          ​ [Symbol('bar')]:'bar var'
          ​};

          ​console.log(o);
          ​// {Symbol(foo):"foo val",Symbol(bar):"bar val"}

          ​let barSymbol = Object.getOwnPropertySymbols(0).find((symbol) => symbol.toString().match(/bar/));

          ​console.log(barSymbol);
          ​// Symbol(bar)
      • 常用内置符号
        • es6也引入了一批常用内置符号,用于暴露语言内部行为,开发者可以直接访问、重写或模拟这些行为。这些内置符号都以Symbol工厂函数字符串属性的形式存在。
        • 这些内置符号最重要的用途之一是重新定义它们,从而改变原生结构的行为。比如,我们知道for-of循环会在相关对象使用Symbol.iterator属性,那么就可以通过在自定义对象上重新定义Symbol.iterator的值,来改变for-of在迭代该对象时的行为。
        • 这些内置符号也没有什么特别之处,它们就是全局函数Symbol的普通字符串属性,指向一个符号的实例。所有内置符号属性都是不可写、不可枚举、不可配置的。
      • Symbol.asyncIterator
        • 根据es规范,这个符号作为一个属性表示"一个方法,该方法返回对象默认的AsyncIterator。由for-await-of语句使用”。换句话说,这个符号表示实现异步迭代器API的函数。
        • for-await-of循环会利用这个函数执行异步迭代操作。循环时,它们会调用以Symbol.asyncIterator为键的函数,并期望这个函数会返回一个实现迭代器API的对象。很多时候,返回的对象时实现该API的AsyncGenerator:
          class Foo{
          async * [Symbol.asyncIterator] () {}
          ​}

          ​let f = new Foo();
          ​console.log(f);
          ​// AsyncGenerator {<suspended>}
        • 技术上,这个由Symbol.asyncIterator函数生成的对象应该通过其next()方法陆续返回Promise实例。可以通过显式地调用next()方法返回,也可以隐式地通过异步生成器函数返回。
      • Symbol.hasInstance
        • 根据es规范,这个符号作为一个属性表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例。由instanceof操作符使用”。instanceof操作符可以用来确定一个对象实例的原型链上是否有原型。
      • Symbol.isConcatSpreadable
        • 根据es规范,这个符号作为一个属性表示“一个布尔值,如果是true,则意味着对象应该用Array.prototype.concat()打平其数组元素”。es6中的Array.prototype.concat()方法会根据接收到的对象类型选择如何将一个类数组对象拼接成数组实例。覆盖Symbol.isConcatSpreadable的值可以修改这个行为。
        • 数组对象默认情况下会被打平到已有的数组,false或假值会导致整个对象被追加到数组末尾。类数组对象默认情况下会被追加到数组末尾,true或真值会导致这个类数组对象被打平到数组实例。其他不是类数组对象的对象在Symbol.isConcatSpreadable被设置为true的情况下将被忽略。
      • Symbol.iterator
        • 根据es规范,这个符号作为一个属性表示"一个方法,该方法返回对象默认的迭代器。由for-of语句使用"。换句话说,这个符号表示实现迭代器API的函数。
        • for-of循环这样的语言结构会利用这个函数执行迭代操作。循环时,它们会调用以Symbol.iterator为键的函数,并默认这个函数会返回一个实现迭代器API的对象。
      • Symbol.match
        • 根据es规范,这个符号作为一个属性表示“一个正则表达式方法,该方法用正则表达式去匹配字符串。由String.prototype.match()方法使用”。String.prototype.match()方法会使用以Symbol.match为键的函数来对正则表达式求值。
      • Symbol.replace
        • 根据es规范,这个符号作为一个属性表示“一个正则表达式方法,该方法替换一个字符串中匹配的子串。由String.prototype.replace()方法使用”。String.protorype.replace()方法会使用以Symbol.replace为键的函数来对正则表达式求值。
      • Symbol.search
        • 根据es规范,这个符号作为一个属性表示“一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由String.prototype.search()方法使用"。String.prototype.search()方法会使用以Symbol.search为键的函数来对正则表达式求值。
      • Symbol.species
        • 根据es规范,这个符号作为一个属性表示“一个函数值,该函数作为创建派生对象的构造函数”。这个属性在内置类型中最常用,用于对内置类型实例方法的返回值暴露比例话派生对象的方法。
      • Symbol.split
        • 根据es规范,这个符号作为一个属性表示“一个正则表达式方法,该方法在匹配正则表达式的索引位置拆分字符串。由String.prototype.split()方法使用”。String.prototype.split()方法会使用以symbol.split为键的函数来对正则表达式求值。
      • Symbol.toPrimitive
        • 根据es规范,这个符号作为一个属性表示“一个方法,该方法将对象转换为相应的原始值。由Toprimitive抽象操作使用”。很多内置操作都会尝试强制将对象转换为原始值,包括字符串、数值和未指定的原始类型。对于一个自定义对象实例,通过在这个实例的Symbol.toPrimitive属性上定义一个函数可以改变默认行为。
      • Symbol.toStringTga
        • 根据es规范,这个符号作为一个属性表示“一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法Object.prototype.toString()使用”。
      • Symbol.unscopables
        • 根据es规范,这个符号作为一个属性表示“一个对象,该对象所有的以及继承的属性,都会从关联对象的with环境中绑定排除”。
    • Object类型
      • es中的独享其实就是一组数据和功能的集合。对象通过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()的返回值相同。
    • 操作符
      • 一元操作符
        • 只操作一个值的操作符叫一元操作符。
        • 递增/递减操作符
          • 分为两个版本:前缀版和后缀版。
            • 前缀版:符号位于操作的变量的前头。
              let age = 29;
              ​++age;
            • 后缀版:符号位于操作的变量的后头。
              let age = 29;
              ​age++;
            • 区别:前缀版的值会在语句被求值之前改变,如下面的例子,变量num以age减1后再加2进行初始化。因为递减操作先发生,所以age的值先变成28,然后再加2,结果是30。后缀版的值会在语句被求值之后再改变。
              let age = 29;
              ​let num = --age + 2;
              ​console.log(age); //28
              ​console.log(num); // 30
          • 递增和递减操作符遵循如下规则:
            • 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变成数值。
            • 对于字符串,如果不是有效的数值形式,则将变量的值设置为NaN。变量类型从字符串变成数值。
            • 对于布尔值,如果是false,则转换为0再应用改变。变量类型从布尔值变成数值。
            • 对于布尔值,如果是true,则转换为1再应用改变。变量类型从布尔值变成数值。
            • 如果是浮点值,加1或减1.
            • 如果是对象,则调用其valueOf()方法取得可以操作的值。对得到的值应用上述规则。如果是NaN,则调用toString()并再次应用其他规则。变量类型从对象变成数值。
          • 一元加和减
            • +、-
      • 位操作符
        • 按位非
          • 按位非操作符用波浪符(~)表示,它的作用是返回数值的一补数。按位非是es中为数不多的几个二进制数学操作符之一。
        • 按位与
          • 按位与操作符用和号(&)表示,有两个操作值。本质上,按位与就是将两个数的每一个位对齐,然后基于真值表中的规则,对每一位执行相应的与操作。

             

        • 按位或
          • 按位或操作符用管道符(|)表示,同样有两个操作数。按位或遵循如下真值表

             

        • 按位异或
          • 按位异或用脱字符(^)表示,同样由两个操作数。下面是按位异或的真值表:

             

        • 左移
          • 左移操作符用两个小于号(<<)表示,会按照指定的位数将数值的所有位向左移动
        • 有符号右移
          • 有符号右移由两个大于号(>>)表示,会将数值的所有32位都向右移,同时保留符号(正或负)。有符号右移实际上是左移的逆运算。
        • 无符号右移
          • 无符号右移用3隔大于号表示(>>>),会将数值的所有32位都向右移,对于正数,无符号右移与有符号右移结果相同。
      • 布尔操作符
        • 逻辑非
          • 逻辑非操作符由一个叹号(!)表示,可应用给es中的任何值。这个操作符始终返回布尔值,无论应用到的是什么数据类型。逻辑非操作符首先将操作数转换为布尔值,然后再对其取反。逻辑非操作符会遵守如下规则:
            • 如果操作数是对象,则返回false。
            • 如果操作数是空字符串,则返回true。
            • 如果操作数是非空字符串,则返回false。
            • 如果操作数是数值0,则返回true。
            • 如果操作数是非0数值(Infinity),则返回false。
            • 如果操作数是null,则返回true。
            • 如果操作数是NaN,则返回true。
            • 如果操作数是undefined,则返回true。
          • 逻辑非操作符也可以用于把人一直转换为布尔值。同时使用两个叹号(!!),相当于调用了转型函数Boolean()。无论操作数是什么类型,第一个叹号总会返回布尔值。第二个叹号对该布尔值取反,从而给出变量真正对应的布尔值。
        • 逻辑与
          • 逻辑与操作符由两个和号(&&)表示。逻辑与操作符遵循如下真值表:

             

          • 逻辑与操作符可用于任何类型的操作数,不限于布尔值。如果有操作数不是布尔值,则逻辑与并不一定会返回布尔值,而是遵循如下规则。
            • 如果第一个操作数是对象,则返回第二个操作数。
            • 如果第二个操作数是对象,则只有第一个操作数求值为true才会返回该对象。
            • 如果两个操作数都是对象,则返回第二个操作数。
            • 如果有一个操作数是null,则返回null。
            • 如果优化一个操作数是NaN,则返回NaN。
            • 如果有一个操作数是undefined,则返回undefined。
          • 逻辑与操作符是一种短路操作符,意思就是如果第一个操作数决定了结果,那么永远不会对第二个操作数求值。对逻辑与操作符来说,如果第一个操作数是false,那么无论第二个操作数是什么值,结果也不可能等于true。
        • 逻辑或
          • 逻辑或操作符由两个管道符(||)表示,比如:

             

          • 与逻辑与类似,如果有一个操作数不是布尔值,那么逻辑或操作符也不一定返回布尔值。它遵循以下规则:
            • 如果第一个操作数是对象,则返回第一个操作数。
            • 如果第一个操作数求值为false,则返回第二个操作数。
            • 如果两个操作数都是对象,则返回第一个操作数。
            • 如果两个操作数都是null,则返回null。
            • 如果两个操作数都是NaN,则返回NaN。
            • 如果两个操作数都是undefined,则返回undefined。
          • 同样与逻辑与类似,逻辑或操作符也具有短路的特性。只不过对逻辑或而言,第一个操作数值为true,第二个操作数就不会再被求值了。
      • 乘行操作符
        • 乘性操作符
          • 乘性操作符由一个星号(*)表示,可以用于计算两个数值的乘积,
          • 不过,乘法操作符在处理特殊值时也有一些特殊的行为。
            • 如果操作符都是数值,则执行常规的乘法运算,即两个正值相乘是正值,两个负值相乘也是正值,正负符号不同的值相乘得到负值,如果es不能表示乘积,则返回Infinity或-Infinity。
            • 如果由任一操作数NaN,则返回NaN。
            • 如果是Infinity乘以0,则返回NaN。
            • 如果是Infinity乘非0的有限数值,则根据第二个操作数的符号返回Infinity或-Infinity。
            • 如果是Infinity乘以Infinity,则返回Infinity。
            • 如果又不是数值的操作数,则先在后台用Number()将其转换为数值,然后再应用上述规则。
        • 除法操作符
          • 触发操作符由一个斜杠(/)表示,用于计算第一个操作数除以第二个操作数的商。
            • 如果操作数都是数值,则执行常规的除法运算,即两个正值相除是正值,两个负值相除也是正值,符号不同的值相除得到负值。如果es不能表示商,则返回Infinity或-Infinity。
            • 如果由任一操作数是NaN,则返回NaN。
            • 如果是Infinity除以Infinity,则返回NaN。
            • 如果是0除以0,则返回NaN。
            • 如果是非0的有限制除以0,则根据第一个操作数的符号返回Infinity或-Infinity。
            • 如果是Infinity除以任何数值,则根据第二个操作数的符号返回Infinity或-Infinity。
            • 如果有不是数值的操作数,则先再后台用Number()函数将其转换为数值,然后再应用上述规则。
        • 取模操作符
          • 取模(余数)操作符由一个百分比符号(%)表示。
            • 如果操作数是数值,则执行常规除法运算,返回余数。
            • 如果被除数是无限值,除数是有限制,则返回NaN。
            • 如果被除数是有限制,除数是0,则返回NaN。
            • 如果是Infinity除以Infinity,则返回NaN,
            • 如果被除数是有限值,除数是无限值,则返回被除数。
            • 如果被除数是0,除数不是0,则返回0.
            • 如果有不是数值的操作数,则先再后台用Number()函数将其转换为数值,然后再应用上述规则。
      • 指数操作符
        • es7新增了指数操作符,Math.pow()现在有了自己的操作符**,结果是一样的:
          console.log(Math.pow(3,2)); // 9
          ​console,log(3 ** 2); // 9
      • 加性操作符
        • 加性操作符
          • 加法操作符(+)用于两个数的和。
          • 如果两个操作数都是数值,加法操作符执行加法运算并根据如下规则返回结果:
            • 如果有任一操作数是NaN,则返回NaN。
            • 如果是Infinity加Infinity,则返回Infinity。
            • 如果是-Infinity加-Infinity,则返回-Infinity。
            • 如果是Infinity加-Infinity,则返回NaN。
            • 如果是+0加+0,则返回+0.
            • 如果是-0加-0,则返回-0.
            • 如果是+0加-0,则返回+0;
            • 不过,如果有一个操作数是字符串,则要应用如下规则:
              • 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面;
              • 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接到一块。
            • 如果有任一操作数是对象、数值或布尔值,则调用他们的toString()方法以获取字符串,然后再应用前面的关于字符串的规则。对于undefined和null,则调用String()函数,分别获取”undefined“和”null“。
        • 减法操作符
          • 减法操作符(-)也是使用很频繁的一种操作符
          • 与加法操作符一样,减法操作符也有一组规则用于处理es中不同类型之间的转换。
            • 如果两个操作数都是数值,则执行数学减法芋艿四年并返回结果。
            • 如果有人以操作数是NaN,则返回NaN。
            • 如果是Infinity减Infinity,则返回NaN。
            • 如果是-Infinity减-Infinity,则返回NaN。
            • 如果Infinity减-Infinity,则返回Infinity。
            • 如果是-Infinity减Infinity,则返回-Infinity。
            • 如果是+0减+0,则返回+0。
            • 如果是+0减-0,则返回-0。
            • 如果是-0减-0,则返回+0.
            • 如果有任一操作数是字符串、布尔值、null或undefined,则现在后台使用Number()将其转换为数值,然后再根据前面的规则执行数学运算。如果转换结果是NaN,则减法计算的结果是NaN。
            • 如果有任一操作数是对象,则调用其valueOf()方法取得表示它的数值。如果该值是NaN,则减法计算的结果是NaN。如果对象没有valueOf()方法,则调用其toString()方法,然后再将得到的字符串转换为数值。
      • 关系操作符
        • 关系操作符执行比较两个值的操作,包括小于(<)、大于(>)、小于等于(<=)和大于等于(>=).
        • 与es中其他操作符一样,在将它们应用到不同数据类型是也会发生类型转换和其他行为。
          • 如果操作符都是数值,则执行数值比较。
          • 如果操作数都是字符串,则逐个比较字符串中对应字符的编码。
          • 如果有任一操作数是数值,则将另一个操作数转换为数值,执行相互之比较。
          • 如果有任一操作数是对象,则调用其valueOf()方法,取得结果后再根据前面的规则执行比较,如果没有valueOf()操作符,则调用toString()方法,取得结果后再根据前面的规则执行比较。
          • 如果有任一操作数是布尔值,则将其转换为数值再执行比较。
      • 相等操作符
        • 等于和不等于
          • es中的等于操作符用两个等于号(==)表示,如果操作数相等,则回返回true。不扽古操作符用叹号和等于号(!=)表示,如果两个操作数不相等,则会返回true。这两个操作符都会先进行类型转换(通常称为强制类型转换)在确定操作数是否相等。
          • 在转换操作数的类型时,相等和不相等操作符遵循如下规则:
            • 如果任意操作数是布尔值,则将其转换为数值在比较是否相等。false转换为0,true转换为1。
            • 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,在比较是否相等。
            • 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法取得其原始值,再根据前面的规则进行比较。
            • null和undefined相等。
            • null和undefined不能转换为其他类型的值再进行比较。
            • 如果有任一操作数是NaN,则相等操作符返回false,不相等操作符返回true。
            • 如果两个操作数都是对象,则比较它们是不是同一个对象,如果两个操作数都指向同一个对象,则相等操作符返回true。否则,两者不相等。
          • 下表总结了一些特殊情况及比较的结果

             

        • 全等和不全等
          • 全等和不全等操作符与相等和不相等操作符类似,只不过它们在比较相等时不转换操作数。全等操作符由3个等于号(===)表示,只有两个操作数再不转换的前提下相等才返回true。
      • 条件操作符
        • 条件操作符时es中用途最为广泛的操作符之一,
          variable = boolean_expression ? true_value:false_value

      • 赋值操作符
        • 乘后赋值(*=)
        • 除后赋值(/=)
        • 取模后赋值(%=)
        • 加后赋值(+=)
        • 减后赋值(-=)
        • 左移后赋值(<<=)
        • 右移后赋值(>>=)
        • 无符号右移后赋值(>>>=)
      • 逗号操作符
        • 逗号操作符可以用来在一条语句中执行多个操作,如:
          let num = 1,num =2,num = 3;

    • 函数
      • 函数
    • 语句
      • if语句
        • if语句时使用最频繁的语句,语法:
          if(条件){
          //条件成功后的逻辑
          ​}else if(条件){
          //条件成功后的逻辑
          ​}else{ // 除了if和if-else中的其他结果
          // 条件成功后的逻辑
          ​}
      • do-whild语句
        • do-while语句是一种后测试循环语句,即循环体中的代码执行后才会对退出条件进行求值。换句话说,循环体内的代码至少执行一次。语法:
          do{
          // 逻辑代码
          ​}whild(条件)

          ​//例子
          ​let i=0;
          ​do{
          i += 2;
          ​}whild(i<10);
      • while语句
        • while语句时一种先测试循环语句,即先检测退出条件,再执行循环体内的代码。因此,while循环体内的代码有可能不执行。语法:
          while(条件){
          //逻辑代码
          ​}
          ​// 例子
          ​let i=0;
          ​while(i<10){
          i++;
          ​}

      • for语句
        • for语句也是先测试语句,只不过增加了进入循环之前的初始化代码,以及循环执行后要执行的表达式,语法:
          for(初始化变量;条件判断;变量改变){
          // 逻辑代码
          ​}
          ​// 例子
          ​let count = 10;
          ​for(let i=0;i<count;i++){
          console.log(i);
          ​}
      • for-in语句
        • for-in语句是一种严格的迭代语句,用于枚举对象中的非符号键属性,语法:
          for(property in expression){
          // 逻辑代码
          ​}

          ​//例子
          ​for(const propName in window){
          ​ document.write(propName);
          ​}
      • for-of语句
        • for-of语句是一种严格的迭代语句,用于遍历可迭代对象的元素,语句:
          for(property of expression){
          // 逻辑代码
          ​}

          ​// 例如
          ​for(const el of [2,4,6,7]){
          ​ console.log(el);
          ​}
      • 标签语句
        • 标签语句用于给语句加标签,语法
          label:statement
          ​// 例子
          ​start:for(let i=0;i<count;i++){
          ​ console.log(i);
          ​}
        • 在上述例子中,start是一个标签,可以在后面通过break或continue语句引用。标签语句的典型应用场景时嵌套循环。
      • break和continue语句
        • break和continue语句为执行循环代码提供了更严格的控制手段。其中,break语句用于立即退出循环,强制执行循环后的下一条语句。而continue语句也用于立即退出循环,但会再次从循环顶部开始执行。例子:
          let num = 0;
          ​for(let i=1;i<10;i++){
          if(i % 5 == 0){
          ​ break;
          ​ }
          ​ num++;
          ​}
          ​console.log(num); // 4



          ​let num = 0;
          ​for(let i=1;i<10;i++){
          if(i % 5 == 0){
          ​ continue;
          ​ }
          ​ num++;
          ​}
          ​console.log(num); // 8
      • with语句
        • with语句的用途时将代码作用域设置为特定的对象,语法:
          with(expression) statement;
        • 使用with语句的主要场景是针对液体个对想法反复操作,这时候将代码作用域设置为该对象能提供遍历,如:
          let qs = location.search.substring(1);
          ​let hostName = location.hostname;
          ​let url = location.href;

          ​// 改为
          ​with(location){
          let qs = search.substring(1);
          ​ let hostName = hostname;
          ​ let url = href;
          ​}
      • switch语句
        • switch语句是与if语句紧密相关的一种流控制语句,从其他语言借鉴而来。语法:
          switch(expression){
          case value1:
          statement​
          ​ break;
          case value2:
          statement​
          ​ break;
          ​ case value3:
          statement​
          ​ break;
          ​ default:
          ​ statement;
          ​}
        • 上述例子中,value可以是数值、字符串、布尔值、表达式。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值