前端学习笔记(二十九)

469 阅读15分钟

学习了一些基础语法。包括 map,set,原始值和引用值,字符串方法,数组方法。
没什么好看的,全是红宝书上的内容,别看了。

1. Map

ES6语法,Map 用于代替 object 来储存键值对。

1.1 基础 API

has,get,set,delete,clear。

  1. 其中 set,返回 map,因此可以多个 set 连在一起使用:
const m = new Map();
m.set("a",1).set("b",2).set("c",3)
  1. map 的可以是任意数据结构。不像 object 有限制。

1.2 顺序

和 object 不同,map 实例会记录数据的插入顺序,map 实例会提供一个迭代器(这是 object 没有的)。
迭代器每次迭代返回 [key, value] 这样的数组。

// 使用 entries 获取迭代器
for (let pair of m.entries()) {
  console.log(pair); // [key, value]
}
// entries 为默认迭代器,不加也可以
for (let pair of m) {
  console.log(pair); // [key, value]
}
// 如果要专门迭代 key 或者 values,则要写
for (let key of m.keys()) {
  console.log(key);
}

2. set

2.1 基本 API

add,has,delete,clear。
和 map 相比,get,set 变成了 add。
add 也是返回新实例,因此也可以连着使用。

2.2 迭代

和 map 一样的迭代方式,有 keys,values,entries,默认是 values,keys 和 values 相等。因此 entries 其实是一对重复值。

3. 数字

  • NaN 指的是 not a number,是一种特殊的数字:typeof NaN === "number"
  • 0/0 会得到 NaN,5/0,5/+0,5/-0 会得到 +/-Infinity。
  • NaN 不等于自身,NaN !== NaN
  • 将非数值转化为数值有三种方法:
    • Number,能够接受的信息最多,无论什么格式都可以尝试转化。
    • parseInt,只能转化字符串,且只能转化为整数,接受第二个参数表示以什么进制来解析字符串里的数字。比如 parseInt("10", 2) 和 parseInt("10", 10)。前者返回值为 2,后者为 10.
      • 推荐永远写上第二个参数,即使是十进制
      • parseInt("1234red", 10); // 解析为 1234
      • parseInt("red1234", 10); // 解析为 NaN,因为第一个字符解析失败立刻返回 NaN
    • parseFloat,和 parseInt 一样,但是可以转化小数(整数当然也可以解析)。此外,parseFloat 没有第二个参数,只能解析为十进制

4. 字符串

  • 像很多语言一样,js 中字符串也是不可变的。看着像是更改的操作在内部都是先销毁再复制。
  • 其他类型转为字符串有两种方法:
    • toString() 方法,几乎所有的类型都有这种方法。 null 和 undefined 没有。(NaN 是有的,因为它毕竟是数字类型,转化为字符串 "NaN")
      • 在对数字使用 toString() 的时候,可以接受一个参数,表示以几进制展示该数字。
        • let a = 255; a.toString(2); // 输出 "11111111"
    • String() 函数。当不确定是否要转化 null,undefined 的时候,用这个函数。当为 null 和 undefined 的时候,返回 "null" 和 "undefined"。其他时候会调用 toString() 方法。
  • 模板字符串 ``
  • 原始字符串: 模板字符串依然会对 \n 等解释为换行符。如果想纯粹使用字符串。则需要用到原始字符串: String.raw`first\nsecond`,输出 "first\nsecond",而不是换行。

5. 操作符

    • 号会对数据进行转化,将其转化为数字,转化方式和 Number 一样。
    • 和 + 一样,只是会在转换后添加负号

5.1 位操作符

js 里的二进制为 32 位,10010 实际上是 0000 0000 0000 0000 0000 0000 0001 0010。其中第 32 位(最左边的),表示符号。0 为 正,1 为 负。

  • 按位非:~,对单个数使用,二进制的每一位取反。由于第三十二位符号位也会取反,因此按位非之后不仅数值变化,符号也会变化。
  • 按位与:&,对两个数使用,顾名思义
  • 按位或:|,对两个数使用,顾名思义
  • (注意)按位异或:^,当两边只有 1 个为 1 的时候返回 1,不要和 R 里的 n 次方搞混了,在 js 里这个表示的是异或。
  • 左移:<<,对单个数使用。如 10010 << 5 === 1001000000。左移会保留符号,空位(右边)补 0.
  • 有符号右移:>>,对单个数使用。如1010010 >> 5 === 101。空位(左边,符号位之后)补 0。
  • 无符号右移:>>>,对于单个数使用。对于正数,表现和有符号一样。对于负数,由于负数是以二补数的形式表现,得到的结果将会是一个非常大的正数。

