看完这篇文章,你将了解:

  • splice 方法不仅能删除元素,还能添加元素
  • Object.prototype.valueOf 方法
  • forEach 的 this 与遍历机制
  • 事件的冒泡属性
  • 七种类型在 typeof 运算符下的返回值

一. Array.prototype.splice

有一道题目,问我 Array 方法中哪个不能添加元素。排查之后只剩 pop 和 splice。

一查 MDN,发现 splice 不仅可以删除元素,还可以替换元素。而当删除元素为 0 时,相当于添加元素

语法是

array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
  • start: 表示修改开始位置,可以为负,负数表示倒数
  • deleteCount: 可选,表示删除元素个数,可以省略,省略表示后面全删
  • item1, ...: 可选,表示替换元素

比如:

  • 从第 2 位开始删除 0 个元素,插入“drum”

    var myFish = ["angel", "clown", "mandarin", "sturgeon"];
    var removed = myFish.splice(2, 0, "drum");
    
    // 运算后的 myFish: ["angel", "clown", "drum", "mandarin", "sturgeon"]
    // 被删除的元素: [], 没有元素被删除
  • 从倒数第 2 位开始删除 1 个元素

    var myFish = ['angel', 'clown', 'mandarin', 'sturgeon'];
    var removed = myFish.splice(-2, 1);
    
    // 运算后的 myFish: ["angel", "clown", "sturgeon"]
    // 被删除的元素: ["mandarin"]

splice(a, b, c) 读作:从第 a 位起删除 b 个元素,插入 c (a 从 0 计数)

二. Object.prototype.valueOf

先看一段代码

let i = 1 + {valueOf: function() { return 9 }}
// i === 10

MDN 是这么说的:

JavaScript calls the valueOf method to convert an object to a primitive value. You rarely need to invoke the valueOf method yourself; JavaScript automatically invokes it when encountering an object where a primitive value is expected.

JS 调用 valueOf 方法将一个对象转换成原始类型。

每个对象都自带 valueOf 函数因此很少需要你自己动手实现

当一个对象期望为原始类型值的时候,就会调用 valueOf

所以说,当下面语句

1 + obj
+obj

出现的时候,obj 需要将自己转换成数字类型,这时会调用自身的 valueOf 方法转换成数字。

而 valueOf 方法可以自己覆盖,所以上面的代码返回 10

三. forEach 的 this 与遍历机制

先看一段令人困惑的代码

new Array(5).forEach(() => console.log(this))
// undefined

在我的设想里,新建长度为 5 的数组后,应该遍历输出 5 次函数环境下的 this。

根据 MDN,this 的值应该是 window

arr.forEach(callback, thisArg)

  • callback 依次传入三个参数(值,下标,数组)
  • thisArg 是独立于 callback 的参数,用于传入 callback 成为其 this 值

...

如果 thisArg 参数有值,则每次 callback 函数被调用的时候,this 都会指向 thisArg 参数上的这个对象。如果省略了 thisArg 参数,`或者赋值为 nullundefined,则 this 指向全局对象

上面的代码,只传入第一参数 callback,省略第二参数 thisArg。因此 this 应为 window

事实上,代码却没有输出 5 个 window。

为了定位问题,这里做几个试验

[1, 2, 3, 4, 5].forEach(() => console.log(this))
// window * 5
// 说明非构造函数生成的数组是正常的

var arr = new Array(5)
arr.forEach(() => console.log(this))
// undefined
// 说明跟数组对象是否为匿名对象无关

new Array(1, 2, 3, 4, 5).forEach(() => console.log(this))
// window * 5
// 找到了,说明跟构造函数成员是否为空有关

经过试验,终于确定了问题所在。原来并不是 this 的问题,单纯只是 new Array(5) 调用的 forEach 里,回调函数一次也没有执行

查看 stackoverflow 上的相关问答,终于搞懂了原因

原来,forEach 遍历是按成员遍历不是按长度遍历

当我们 new Array(5) 的时候,返回 [empty, empty, empty, empty, empty]。empty 并不是成员值,因此不会遍历。比如:

var arr = new Array(5)

arr.forEach(function(elem, index, array) {
    console.log(index);
});
//prints nothing

arr[3] = 'hey'
arr.forEach(function(elem, index, array) {
    console.log(index);
});
//prints only 'hey' even though arr.length === 5

注意:没有值并不等于 undefined,比如:

arr = [undefined, undefined, undefined, undefined, undefined]
arr.forEach(() => console.log(this))
// 5 * window

下面 forEach 的一个简易实现,可以彻底说明问题

Array.prototype.my_for_each = function(callback, thisArg) {
    for (var i = 0; i < this.length; i++) {
        if (i in this) {
            callback.call(thisArg, this[i], i, this);
        }
    }
};

// new Array(2)
// {length: 2}

// new Array(1, 2)
// {"0": 1, "1": 2, length: 2}

// [undefined, undefined]
// {"0": undefined, "1": undefined, length: 2}

// var arr = [1]; arr[3] = 1
// {"0": 1, "3": 1, length: 4}

重复一遍,forEach 是按成员遍历,不是按长度遍历

四. 事件的冒泡属性

冒泡,就是一个元素上的事件触发后会在父元素上也触发

不是所有的事件都像 click 一样,点了一个元素后,所有父元素的点击事件也会触发。比如 img 的 onload 事件,并不会冒泡到父元素

怎么知道一个事件是否冒泡?所有 event 都有一个 event.bubbles 属性,可以知道它是否冒泡

一般而言,以下三种事件不冒泡

  • UI事件

    • load
    • unload
    • scroll
    • resize
  • 焦点事件

    • blur
    • focus
  • 鼠标事件

    • mouseleave
    • mouseenter

五. JS 七种类型的 typeof 返回值

JS 有 七种类型:

Null, undefined, Number, String, Boolean, Symbol, Object

分别用 typeof 调用它们

typeof null    //"object"
typeof undefined    //"undefined"
typeof Number()    //"number"
typeof String()    //"string"
typeof Boolean()    //"boolean"
typeof Symbol()    //"symbol"
typeof Object()    //"object"
typeof Function()    //"function"

可以看到,大部分情况返回值按前面的记就行。只有两个例外:

  • typeof null 值为 "object"
  • typeof Function() 值为 "function"

六. 细节

  • deferral 和 promise 的关系:都是异步函数,前者是 jquery 版的,后者是 es6 版的
  • 原生 js 中,获取父元素的语句是:xx.parentNode。注意 parentNode 不是方法
  • 十六进制转十进制:parseInt('0xA')

金句

  • splice 不仅可以删除元素,还可以替换元素,添加元素
  • splice(a, b, c) 读作:从第 a 位起删除 b 个元素,插入 c
  • 对象转换原始类型时会调用 valueOf
  • forEach 遍历是按成员遍历不是按长度遍历
  • typeof 的返回值有两个例外,null 和 Function