avatar

目录
前端干货 Superdry

JavaScript 是ー门用来与网页交互的脚本语言,包含以下三个组成部分:

  • ECMAScript:由 ECMA-262 定义并供核心功能;ECMA-262 是标准,ECMAScript 是它的实现。
    • ECMA-262 定义了什么:语法、类型、语句、关键字、保留字、操作符、全局对象。
    • ECMAScript:只是对实现这个规范描述的所有方面的一门语言的称呼(伪语言)。JavaScript 实现了 ECMAScript,而 Adobe ActionScript 同样也实现了 ECMAScript。
  • 文档对象模型(DOM):提供与网页内容交互的方法和接口。
  • 浏览器对象模型(BOM):提供与浏览器交互的方法和接口。
  • ECMA-262 标准是由欧洲计算机制造商协会(Ecma)的第 39 技术委员会(TC39) 承担制订。
  • DOM 的标准是由万维网联盟(W3C, World Wide Web Consortium)制订。
  • BOM 是没有相关标准的 Javascript 实现。因为没有标准,每个浏览器实现的都是自己的 BOM。有一些所谓的事实标准,比如对于 window 对象和 navigator 对象,每个浏览器都会给它们定义自己的属性和方法。现在有了 HTML5,BOM 的实现细节应该会日趋一致。

数据类型

  • 基础(简单 | 原始 | 值)数据类型: undefinednullBooleanNumberSymbolBigIntString
  • 引用(复杂)数据类型 ————ObjectObject 本质上是由一组无序的键值对组成的。

Number 数字类型

包含:常规数字、NaN、Infinity。不包含 BigInt

NaN

not a number:不是一个数(非有效数字)但它率属于数字类型

NaN 和任何值(包括自己)都不相等:NaN !== NaN,所以我们不能用相等的方式判断是否为有效数字

isNaN

  • window.isNaN(全局的)

    检测一个值是否为 非有效数字,如果不是有效数字返回 true,反之返回 false

    在使用 isNaN 进行检测的时候,首先会验证检测的值是否为数字类型,如果不是,先基于 Number() 这个方法,把值转换为数字类型,然后再检测

    JavaScript
    1
    2
    isNaN('Tim') → isNaN(Number('Tim')) → IsNaN(NaN) → true
    isNaN('12.3') → isNaN(Number('12.3')) → IsNaN(12.3) → false
  • Number.isNaN

    检测一个值是否为 数字值 并且是否等于 NaN,不会转型,一句话概括:某个值是否 恒等于 NaN

    JavaScript
    1
    2
    3
    Number.isNaN('Tim') → 字符串直接返回 false
    Number.isNaN(12.3) → 12.3 !== NaNfalse
    Number.isNaN(NaN) → true

toString() 方法转换 Number 为字符串

  • (numObj).toString(radix)
  • radix:转换的进制数 (从 2 到 36)。如果未指定 radix,则默认值为 10。
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 数字类型直接调用方法是有个小坑,当整型调用时会报错,引擎把调用的方法的 “.” 当成小数点了
// 所以调用时最好用括号括起来,或使用变量
777.toSting() // Uncaught SyntaxError: Invalid or unexpected token

(88).toString(2) // "1011000"

// 浮点型可以直接调用,就是写法丑了点
2.3.toString() // "2.3"

let num = 254;
num.toString(16) // "fe"

// 利用 36 进制,获取 a-z 字母
let a_z = [];
for(let i = 10; i < 36; i++){
a_z.push(i.toString(36))
}

把其它类型值转换为数字类型

  • Number([val])
  • parseInt(string,[radix])string:参数不是一个字符串,则将其转换为字符串,转换时从左到右依次查找有效数字字符,忽略空格,直到遇到非有效数字字符,停止查找(不管后面是否还有数字,都不再找了),把找到的当做数字返回。radix 转换的进制数 (从 2 到 36)。注意,默认值不是 10,如果 radix 是 undefined、0 或未指定的,JavaScript 会假定以下情况:
    • 如果输入的 string 以 “0x” 或 “0X”(一个 0,后面是小写或大写的 X)开头,那么 radix 被假定为 16,字符串的其余部分被当做十六进制数去解析。
    • 如果输入的 string 以 “0”(0)开头, radix 被假定为 8(八进制)或 10(十进制)。具体选择哪一个 radix 取决于实现。ECMAScript 5 澄清了应该使用 10 (十进制),但不是所有的浏览器都支持。因此,在使用 parseInt 时,一定要指定一个 radix。 如果输入的 string 以任何其他值开头,或 radix 指定的是 0,parseInt 默认是十进制解析。
  • parseFloat(string) 只解析 10 进制,不能指定进制,其他与 parseInt 无差。
  • == 进行比较的时候,可能要出现把其它类型值转换为数字
JavaScript
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 把字符串转换为数字,只要字符串中包含任意一个非有效数字字符(第一个点除外)结果都是 NaN,
// 空字符串会变为数字零
Number("12.5"); // → 12.5
Number("12.5px"); // → NaN
Number("12.5.5"); // → NaN
Number(""); // → 0

// 布尔转换为数字
Number(true); // → 1
Number(false); // → 0
isNaN(false); // → false

// null → 0 undefined → NaN
Number(null); // → 0
parseInt(null); // → NaN 😢
Number(undefined); // → NaN

// 把引用数据类型转换为数字,是先把他基于 toString 方法转换为字符串,然后在转换为数字
Number({ name: "10" }); // → NaN
Number({}); // → NaN
// {}/{xxx:'xxx'} .toString() → "[object Object]" → NaN
Number([]); // → 0
// [].toString() → ''
Number([12]); // → 12
// [12].toString() → '12'
Number([12, 23]); // → NaN
// [12,23].toString() → '12,23'

let str = "12.5px";
Number(str); // → NaN
parseInt(str); // → 12
parseFloat(str); // → 12.5
parseFloat("width:12.5px"); // → NaN
parseFloat(""); // → NaN 因为没找到有效数字字符 ⭐️

// 以下俩个例子均返回 NaN:
parseInt("Hello", 8); // → NaN 根本就不是数值
parseInt("51146", 2); // → NaN 除了“0、1”外,其它数字都不是有效二进制数字,从左到右找,第一个是 5 即无效。

parseInt("11546", 2); // → 3,只获取 “11”,“546” 忽略

BigInt(1.2); // 报错,浮点数不能转换 Bigint

i++ 与 ++i 细节

JavaScript
1
2
3
4
5
6
7
8
9
10
11
let i = '10';
i= i + 1 → '10' + 1 → '101'
i += 1 → '101'
i++ → i = 11
// i++ 和以上两种不完全一样,他是纯粹的数学运算
// i++ 和 ++i 都会是数学运算中的累加 1,区别是计算的顺序

let i = 1;
5 + (i++) // → 先算 5 + i = 6 然后 i 再加 1 (i = 2),这里加不加小括号都一样 → 6
i = 1;
5 + (++i) // → 先 i 累加 1,然后拿累加后的结果去和 5 运算 → 7

String 字符串数据类型