5.2 指数操作符

**,这个是ES7的语法。代替 Math.pow()

5.3 相等操作符

== 判断相等前会做类型转换,因此类型不一样也会返回 true。
=== 全等,数据类型不同会返回 false。

6. 标签语句

与 break,continue 一起使用,可以跳到特定的循环。

outermost:
for (let i in a) {
  for (let j in b) {
    break outermost; // 直接跳到最外层,两个循环都不再执行了。
    break; // 只跳出一层循环。
  }
}

7. 原始值和引用值

  • 和很多语言不同,js 中的字符串是原始值,是按值访问的。
  • 函数的参数全部是按值复制的,所有参数都是局部变量。
    • 那么有个问题,如果都是复制,那为什么引用值的改变会影响到外部变量呢?
      • 因为复制的仅仅是一个指针,指向的和外部变量的是同一个内存地址。
      • 函数执行完毕后,垃圾回收掉该参数,不会对外部变量有影响。

8. 日期

  • Date 类型的 toLocaleString,toString 显示的内容会根据浏览器而不同。如果想要保持一致,应该获取特定部分再组合。
  • 获取特定部分的方法:getFullYear(),getMonth(),getDate(),getDay(),getHours(),getMinutes(),getSeconds(),getMilliSeconds()

9. 原始值包装类型

之前提到字符串是原始值,但是 String 是引用值。这个叫做原始值包装类型。类似的还有 Boolean,Number。这三种被称作原始值包装类型,都是引用值。但是其对应的布尔值,数字,字符串都是原始值。

// 这三个都是原始值
const a = 1;
const b = true;
const c = "yukari";
typeof a; // "number"
typeof b; // "boolean"
typeof c; // "string"
// 这三个是引用值
const ar = new Number(1);
const br = new Boolean(true);
const cr = new String("yukari");
typeof ar; // "object"
typeof br; // "object"
typeof cr; // "object"

每当创建原始值的时候,其实 js 内部都创建了一个对应的原始值包装类型。这样就可以使用包装类型的方法了。比如说字符串的 substring() 等等。const s1 = "hello".substring(2)
这很合理,因为原始值是不应该有方法的,但很多时候我们对原始值使用方法都没问题,这都是因为背后有着原始值包装类型在工作。
实际上后台在每次原始值调用方法的时候都会执行三步,①创建一个包装类型实例②在这个实例上调用方法③销毁该实例。
也就是说变量本身还是原始值。只是拥有引用值的调用方法行为而已。

let s1 = "color text";
s1.color = "red"; // 生成了一个包装类型,确实也给这个包装类型添加了 color 属性,但是立刻就销毁了。
console.log(s1.color); // undefined,因为上一行生成的包装类型立刻就销毁了。这一行又是新生成的一个实例,并没有 color 属性。
  • 注意:Number() 和 new Number() 不一样,前者为普通函数,作用为把各种数据类型转化为数字。后者为构造函数,用于生成原始值包装类型。前者结果的 typeof 为 "number",后者为 "object"。
  • 一般不推荐使用原始值包装类型,仅用于后台。

10. String 方法

当谈到字符串方法的时候,实际上是在说 String 包装类型的方法。

10.1 拼接:concat()

拼接字符串,大部分时候 + 更方便。

"yukari".concat(" cannot", " beat ", "murasaki"); // "yukari cannot beat murasaki"

10.2 子字符串:slice(), substring(), substr() 方法以及区别

都是剪切出子字符串的方法。

  1. slice, substring 使用方法相同,第一个参数表示开始,第二参数表示结束(不包括结束)。
    • substring 当第二个参数比第一个小的时候,会调换位置
    • slice 则会返回一个空字符串
  2. substr,第二个参数表示子字符串的长度。
  • 这些方法都不会有 index 错误,比如 "asd".substr(1,2000) 会返回 "sd"
    • 嘛,毕竟本来 "asd"[2000] 也只是返回 undefined。
  1. 省略第二个参数都意味着选取后面的所有。
  2. 当参数有负值的时候,表现又有不一样了。
    • substring 会把所有负值变成 0
    • substr 会把第一个参数的负值变成字符串长度加上负值,第二个参数变为0(别忘了这个是长度为 0 的意思,也就是说返回一个空字符串)
    • slice 会把所有负值都变成字符串长度加上该负值
    • 字符串长度加上负值,其实就是倒着数的意思
  3. NaN 都会被转成 0