所有用单引号、双引号、反引号(撇 ES6 模板字符串)包起来的都是字符串

String 有些特殊:因为字符串具有可变的大小,所以显然它不能被直接存储在具有固定大小的变量中。由于效率的原因,我们希望 JavaScript 只复制对字符串的引用,而不是字符串的内容。但是另一方面,字符串在许多方面都和基本类型的表现相似,而字符串是不可变的这一事实(即没法改变一个字符串值的内容),因此可以将字符串看成行为与基本类型相似的不可变引用类型

String.length

该属性返回字符串中字符编码单元的数量。JavaScript 使用 UTF-16 编码,该编码使用一个 16 比特的编码单元来表示大部分常见的字符,使用两个代码单元表示不常用的字符。因此 length 返回值可能与字符串中实际的字符数量不相同。

String.length 是静态属性,因此不能修改它的值(与 Array.prototype.length 不同)。

JavaScript
1
2
3
4
5
6
let str = "Hello";
str.length; // → 5
str[0]; // → 'H'

str.length = 10; // 不报错,但是没有任何效果
str.length; // → 5

把其它类型值转换为字符串

  • [val].toString()
  • 字符串拼接
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 四则运算法则中,除加法之外,其余都是数学计算,只有加法可能存在字符串拼接
// 加法一旦遇到字符串,则不是数学运算,而是字符串拼接
console.log("10" + 10); // → '1010'
console.log("10" - 10); // → 0
console.log("10px" - 10); // → NaN

let a = 10 + null + true + [] + undefined + "🍌~" + null + [] + 10 + false;
/*
* 10 + null -> 10 + 0 -> 10
* 10 + true -> 10 + 1 -> 11
* 空数组变为数字,先要经历变为空字符串,遇到字符串,啥都别想了,直接变为字符串拼接
* 11 + [] -> 11 + '' -> '11'
* '11' + undefined -> '11undefined'
* ...
* '11undefined🍌~null10false'
*/
console.log(a);

字符串常用方法

  1. chartAt(n):根据索引获取指定位置的字符串 / chartCodeAt(n):根据索引获取指定位置字符串的 ASCII 码(Unicode 编码值)

    • param:n 指定索引
    • return:查找到的字符串(找不到返回的是空字符串不是 undefined)/ 对应的编码值
  2. 截取 ⭐️

    • substr(n,m):从索引 n(可负数,但 IE 不兼容)开始截取到 m 个字符,m 不写,截到末尾(后面一样) ;已过时,未来将可能会被移除掉

    • substring(n,m):从索引 n 开始提取到 m 处不含 m),返回新的字符串,且不会改动原字符串

      • 如果 n 或 m 小于 0 或为 NaN 则被当作 0,而大于 stringName.length 则当做 stringName.length
      • 如果 m 大于 n,则 substring 的执行效果就像两个参数调换了一样
      JavaScript
      1
      2
      3
      4
      5
      6
      7
      8
      var str = "Mozilla";
      // 输出 "Moz"
      console.log(str.substring(0, 3));
      console.log(str.substring(3, 0));
      console.log(str.substring(3, -3));
      console.log(str.substring(3, NaN));
      console.log(str.substring(-2, 3));
      console.log(str.substring(NaN, 3));
    • slice(n,m):从索引 n 开始提取到 m 处不含 m),返回新的字符串,且不会改动原字符串,但n、m 可以使用负数(负数就是倒数,或用字符串 length 加负数后的值),更灵活,用的多。⭐️

    JavaScript
    1
    2
    3
    4
    let str = "abcdefg";
    str.slice(-4, -2); // → de 倒数从 2 截取到 4,或 7 +(-4)截到 7 + (-2)
    str.slice(-4); // → defg
    Object.prototype.toString.call([]).slice(8, -1); // → Array
  3. 验证字符串是否存在 (两个方法都对查找的内容大小写敏感,在所有浏览器都兼容,Array 的这两个方法低版本浏览器不兼容)

    • indexOf(searchValue,fromindex):方法返回调用它的 String 对象中第一次出现的指定值的索引,从 fromIndex 处进行搜索。如果未找到该值,则返回 -1。
      • searchValue(必需)一个字符串表示被查找的值。如果没有提供确切地提供字符串,searchValue 会被强制设置为 undefined, 然后在当前字符串中查找这个值。
      • fromIndex(可选)表示开始查找的位置。可以是任意整数,默认值为 0。如果 fromIndex 小于 0,则查找整个字符串(等价于传入了 0)。如果 fromIndex 大于等于 str.length,则必返回 -1。
      • 返回值 指定值的第一次出现的索引;如果没有找到,则返回 -1。若被查找的字符串是一个空字符串,则返回值在 0—str.length 之间,即:
        • fromIndex 小于等于 0 时,返回 0;
        • fromIndex 大于 0 且小于等于 str.length 时,返回 fromIndex;
        • fromIndex 大于字符串长度 str.length 时,返回 str.length
    • lastIndexOf(searchValue,fromIndex):最后一次出现的索引。。。
    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var str = "Hello world!";
    str.lastIndexOf("Hello"); // 0
    str.lastIndexOf("World"); // -1
    str.lastIndexOf("world"); // 6

    "Blue Whale".indexOf("Blue"); // 返回 0
    "Blue Whale".indexOf("Blute"); // 返回 -1
    "Blue Whale".indexOf("Whale", 0); // 返回 5
    "Blue Whale".indexOf("Whale", 5); // 返回 5
    "Blue Whale".indexOf("", -1); // 返回 0
    "Blue Whale".indexOf("", 9); // 返回 9
    "Blue Whale".indexOf("", 10); // 返回 10
    "Blue Whale".indexOf("", 11); // 返回 10
    "Blue Whaleundefined".indexOf(); //10
  4. 大小写转换

    • toUpperCase():大写
    • toLowerCase():小写
  5. split([分隔符]):把字符串按照指定的分隔符转换为数组,(与数组 join 相对应),支持传递正则表达式

    JavaScript
    1
    2
    3
    4
    let time = '2019-10-21 15:29:36';
    // ?: 只匹配,不捕获,空格或 - 或 :
    time = time.split(/(?: |-|:)/g) // → ["2019", "10", "21", "15", "29", "36"]
    time = time[0] + '年' + time[1] + '月' + ...
  6. replace(新字符,老字符):实现字符串替换(经常伴随正则使用)

    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let str = "这么🍌枯燥🍌无聊🍌乏味";
    console.log(str.replace("🍌", "🍑"));
    // 不使用正则的情况下只能替换一次字符串
    // → '这么🍑枯燥🍌无聊🍌乏味'

    // 查找不重复字符串长度 ⭐️
    // 正则 “()\1” 代表对应分组出现一模一样的内容
    let str = "dddasdlkfjsssslkjk".replace(/([a-z])\1+/g, "|");
    // str → "|asdlkfj|lkjk"

    let res = str.split("|").sort((x, y) => y.length - x.length);
    // res → ["asdlkfj", "lkjk", ""]

Boolean 布尔数据类型

只有两个值 true / false

把其它类型值转换为布尔类型

只有 0-00n-0nNaN、空字符串("" or '' or `` 注:有空格就不是空字符串)、nullundefinedfalse9 个值转换为 false,这几个值也称为 虚值(falsy)(转换为布尔值时变为 false 的值)其余都转换为 true且无任何的特殊情况 ⭐️ ⭐️。

  • Boolean([val])
  • !/!!
  • 条件判断

null / undefined

null 和 undefined 都代表空

  • null:意料之中(一般都是开始不知道值,我们手动先设置为 null,后期再给予赋值操作)
JavaScript
1
2
3
4
5
// let num = 0;  一般最好用 null 作为初始的空值,
// 因为零不是空值,它在栈内存中有自己的存储空间
let num = null;
...
num = 12;
  • undefined:意料之外(不是我能决定的)。🪅:在 JavaScript 中 undefined 不是一个保留字,如果你闲得慌可以用它做变量名 。👿
JavaScript
1
2
3
let num; // → 创建一个变量没有赋值,默认值是 undefined
...
num = 12;

Object 对象数据类型 – 普通对象:无序的键值对集合

{[key]:[value],...} 任何一个对象都是由零到多组键值对(属性名:属性值)组成的(并且属性名不能重复)
数组是特殊的对象数据类型

JavaScript
1
2
3
// → 如果属性名是数字,则不能使用点的方式获取属性值
console.log(person[1]);
console.log(person.1); // → SyntaxError:语法错误

JavaScript 隐式转换 ⭐️⭐️

涉及隐式转换最多的两类运算符 ==+-×÷

  • == 进行比较的时候,如果左右两边数据类型不一样,则先转换为相同的数据类型,然后再去比较

    1. {} == {} 两个对象进行比较,比较的是堆内存地址
    2. null == undefined 相等的,null === undefined 不相等
    3. NaN == NaN 不相等,NaN 和谁都不相等
    4. [12] == "12" 对象与字符串比较,先调用对象 valueOf()Number() 转换,如果转换结果为 NaN,再调用 toString() 转换为字符串后再进行比较
    5. 剩余所有的情况在进行比较的时候,都是转换为数字前提数据类型不一样
      • 对象转数字:先转换为字符串,然后再转换为数字
      • 字符串转数字:只要出现一个非数字字符,结果都是 NaN
      • 布尔转数字: true → 1 false → 0
      • null 转数字为 0,但当 null == 0 是不会转换类型,返回 false
      • undefined 转数字为 NaN

    小结:null、undefined、NaN 为特殊;对象与非对象比较就先 toString() 后再比较;其余都转为数字再比较

    JavaScript
    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
    27
    28
    29
    30
    31
    32
    33
    34
    [12] == trueNumber([12].valueOf()) → 12 == 1 false
    [] == falseNumber([].valueOf()) → 0 == 0 true // 注意:Number(" ") 空格会忽略,还是 0
    [] == 10 == 1 false
    "1" == 11 == 1 true
    true == 21 == 2 false
    [] == ![] → [] == !true → [] == false → [] == 00 == 0true // 操作符优先级导致其实是不同数据类,一个是数组,一个 bool,所以是转换为数组再比较 ⭐️⭐️

    {} == 1 → 语法错误 // Uncaught SyntaxError: Unexpected token '==' {} 被单独解析了
    if({} == 1){ ... } // 加了小括号是没有问题的

    {"obj" : 2} == {"obj" : 2} → false // 比较的是地址
    '{"obj":2}' == {"obj" : 2} → '{"obj":2}' == "[object Object]"false // 对象与字符串比较,先把对象 `toString()` 转换为字符串后再进行比较

    ({"obj" : 7}).toString() == {"obj" : 2,"obj1":7} → "[object Object]" == "[object Object]"true

    // 这是转型
    String('Hello') === 'Hello'true
    // 这是创建一个 字符串对象
    new String('Hello') === 'Hello'false

    // 空 [] 和空 {},在 if 判断是 true ⭐️
    if({} && []){ console.log(123) } // 123

    // 什么时候用 `==` ?
    if (obj.a == null) {
    // 这里相当于 obj.a === null || obj.a === undefined 的简写形式,jQuery 推荐写法
    // 其他应该都用 `===` 严格(恒)等于
    }
    null 只和 undefined 相互比较时为 true,除了他俩之外的比较都为 false
    null == undefinedtrue
    undefined === undefinedtrue
    null === nulltrue
    null === undefinedfalse
    虽然 null 转换数字为 0,但当 null == 0 是不会转换类型的 🤯 → false ⭐️
  • +-×÷ 隐式转换

    • 非数字 Boolear 开头除外,+ 默认 toString() 再拼接,Boolear 开头会转换数字再加
    • 遇到 -×÷ 引擎先调用 Number() 再做对应的运算,具体怎么转换 参看上面 Number() 转换的示例
    JavaScript
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    true + true + false = 2
    true + false + "yoo~" = "1yoo~"
    "yoo~" + true + false ="yoo~truefalse"

    '123'+ NaN ="123NaN"
    NaN + '123' ="NaN123"

    // undefined、NaN 跟任何数值计算都是 NaN
    undefined + NaN = NaN
    undefined + 0 = NaN
    NaN + 123 = NaN

    2 + '12' - '2' * 2 = 208

    var a; var b = a * 0;
    if(b == b){
    console.log(b * 2 + "2" - 0 + 4);
    }else{
    // undefined、NaN 属于虚值一员,取反等于 true
    console.log(!b * 2 + "2" - 0 + 4);
    }
    //→ 26

    // Infinity 布尔值转换为 true
    var a=1; var b = a / 0;
    if(b == b){ // b = Infinity
    console.log(b * 2 + "2" - 0 + 4); // Infinity + "2" → "Infinity2" - 0 → NaN + 4 → NaN
    }else{
    console.log(!b * 2 + "2" - 0 + 4);
    }
    //→ NaN

    [] + {} → [] + "[object Object]""" + "[object Object]""[object Object]"
    [] - {} → 0 + Number({}) → 0 + Number('[object Object]') → 0 + NaNNaN
    {} + [] → {} 在前,整个语句并没有被解析成一个运算表达式,而是 {} 被单独解析,真正的语句的值就成了 +[] → 0
    {foo:'bar'} + 1 // 一样的道理
    ({} + []) → ("[object Object]" + []) → "[object Object]"

    // 值前面的 +,相当于 Number() 转换该值,这也是种转换技巧,类似 “!” 取 bool 一样
    +null0
    +undefinedNaN


    var a; var b = a * 0;
    if(b == b){
    console.log(b * 2 + "2" - 0 + 4);
    }else{
    // undefined、NaN 取反等于 true
    console.log(!b * 2 + "2" - 0 + 4);
    }
    // 26

    // Infinity 布尔值转换为 true
    var a=1; var b = a / 0;
    if(b == b){ // b = Infinity
    console.log(b * 2 + "2" - 0 + 4); // Infinity + "2" → "Infinity2" - 0 → NaN + 4 → NaN
    }else{
    console.log(!b * 2 + "2" - 0 + 4);
    }
    // NaN