10.3 寻找位置 indexOf(),indexLastOf()

寻找某个子字符串的位置 一个从前面开始找,一个从后面开始找。
为什么要分成两个方向呢,因为还能接受第二个参数,表示开始找的位置。一个向前找,一个向后找。这样区别就大了。

10.4 包含

这是 Es6 新增的方法。返回的都是布尔值。

  1. startsWith() 和 endsWith(),顾名思义,字符串是否以参数为开头或者结尾。
  2. includes(),是否包含子字符串。
  3. startsWith() 和 includes() 可以接受第二个参数,表示从哪里开始往找。
  4. endsWith() 也接受第二个参数,不过表示的是从哪里开始往找(换句话说,以什么位置作为结尾,前面的 startsWith 也可以换句话说以什么位置作为开始)。

10.5 trim()

新生成一个字符串,去掉原始字符串头尾的空格符。
还有顾名思义的 trimLeft() 和 trimRight()

10.6 repeat()

将字符串复制n遍。参数为复制多少遍。

const a = "Aqua";
a.repeat(3); // "AquaAquaAqua"

10.7 padStart, padEnd

接受一个数字参数,当字符串长度不够参数的时候,往前或者往后添加空格直到该长度。
也可接受第二个参数,表示当你不想用空格的时候,可以自定义填充字符。

10.8 迭代

字符串可以迭代

10.9 大小写转换

  • toLowerCase() 和 toUpperCase()。顾名思义。

11. eval()

和 python 里的一样,接受一个字符串,把这个字符串当做代码来执行。
eval 里的变量和函数不会被提升。
严格模式下,eval 内部定义的函数和变量,外部是无法访问的。

12. Array

12.1 Array.from 和 Array.of

这两个是 ES6 新增的语法。

12.1.1 Array.from

第一个参数是任何一个可迭代的对象,将其转化为数组。像 map 的键值对就会被转成 [[key1,value1], [key2, value2], [key3, value3]] 的形式。如果参数是数组,则会进行浅复制。
第二个参数是一个函数,作用和 Array.map 一样,直接改变新数组的值。

12.1.2 Array.of

把一组参数转化成数组。

Array.of(1,2,3,4); // [1,2,3,4]

12.2 数组空位

比如 [1,,,,3],但是不同方法对空位的处理不一样,为了保持一致性,可以把每个空位显式赋值为 undefined。

12.3 length

数组的 length 不是只读的,可以通过更改 length 来从末尾添加或删除元素。
如果改变 length 为更小的值,则从末尾删除元素。
如果改变 length 为更大的值,则末尾为空值。

let a = [1,2,3,4];
a.length = 3; // a === [1,2,3]
a.length = 4; // a === [1,2,3,empty]

12.4 Array.isArray()

判断一个变量是否为数组

let a = [];
Array.isArray(a); // true

12.5 迭代器方法

Es6 中 Array 暴露了 keys,values,entries 方法。
keys 为索引,values 为值,entries 为 [index, value]

for (const [index, value] of [1,2,3]) {
  console.log(index);
  console.log(value);
}

12.6 填充方法 fill(), copyWithin()

首先注意这两个方法都不会改变数组的长度。

  1. fill() 顾名思义给数组填充。接受3个参数。
    第一个参数为要填充的值,必填。
    第二个参数为填充开始的索引。
    第三个参数为填充结束的索引。
    如果不声明后两个参数则为填充所有。
    不声明第三个则为填充从开始索引到数组最后。
  2. copyWithin() 也是填充,但是是复制数组某个范围的值来填充。
    第一个参数为要填充的开始索引,必填。
    第二个参数为复制的数组的开始索引。
    第三个参数为复制的数组的结束索引。
    如果不声明后面两个参数则为默认第一个参数为0。 如果不声明第三个参数则为从开始索引直到数组最后。

12.7 数组转换方法

  1. toString():把所有元素变成字符串,再用逗号拼接(注意没有空格)。

    [1, 2, 3, 4].toString(); // "1,2,3,4"
    
  2. valueOf():返回数组自身

  3. join():使用指定参数字符串拼接数组中的元素。

    [1,2,3,4].join(","); // 相当于 toString()
    

12.8 栈方法

push() 和 pop(),会在数组末尾添加和删除元素。返回值都为添加/删除的那个值。

12.9 队列方法

push() 和 shift(),push() 不仅是栈方法也是队列方法,因为两者都是从末尾添加元素。区别在于取出元素,队列是从头部取数据。因此使用的是 shift() 方法。
shift() 方法会删除数组头部的元素,返回该值。