JavaScript 显式转换

强制是将值转换为另一种类型的方法,我们需要手动转换。参考上面各数据类型中的转换。

什么是包装对象(wrapper object)⭐

我们现在复习一下 JavaScript 的数据类型,JavaScript 数据类型被分为两大类,基本类型和引用类型。

基本类型:undefined,null,Boolean,Number,String,Symbol,BigInt

引用类型:Object,其中 Array,Date,RegExp,Math 等都隶属于 Object,说白了就是对象。

其中引用类型有方法和属性,但是基本类型是没有的,但我们经常会看到下面的代码:

JavaScript
1
2
3
4
let name = "marko";

console.log(typeof name); // "string"
console.log(name.toUpperCase()); // "MARKO"

name 类型是 string,属于基本类型,所以它没有属性和方法,但是在这个例子中,我们调用了一个 toUpperCase() 方法,它不会抛出错误,还返回了对象的变量值。

原因是基本类型的值被临时转换或强制转换为对象,因此 name 变量的行为类似于对象。除 null 和 undefined 之外的每个基本类型都有自己包装对象。也就是:String,Number,Boolean,Symbol 和 BigInt。在这种情况下,name.toUpperCase() 在幕后看起来如下:

JavaScript
1
console.log(new String(name).toUpperCase()); // "MARKO"

在完成访问属性或调用方法之后,新创建的对象将立即被丢弃。

Date()

创建一个新 Date 对 象的唯一方法是通过 new 操作符:
let now = new Date();
只能将 Date 作为构造函数调用,才能实例化(instantiate)Date 对象:若将它作为常规函数调用(即不加 new 操作符),则将会返回一个字符串,而非 Date 对象。另外,不像其他的 JavaScript 对象类型,Date 对象没有字面量语法(literal syntax)。

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var a = Date();
// a → "Tue Nov 19 2019 17:19:20 GMT+0800 (中国标准时间)"

// 获取 2017-02-12 格式日期
new Date("2017-2-2").toLocaleDateString().replace(/\//g, "-");

var d = new Date();
d.getFullYear(); // 获取年
d.getMonth(); // 获取月 结果是 0~11 代表第一月到第十二月
d.getDate(); // 获取日
d.getDay(); // 获取星期 结果是 0~6 代表周日到周六
d.getHours(); // 获取时
d.getMinutes(); // 获取分
d.getSeconds(); // 获取秒
d.getMilliseconds(); // 获取毫秒
d.getTime() | d.valueOf(); // 获取当前日期距离 1970/1/1 00:00:00 的毫秒差(时间戳)
d.toLocaleDateString(); // 获取年月日(字符串)
d.toLocaleString(); // 获取完整的日期字符串

// 获取时间戳有好几种方法(时间戳后三位是毫秒)
Date.now() | d.getTime() | d.valueOf() | d * 1 | Number(d);

// 时间戳转日期
new Date(时间戳);

Math

一个内置对象,它拥有一些数学常数属性和数学函数方法。Math 不是一个函数对象。与其他全局对象不同的是,Math 不是一个构造器。Math 的所有属性与方法都是静态的。不支持 BigInt。

JavaScript
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
console.log(typeof Math) // → "object"

Math = {
abs:function(){[native code]},
acos:function(){[native code]},
...,
ceil:function(){[native code]},
PI:3.1415926...,
...
}

// abs:取绝对值
// ceil(天花板):向上取整
Math.ceil(12.1) // → 13
Math.ceil(12.9) // → 13
Math.ceil(-12.1) // → -12
Math.ceil(-12.9) // → -12

// floor(地板):向下取整
Math.floor(12.1) // → 12
Math.floor(12.9) // → 12
Math.floor(-12.1) // → -13
Math.floor(-12.9) // → -13

// round:四舍五入
Math.round(12.1) // → 12
Math.round(12.5) // → 13 正数中,5 属于入
Math.round(-12.1) // → -12
Math.round(-12.5) // → -12 负数中,5 属于舍
Math.round(-12.5) // → -13

// min/max:([val1],[val2],...) : 一堆数(不是数组)最小最大值
Math.max(12,5,23,8,1,65,9) // → 65

// 实参只能是一个数,如穿数组与内置语法不符
Math.max([12,5,23,8,1,65,9]) // → NaN

// 数组中最大值 注意参数类型,如传数组用 apply,一堆数用 call
// call、apply 第一个参数如果是 null,undefined,this 指向 window / global
Math.max.apply(null,[12,5,23,8,1,65,9])

// 或使用扩展运算符
Math.max(...[12,5,23,8,1,65,9]) // → 65

// sqrt():开平方,只能开平方,不是立方
Math.sqrt(9) // → 3 符合 n * n = m 这样的 m 才能整开平方
Math.sqrt(-9) // → NaN 负数开不了平方

// pow():计算一个数几次幂
Math.pow(2,10) // 1024

// 生成一个浮点型伪随机数字,在 0(包括 0)和 1(不包括)之间
Math.random()

// 获取 N 到 M 之间整数,包含了 N 和 M,取值比 round 要平均 ⭐️
Math.floor(Math.random() * (M - N + 1) + N);

// 生成长度为 40,元素为 10 - 20 的随机数数组 ⭐️
Array.from({ length: 40 }, n => n = Math.floor(Math.random() * (20 - 10 + 1) + 10));

Aarry 有序的数据集合

JavaScript 的 Array 是可以存储任意值,所有当一个 Array 中元素的内型不统一时,存储的内存是非连续内存,相当于由多个非连续内存形成一个链表,这样数组操作效率会很低;而存储的如果是同一内型元素,即为连续内存数组,此时操作数组效率高。

  • ✂️:表示对原数组有影响(7 个)
  • 📌:表示对原数组无影响

数组增删改

  1. push() 将一个或多个元素添加到数组的末尾,返回数组 length ✂️

  2. unshift() 将一个或多个元素添加到数组的开头,返回数组 length ✂️

    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // unshift 插入时数组的已有所要元素都需要往后移动一位,所要性能较差
    var arr = [];

    const unshiftTimer = performance.now();

    for (let i = 0; i < 100000; i++) {
    arr.unshift(i);
    }
    console.log("unshift 插入 10 万个数据使用时间:", performance.now() - unshiftTimer);

    const pushTimer = performance.now();
    for (let i = 0; i < 100000; i++) {
    arr.push(i);
    }
    console.log("push 插入 10 万个数据使用时间:", performance.now() - pushTimer);

    // unshift 插入 10 万个数据使用时间: 767.745000001014
    // push 插入 10 万个数据使用时间: 3.8700000004610047
  3. shift() 删除开头,无参数,返回被删除元素 ✂️

    JavaScript
    1
    2
    3
    4
    // 基于原生 JavaScript 中的 Delete,把数组当做普通对象,确实可以删除掉某一项内容
    // 但是不会影响数组本身的结构(length 长度不会跟着修改),项目中应杜绝使用
    let ary = [10, 20, 30];
    delete ary[0]; // → {0:empty,1:20,2:30} ary[0] → undefined
  4. pop() 删除结尾,无参数,返回被删除元素 ✂️

    JavaScript
    1
    2
    3
    4
    const arr = [1, 2, 3];
    // 指定了参数也无效
    arr.pop(2);
    console.log(arr); // [1,2]
  5. splice(start[, deleteCount[, item1[, item2[, ...]]]]) 实现对数组增、删、改 ✂️

    • params:
      • start:开始位置(从 0 计数),超出了数组的长度,则从数组末尾开始添加内容,支持负数
      • deleteCount:移除的数组元素的个数,可选,整数,省略则 start 之后的所有元素都会被删除。非数字或小于等于 0 不会删除,非数字但转换非 NaN,则取转换后的值,非整数,则取向下取整的个数
        • n,m,x 从索引 n 开始,删除 m 元素,用 x 占用删除的部分
        • n,0,x 从索引 n 开始,一个都不删,把 x 放到索引 n 的前面
    • return :把删除的部分用新数组存储返回,没有删除的元素返回空数组 ⭐️
    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let ary = [10, 20, 30, 40];
    // 修改 :先删后增
    let res = ary.splice(1, 2, "yooo~", "煎饼果子");
    console.log(res, ary); //→ [20,30] [10,'yooo~','煎饼果子',40]
    // 实现增加
    ary.splice(3, 0, "切克闹"); //→ [10,'yooo~','煎饼果子',40,'切克闹']
    // 增加到末尾
    ary.splice(ary.length, 0, ["🍉", "🍒"]); //→ [10,'yooo~','煎饼果子',40,'切克闹',["🍉","🍒"]]
    // 增加到开头
    ary.splice(0, 0, {"🍌": "🍑"}); //→ [{"🍌":"🍑"},10,'yooo~','煎饼果子',40,'切克闹',["🍉","🍒"]]

数组查询

  1. slice([begin[, end]]) 📌

    • params:begin,end 都是可选、数字,从索引 begin 开始,找到索引为 end 的地方(包含 begin,但不包含 end)
    • return:把找到的内容以新数组的形式返回
      String.slice()Array.slice() 相似。
    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var s = "123456";
    var a = s.split("");
    // 用负数,实现往后查找
    a.slice(2, -2); // '3','4','5','6'

    // 负数就是从后算起,n 到 m 永远是个区间,所以 n 与 m 都是负值的话,
    // m 必须大于 n ⭐️
    a.slice(-3, -1); // '4','5'
    // m 不写,找到末尾
    a.slice(1); // '2','3','4','5','6'
    // 实现数组克隆(浅),参数 0 不写也可以
    a.slice(0);
  2. indexOf() / lastIndexOf():检测当前项在数组中出现的第一次或最后一次出现的索引(IE6~IE8 不兼容)📌

    • params:要检索的这一项内容
    • return:这一项出现的位置索引值,如果数组中没有这一项,返回 -1
    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let ary = [10, 20, 30, 40];
    // 判断某元素是否属于数组
    if (ary.indexOf("yoo~") === -1) {
    // 不包含
    }
    // 或使用 ES6 的 includes
    if (ary.includes("yoo~")) {
    // 包含
    }
  3. find() / findIndex(): 方法返回数组中满足提供的测试函数的第一个元素的值 / 索引。否则返回 undefined / -1;因为传递的参数是方法,所以查找数组元素为引用类型很方便 📌

    JavaScript
    1
    2
    3
    4
    5
    6
    7
    const array1 = [5, 12, 8, 130, 44];
    const found = array1.find((element) => element > 10);
    const foundIndex = array1.find((element) => element > 10);
    console.log(found, foundIndex); // 12,1

    let array2 = [{ name: "kaizi" }, { name: "25zai" }, { name: "bs" }];
    array2.find((e) => e.name == "25zai"); // {name:'25zai'}

数组拼接

  1. concat() 方法不会改变 this 或任何作为参数提供的数组,而是返回一个浅拷贝,它包含与原始数组相结合的相同元素的副本。📌

    • params:多个任意类型值
    • return:拼接后的新数组
    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    let a1 = [10, 50],
    a2 = [20, 50, 60, { key: "🍉🍉" }];
    let res = a1.concat("yoo~", a2);
    // 10,50,"yoo~",20,50,60,{key:"🍉🍉"}

    a2[3].key = "🍈🍈🍈";
    console.log(res);
    // 10,50,"yoo~",20,50,60,{key:"🍈🍈🍈"}

数组转为字符串

  1. toString() 📌

    • params:无
    • return:转换后字符串,每一项用逗号分隔
  2. join() 📌

    • params:指定的分隔符(字符串格式),默认是逗号
    • return:转换后字符串,每一项用指定分隔符
    JavaScript
    1
    2
    3
    let arg = [10, 20, 30];
    let res = arg.join("+");
    eval(res); // → 60 eval 把字符串转化成 js 表达

数组的排序

  1. reverse() 方法将数组中元素的位置颠倒,并返回该数组 ✂️

    • params:无
    • return:排列后的原数组
    JavaScript
    1
    [3,50,10,20,2].reverse()  → [2, 20, 10, 50, 3]
  2. sort()原地算法对数组的元素进行排序,并返回数组。默认升序,比较时会调用每项的 toString() 根据Unicode 码点比较 ✂️ ⭐️

    • params:可选,规定排序顺序,必须是函数。
    • return:排序后的原数组
    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var months = ["March", "Jan", "Feb", "Dec"];
    months.sort();
    console.log(months);
    // expected output: Array ["Dec", "Feb", "Jan", "March"]

    var arg = [1, 30, 4, 21, 100000];
    arg.sort(); // 10 以上排序处理不了
    console.log(arg);
    // expected output: Array [1, 100000, 21, 30, 4]
    // a,b 是相邻的两项
    // 如果 a > b return 大于零的值
    // a === b return 0
    // a < b return 小于零的值
    console.log(arg.sort((a, b) => a - b)); // 1,4,21,30,100000

    console.log(arg.sort((a, b) => b - a)); // 100000,30,21,4,1

数组的迭代(遍历)方法

  1. forEach(callback(currentValue [, index [, array]])[, thisArg]) 📌

    • params:
      1. callback 为数组中每个元素执行的函数,该函数接收三个参数:
        • currentValue: 数组中正在处理的当前元素。
        • ?index:可选 数组中正在处理的当前元素的索引。
        • ?array 可选 forEach() 方法正在操作的数组。
      2. ?thisArg 可选 可选参数。当执行回调函数 callback 时,用作 this 的值,利用这个参数可以执行一些骚操作。
    • return:undefined
    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function foo(el) {
    console.log(el, this.id);
    }
    var obj = {
    id: "awesome",
    };
    var myArray = [1, 2, 3];
    // 调用 foo(..) 时把 this 绑定到 obj
    myArray.forEach(foo, obj);
    // 1 awesome 2 awesome 3 awesome

    注意:没有办法中止或者跳出 forEach() 循环,除非抛出一个异常。若你需要提前终止循环,你可以使用:

    • 简单循环
    • for...of 循环
    • Array.prototype.every()
    • Array.prototype.some()
    • Array.prototype.find()
    • Array.prototype.findIndex()

    这些数组方法可以对数组元素判断,以便确定是否需要继续遍历:every()some()find()findIndex()

    若条件允许,也可以使用 filter() 提前过滤出需要遍历的部分,再用 forEach() 处理。

    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function logArrayElements(element, index, array) {
    console.log("a[" + index + "] = " + element);
    }

    // 注意索引 2 被跳过了,因为在数组的这个位置没有项
    [2, 5, , 9].forEach(logArrayElements);
    // logs:
    // a[0] = 2
    // a[1] = 5
    // a[3] = 9
  2. every() 判断所有元素是否符合条件,所有元素返回 truthy(真值),则返回 true 📌

    • params:与 forEach() 一致
    • return:true / false

    注:若收到一个空数组,此方法在任何条件下都会返回 true。

    JavaScript
    1
    2
    3
    4
    5
    function isBigEnough(element, index, array) {
    return element >= 10;
    }
    [12, 5, 8, 130, 44].every(isBigEnough); // false
    [12, 54, 18, 130, 44].every(isBigEnough); // true
  3. some() 判断一个元素符合条件,只要有一个元素符合条件,就返回 true 📌

    • params:与 forEach() 一致
    • return:true / false

    注:若收到一个空数组,此方法在任何条件下都会返回 false

    JavaScript
    1
    2
    3
    4
    5
    6
    function isBiggerThan10(element, index, array) {
    return element > 10;
    }

    [2, 5, 8, 1, 4].some(isBiggerThan10); // false
    [12, 5, 8, 1, 4].some(isBiggerThan10); // true
  4. map() 对元素重新组装,生成新数组(得注意元素值为引用类型)📌

    • params:与 forEach() 一致
    • return:一个新数组,每个元素都是回调函数的结果

    注:如数组的元素是引用型,直接操作元素会有可能改原数组值

    map

    JavaScript
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    var numbers = [1, 4, 9];
    var roots = numbers.map(Math.sqrt);
    // roots 的值为 [1, 2, 3], numbers 的值仍为 [1, 4, 9]

    var elems = document.querySelectorAll("select option:checked");
    var values = Array.prototype.map.call(elems, function (obj) {
    return obj.value;
    });

    // 下面的语句返回什么呢:
    ["1", "2", "3"].map(parseInt);
    // 你可能觉的会是 [1, 2, 3]
    // 但实际的结果是 [1, NaN, NaN]

    // 通常使用 parseInt 时,只需要传递一个参数
    // 但实际上 parseInt 可以有两个参数。第二个参数是进制数
    // 可以通过语句 "alert(parseInt.length)===2" 来验证
    // map 方法在调用 callback 函数时,会给它传递三个参数:当前正在遍历的元素,
    // 元素索引,原数组本身
    // 第三个参数 parseInt 会忽视,但第二个参数不会,也就是说
    // parseInt 把传过来的索引值当成进制数来使用,从而返回了 NaN

    function returnInt(element) {
    return parseInt(element, 10);
    }

    ["1", "2", "3"].map(returnInt); // [1, 2, 3]
    // 意料之中的结果

    // 也可以使用简单的箭头函数,结果同上
    ["1", "2", "3"].map((str) => parseInt(str));

    // 一个更简单的方式:
    ["1", "2", "3"].map(Number); // [1, 2, 3]
    // 与`parseInt` 不同,下面的结果会返回浮点数或指数:
    ["1.1", "2.2e2", "3e300"].map(Number); // [1.1, 220, 3e+300]

    // 注意引用类型细节,有可能会改变原数组值
    let ar = [
    { name: "html", score: 90 },
    { name: "js", score: 80 },
    { name: "css", score: 70 },
    ];
    let newAr = ar.map((n, i) => {
    if (n.name === "js") {
    n.score += 10;
    // 使用 数组拷贝或者返回一个新对象
    // return {name:n.name,score:(n.score + 10)}
    }
    return n;
    });
    console.table(ar); // js 也会加 10
    console.table(newAr);

    // 第三个参数 Array 的使用场景,直接变更原数组
    // 原数组加 10
    let ar = [1, 2, 3, 4, 5];
    ar.map((item, index, array) => {
    array[index] += 10;
    });
    console.log(ar);
  5. filter() 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素 📌

    • params:与 forEach() 一致
    • return:一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,则返回空数组。

    filter

    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var fruits = ["apple", "banana", "grapes", "mango", "orange"];

    function filterItems(query) {
    return fruits.filter(function (el) {
    return el.toLowerCase().indexOf(query.toLowerCase()) > -1;
    });
    }

    console.log(filterItems("ap")); // ['apple', 'grapes']
    console.log(filterItems("an")); // ['banana', 'mango', 'orange']

数组的归并

  1. reduce() 方法对数组中的每个元素执行一个由您提供的 reducer 函数 (升序执行),将其结果汇总为单个返回值。 📌⭐️⭐️

    • params:
      1. callback 执行数组中每个值 (如果没有提供 initialValue 则第一个值除外) 的函数,包含四个参数:
        • Accumulator (acc) (累计器)
        • Current Value (cur) (当前值)
        • ?Current Index (idx) (当前索引)
        • ?Source Array (src) (源数组)
      2. ?initialValue:作为第一次调用 callback 函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。
    • return:最终累计处理的结果

    reduce

    此图更好理解:
    reduce

    如果你打算提供一个初始值作为 reduce() 方法的第二个参数,以下是运行过程及结果:

    JavaScript
    1
    2
    3
    4
    [0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array)
    => {
    return accumulator + currentValue;
    }, 10 );
    callback accumulator(累计器) currentValue(当前值) currentIndex array return value
    first call 10 0 0 [0, 1, 2, 3, 4] 10
    second call 10 1 1 [0, 1, 2, 3, 4] 11
    third call 11 2 2 [0, 1, 2, 3, 4] 13
    fourth call 13 3 3 [0, 1, 2, 3, 4] 16
    fifth call 16 4 4 [0, 1, 2, 3, 4] 20

    这种情况下 reduce() 返回的值是 20。

    如果没有给初始值,first call 的 accumulator 值为 0,currentValue 值为 1,整体会少循环一次。

    JavaScript
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    let arr = [1, 3, 5, 7, 9];

    // 取最大值 (应用到查找购物车最贵商品)
    arr.reduce((acc,item)=> acc > item ? acc : item);

    // 累加
    arr.reduce(function (acc, item) {
    console.log(acc * 10 ,item)
    return acc * 10 + item;
    });
    // 10 3
    // 130 5
    // 1350 7
    // 13570 9
    // → 13579

    //累加对象里面值
    [{x: 1}, {x:2}, {x:3}].reduce((acc, item) => acc + item.x, 0);

    //累计次数
    var a = "assscccasdfwqeree"
    var arr =a.split("");
    arr.reduce((acc,item) => {
    if(item in acc){
    acc[item]++
    }else{
    acc[item]=1;
    }
    return acc;
    },{})

    // 替换字符串的里的关键字
    const word = ["css","js"]
    const str = "学习 css 和 js"
    word.reduce((acc,item) => acc.replace(item,`<a>${item}</a>`),str);


    // 二维转一维 ⭐
    var array = [[2,4],[3,4]];
    arry.reduce((acc,item) => acc.concat(item),[])
    // 或
    [].concat(...array);

    // 多维转一维 ⭐⭐
    var arr1 = [1,2,3,[1,2,3,4, [2,3,4]]];
    function flatten(arr){
    return arr.reduce((a,v) => Array.isArray(v) ? a.concat(flatten(v)) : a.concat(v),[])
    }
    flatten(arr1)

    // 数组去重
    // 兼容性性能较优方法
    let obj = {};
    for(let i = 0; i < arr.length; i++){
    let item = arr[i];
    if(obj[item]!=undefined){
    // arr.splice(i,1); // 删除当前项
    // i--; // 解决数组塌陷问题
    // splice 删除是把选择的元素删除,后面元素都往前移,
    // 如果数组元数很多,性能不是很好;
    // 于是可以把数组最后一项替换当项即可
    arr[i] = arr[length-1]; // 替换
    arr.length--; // 再删除
    i--; // 这里 i-- 替换的元素需再比对一次
    continue;
    }
    obj[i]=i;
    }

    // 解法 2
    arr.reduce((a,c)=>
    {
    if(a.indexOf(c)==-1){
    a.push(c)
    }
    return a;
    },[])

    // (5) [1, 2, 3, 5, 4]

    // 解法 3
    arr = [...new Set(arr)];

    // 类数组转换数组
    // querySelectorAll 获取的是 NodeList,只能算类数组,是没有数组相关方法和特性的。
    var list = document.querySelectorAll('a');
    [].slice.call(list);// ES5 写法
    Array.from(list) | [...list] // ES6 写法,推荐使用...


    // 使用 reduce 链式获取对象子属性的值
    const data = {
    more:{
    like:{
    coding:false,
    doTa:true
    }
    }
    }

    const getObjectValue = (obj,path) => {
    return path.split('.').reduce((acc,item) => acc[item],obj)
    }

    getObjectValue(data,'more.like.coding')
  2. reduceRight():作用与 reduce() 无差,只是从数组的右边开始遍历。