还有一个 unshift() 方法,可以在数组开头添加数据。

12.10 排序方法

12.10.1 数组反向 reverse()

顾名思义,数组反向排列即可。

[2,34,1,34].reverse(); // "34,1,24,2",只是单纯反向。

12.10.2 排序 sort()

将数组排序。
注意:sort() 会在排序前将元素转化为字符串,因此对于数字排序是有问题的。需要靠接受的函数参数来解决

// 由于转成字符串,排序数字会有问题
[1,2,3,4,5,10,20].sort(); // [1,10,2,20,3,4,5] ???????????????????????????
// 因此需要参数函数来帮助
[1,10,2,20,3,4,5].sort( (a,b) => a-b ); // 数字从小到大排序,改成 b-a 就是从大到小排序

该函数参数被称为比较函数,接受两个数值。如果要升序排序,则应该当第一个参数小于第二个参数,函数返回负值,大于的时候返回正值,相等的时候返回 0。比如:

.sort( (a,b) => a<b ? -1 : a>b ? 1 : 0 ); // 适用于多种情况,不只是数值
.sort( (a,b) => a<b ? 1 : a>b ? -1 : 0 ); // 降序版

不过如果是数值,可以就用上上段代码,返回 a-b 即可。

12.11 操作方法

  1. concat(),该方法可以接受多个参数,当参数为数组的时候,把这个数组摊开再添加到原数组上,当参数不是数组的时候,直接把这个元素加到原数组后面。返回值为新数组,原数组不会改变。
const a = [1,2,3,4];
const b = a.concat(5,"6",[{7:8}, 9]);
console.log(b); // [1,2,3,4,5,"6",{7:8},9]

可以用符号阻止数组被摊开:

const a = [1,2,3];
const b = [4,5];
b[Symbol.isConcatSpreadable] = false;
a.concat(b); // [1,2,3,[4,5]]
  1. slice() 和字符串的行为类似,只是元素变成了数组元素。 返回值为新数组。

  2. splice() 更强大的数组方法,有多种使用方法:

  3. 删除:传两个参数,第一个参数表示要删除的元素的位置,第二个参数表示要删除多少个元素。

  4. 替换:传三个或以上参数,上面的基础上,传第三个参数,表示在删除的部分插入的元素,还可以第四个,第五个等等,注意这里的参数和 concat 不一样的是,这里如果传数组不会被摊开。

  5. 插入:如果第二个参数设置为 0,其实就是相当于插入了。 和前面两个方法不同,splice 是直接改变原数组而不是创建一个新数组。
    splice 的返回值是被删除的元素组成的数组。

12.12 搜索方法

  1. indexOf,lastIndexOf,includes 和字符串一样
  2. find,findIndex 方法:这两个方法分别返回第一个匹配参数函数的元素和该元素的索引。
    • 接受的第一个函数被称作断言函数,只有匹配该函数的元素会被返回。
    • 而 find 和 findIndex 就会返回第一个匹配该断言函数的元素/索引(或者说第一个在断言函数里返回 true 的元素/索引)
    • 断言函数接受三个参数,分别为(当前元素,当前元素的索引,数组本身)/(item, index, array)
      const a = [1,2,3,4,5,6];
      a.find((element,index,array) => { return (element === 4) }); // 返回 4
      
    • find 和 findIndex 除了第一个参数(断言函数)之外,还可以接受第二个参数,可以指定断言函数内部的 this 的值。

12.13 迭代方法

  • React 里经常会用到的 map,就属于迭代方法的一种。
    迭代方法接受两个参数,第一个参数为函数,对数组里的每个元素运行,第二个参数为该函数里的 this 值。
    数组有 5 个迭代方法:
  1. every,如果数组每一项都返回 true,则整体返回 true
  2. filter,只有返回 true 的元素会在最后组成数组被返回
  3. forEach,仅仅只是对每一项运行函数,相当于遍历数组
  4. map,把每一项的返回值组成数组
  5. some,和 every 对应,只要有一项返回 true,则整体返回 true

迭代方法接受的参数为 (item, index, array)

12.14 归并方法

reduce() 和 reduceRight(),前者从头到尾,后者从尾到头。
归并指的是,遍历每一项,但是和迭代方法不一样,只返回一个综合的值。
接受的参数为 (prev, cur, index, array),其中 prev 为上次遍历的返回值。cur 为这次遍历的元素。

[1,2,3,4].reduce( (prev, cur) => {
  return (prev + cur);
}) // 数组里所有的元素加和。此例返回 10