题目

将数组 const arr = [1,2,3],修改成为 [4,3,2,1] 下面方式正确的是?

  • A: arr.reverse().unshift(4)
  • B: arr.push(4).reverse()
  • C: arr.push(4); arr.reverse()
  • D: arr.splice(3,1,4).reverse()
答案

答案: A,C

B 错在 a.push(4) 返回的是 a.length 是个数字, 数字是没有 reverse()
D a.splice(3,1,4) 返回的是删除元数组成的新数组,这里的结果是个空数组

Symbol

Symbol 是 JavaScript 的 原始数据类型,表示独一无二的值,可以充当唯一 ID 的令牌,可理解为 GUID,一般用作保证每个属性的名字都是独一无二
在一些编程语言中 Symbol 也被称为原子 (atoms)
Symbol 的描述是可选的,但仅用于调试目的
不支持 new Symbol() 声明,因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。⭐

和 Symbol() 不同的是,用 Symbol.for() 方法创建的的 symbol 会被放入一个全局 symbol 注册表中。Symbol.for() 并不是每次都会创建一个新的 symbol,它会首先检查给定的 key 是否已经在注册表中了。假如是,则会直接返回上次存储的那个。否则,它会再新建一个,类似单例模式。

JavaScript
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
Symbol('加描述不加描述都行') == Symbol('加描述不加描述都行');// false

// 创建 全局 symbol
Symbol.for('namespace.global1') == Symbol.for('namespace.global1') // true

// 解决的对象属性名产生冲突,这也是 ES6 引入 Symbol 的原因。
let user1 = { name:'李四', key:Symbol()},
user2 = { name:'李四', key:Symbol()};
let score = {
[user1.key]:{js:100,css:99},
[user2.key]:{js:35,css:55}
}

console.log(score[user2.key]);

class Cache {
static data = {};
static set(name,value){
return (this.data[name] = value);
}
static get(name){
return this.data[name];
}
}

// 保证对象唯一
let user = {
name:'apple',
desc:'用户资料',
key:Symbol('用户资料')
};
let cart = {
name:'apple',
desc:'购物车',
key:Symbol('购物车资料')
};
Cache.set(user.key, user);
Cache.set(cart.key, cart);
console.log(Cache.get(user.key)));

// 私有属性
let site = Symbol('地址保密');
class User{
constructor(name){
this.name = name;
this[site] = '兰山';
}
getName(){
return `${this[site]} ${this.name}`;
}
}

let user1 = new User('三儿');
console.log(user1); // 兰山 三儿
for(const key in user1){
console.log(key) // 只输出 name
}

遍历

Symbol 作为属性名,遍历对象的时候,该属性不会出现在 for…in、for…of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。
但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols() 方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

JavaScript
1
2
3
4
5
6
7
8
9
const obj = {};
let a = Symbol("a");
let b = Symbol("b");

obj[a] = "Hello";
obj[b] = "World";

const objectSymbols = Object.getOwnPropertySymbols(obj);
// [Symbol(a), Symbol(b)]

另一个新的 API,Reflect.ownKeys() 方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。

JavaScript
1
2
3
4
5
6
7
8
let obj = {
[Symbol("my_key")]: 1,
enum: 2,
nonEnum: 3,
};

Reflect.ownKeys(obj);
// ["enum", "nonEnum", Symbol(my_key)]

DOM

文档对象模型(DOM, Document Object Model)是一个应用编程接口(API),用于在 HTML 中使用扩展的 XML。DOM 将整个页面抽象为ー组分层节点。DOM 通过创建表示文档的树,让开发者可以随心所欲地控制网页的内容和结构。使用 DOM API 可以轻松地删除、添加、替换、修改节点。

获取 DOM 元素的方法

  • document.getElementById() 指定在文档中,基于元素的 ID 或者这个元素对象
  • [context].getElementsByTagName() 在指定上下文(容器)中,通过标签名获取一组元素集合
  • [context].getElementsByClassName() 在指定上下文中,通过样式类名获取一组元素集合(不兼容 IE6~8)
  • document.getElementsByName() 在整个文档中,通过标签的 NAME 属性值获取一组节点集合(在 IE 中只有表单元素的 NAME 才能识别,所以我们一般只应用于表单元素的处理)

除了 ById 其他 getElements 都要加 s

  • document.head / document.body / document.documentElement 获取页面中的 HEAD/BODY/HTML 三个元素
  • [context].querySelector([selector]) 在指定上下文中,通过选择器获取到指定的元素对象,上下文中有多个元素也只获得一个
  • [context].querySelectorAll([selector]) 在指定上下文中,通过选择器获取到指定的元素集合,上下文中捕获只有元素也返回一个元素集合DOM 通过创建表示文档的树,让开发者可以随心所欲地控制网页的内容和结构。使用 DOM API 可以轻松地删除、添加、替换、修改节点

NodeList 不是一个数组,是一个类似数组的对象(Like Array Object).
虽然 NodeList 不是一个数组,但是可以使用 forEach() 对其进行迭代。
还可以使用 Array.from() 将其转换为实际数组,方便使用 Array 的方法。

JavaScript
1
2
3
4
5
// →  querySelector / querySelectorAll 不兼容 IE6~8
let box = document.querySelector("#box");
let links = box.querySelectorAll("a");
// links=document.querySelectorAll('#box a');
let aas = document.querySelectorAll(".aa");

JavaScript 中的节点和描述节点之间关系的属性

节点:Node(页面中所有的东西都是节点)

节点集合:NodeList(getElementsByName / querySelectorAll 获取的都是节点集合)

  • 元素节点(元素标签)
    • nodeType:1
    • nodeName: 大写的标签名
    • nodeValue :null
  • 文本节点
    • nodeType:3
    • nodeName: ‘#text’
    • nodeValue:文本内容
  • 注释节点
    • nodeType:8
    • nodeName: ‘#commen’
    • nodeValue:注释内容
  • 文档节点 document
    • nodeType:9
    • nodeName : ‘#document’
    • nodeValue :null
  • ……

描述这些节点之家关系的属性

  • childNodes:获取所有的子节点
  • children:获取所有的元素子节点(子元素标签集合)
  • parent:获取父亲节点
  • firstChild:获取第一个子节点
  • lastChild:获取最后一个子节点
  • firstElementChild / lastElementChild :获取第一个和最后一个元素子节点(不兼容 IE6~8)
  • previousSibling:获取上一个哥哥节点
  • nextSibling:获取下一个弟弟节点
  • previousElementSibling / nextElementSibling :获取哥哥和弟弟元素节点(不兼容 IE6~8)
  • ……

在 JavaScript 中动态增删改元素

createElement 创建元素对象

createTextNode 创建文本对象

appendChild 把元素添加到容器的末尾

insertBefore 把元素添加到指定容器中指定元素的前面

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 动态创建一个 DIV 元素对象,把其赋给 BOX
let box = document.createElement("div");
box.id = "boxActive";
box.style.width = "200px";
box.style.height = "200px";
box.className = "RED";

// 动态创建一个文本
let text = document.createTextNode("yooo~");

// 添加:容器.appendChild(元素)
box.appendChild(text);
// document.body.appendChild(box);

// 放到指定元素前:容器.insertBefore([新增元素],[指定元素])
let haha = document.getElementById("haha");
// haha.parentNode.insertBefore(...)
document.body.insertBefore(box, haha);

cloneNode(true/false) 克隆元素或者节点

removeChild 移除容器中的某个元素

html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="box">
<span>yooo~1</span>
</div>
<script>
let box1 = document.querySelector(".box");
// 克隆第一份(深克隆)
let box2 = box1.cloneNode(true);
box2.querySelector("span").innerText = "yooo~2";
// 克隆第二份(浅克隆)
let box3 = box1.cloneNode(false);
box3.innerHTML = "<span>yooo~3</span>";

document.body.appendChild(box2);
document.body.appendChild(box3);

//===========
// 容器.removeChild(元素)
document.body.removeChild(box2);
</script>

setAttribute / getAttribute / removeAttribute 设置获取移除元素的自定义属性信息(这种方式是把自定义属性放到元素结构上)

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var btnList = document.querySelectorAll("button");
for (var i = 0; i < btnList.length; i++) {
// 设置自定义属性:元素对象。属性名 = 属性值(原理是向元素对象对应的堆内存中添加了一个属性)
// btnList[i].myIndex = i;

// 设置自定义属性:基于 SET-ATTRIBUTE 是把属性信息写到了元素标签的结构上(在结构中可以看到的),并没有放到元素对象对应的堆内存中
btnList[i].setAttribute("data-index", i);
btnList[i].onclick = function () {
// 获取自定义属性:元素对象。属性名(原理是从堆内存中获取到对应的属性值)
// alert(this.myIndex);

// 基于 GET-ATTRIBUTE 可以把结构上存储的自定义属性值获取
alert(this.getAttribute("data-index"));
};
}

attribute 和 property 区别

  • propertyDOM 中的属性,是 JavaScript 里的对象;
  • attributeHTML 标签上的特性,它的值只能够是字符串;
  • 简单理解,Attribute 就是 dom 节点自带的属性,例如 html 中常用的 idclasstitlealign 等。
  • Property 是这个 DOM 元素作为对象,其附加的内容,例如 childNodesfirstChild 等,attributes 是属于 property 的一个子集。

documnet.write 只能重绘整个页面
innerHTML 可以重绘页面的一部分

JavaScript
1
2
//获取页面选中的 checkbox
document.querySelector("input:checked");

DOM 事件

  1. Dom 事件级别(版本)

    Dom0 element.onclick = function(){}
    Dom2 element.addEventListener('click',function(){},false) false(默认):冒泡时触发,true:捕获时触发
    Dom3 element.addEventListener('keyup',function(){},false)
    Dom1 制订的时候没有设计与事件相关的东西,所有没有 Dom1 ,Dom2 与 Dom3 写法没差别,Dom3 多了许多事件类型;

  2. Dom 事件模型(捕获,冒泡)⭐

    捕获:上到下(windowdocumenthtmlbody→ 目标元素)
    冒泡:下到上(目标元素 → bodyhtmldocumentwindow

  3. Dom 事件流

    事件流:捕获(阶段)→ 目标阶段 → 冒泡(阶段)

  4. Dom 捕获流程 (参考 2)

  5. Event 对象常用应用

    • event.preventDefault():阻止默认行为(如 a 标签:设置了 click 事件,阻止其默认点击跳转)
    • event.stopPropagation():阻止捕获和冒泡阶段中当前事件的进一步传播
    • event.stoplmmediatePropagetion():事件优先级设置
    • event.currentTarget: 当前绑定事件元素,父级元素(事件代理)⭐
    • event.target:获取具体点击元素 ⭐
  6. 自定义事件

    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //与其他事件结合使用
    var eve = new Event("test");
    ev.addEventListener("test", function () {
    console.log("test dispatch");
    });

    setTimeout(function () {
    ev.dispatchEvent(eve); //调用是对象,不是名称
    }, 1000);

BOM Browser Object Model

浏览器对象模型(BOM)API,用于支持访问和操作测览器的窗口,只使用 BOM,开发者可以操控浏览器显示页面之外的部分,它是唯一一个有相关标准的 JavaScript 实现。

  • navigator.userAgent
  • screen (screen.width,screen.height)
  • location
    1. location.href
    2. location.protocol // ‘http:’ ‘https:’
    3. location.host // ‘www.baidu.com'
    4. location.pathname // ‘/className.html’
    5. location.search // ‘?id=xx&&a=b’ 问号后面的值
    6. location.hash // #mid=123312 #后面值 锚点
  • history
    1. history.back()
    2. history.forward()
文章作者: Tim
文章链接: http://w3ctim.com/post/e2f52f48.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Tim

评论