资讯专栏INFORMATION COLUMN

JavaScript易错知识小点

rottengeek / 1567人阅读

摘要:字符串在字符串中找到匹配的字面量,只能替换一次出现的匹配项,暂不存在函数,想要替换多次出现的匹配项,必须使用正则表达式标识。这符合赋值改变但不破坏继承属性的理念。二十七数组构造函数判断是否为一个数组,能够正确处理跨域对象。

一、String, Number, Boolean等包装类型是对象,JavaScript没有比较对象的方法,即使通过宽松比较相等==
var a = new String("abc");
var b = new String("abc");
console.log(a == b) // false
console.log(a === b) // false
二、对包装类型转换为原始值只能正确提取出字符串和数字,而不布尔值不能
Boolean(new Boolean(false)) // true
Number(new Number(123)) // 123
String(new String("abc")) // abc

new Boolean(new Boolean(false)) // true对象
三、String.prototype.split(separator?, limit?)

作用: 通过指定边界(separator)将字符串分割成子字符串,返回字符串组成的数组。

separator: 可选,可以为字符串或正则表达式。不传的话,将原字符串封装在数组中返回

limit: 可选, 若传入,最多返回数组中limit个子字符串

注:若separator为包含子表达式分组的正则表达式,子表达式匹配的字符串也会作为数组中的元素返回。

"a, b   ,   ".split(/(,)/)
# ["a", ",", " b   ", ",", "   "]
四、String.prototype.match(regexp)

作用: 捕获分组或返回所有匹配的子字符串

注: 若regexp未设置全局标志/g, 则返回一个对象(数组: 存在index和input属性),存放第一次匹配相关的信息;若设置了/g标志,则将所有匹配的子字符串(不包含分组元素)放在一个数组中返回; 如果未匹配到任何子字符串,返回null

var a = "-abb--aaab".match(/(a+)b/) // ["ab", "a"]
a.index === 1 // true
a.input === "-abb--aaab" // true
var b = "-abb--aaab".match(/(a+)b/g) // ["ab", "aaab"]
b.index === undefined // true
b.input === undefined // true
var c = "-abb--aaab".match(/(a+)bc/g) // null
c === null // true
五、String.prototype.replace(search, replacement)

存在两个参数: search, replacement

search: 字符串或者正则表达式。

1) 字符串: 在字符串中找到匹配的字面量,只能替换一次出现的匹配项,暂不存在
    (replaceAll函数),想要替换多次出现的匹配项,必须使用正则表达式/g标识。
2) 正则表达式: 对输入的字符串进行匹配。如果想替换多次,必须使用/g标识

replacement: 字符串或者函数。

1) 字符串: 描述如何替换找到的匹配项), 使用该字符串替换匹配项,替换字符串中的$
符号允许使用完全匹配或者匹配分组进行替换
2) 函数: 执行替换并提供参数匹配信息.

"iixxxixx".replace("i", "o") // oixxxixx
"iixxxixx".replace(/i/, "o") // oixxxixx
"iixxxixx".replace(/i/g, "o") // ooxxxoxx

// 使用$符号
"iixxxixx".replace(/i+/g, "($&)") // (ii)xxx(i)xx
"iixxxixx".replace(/(i+)/g, "($1)") // (ii)xxx(i)xx

// 使用函数替换
function repl(search, group, offset, input) {
    return "{ search: " + search.toUpperCase() + ", group: " + group + ", offset: " + offset + " }";
}
"iixxxixx".replace(/(i+)/g, repl)
// { search: II, group: ii, offset: 0 }xxx{ search: I, group: i, offset: 5 }xx
1. replacement为字符串

内容用来逐字替换匹配项,唯一例外是特殊字符美元符号($),它会启动替换指令

分组:$n在匹配中插入分组n。n必须大于等于1($0没有任何特殊含义)

匹配的子字符串:

1) $` (反引号)插入匹配项之前的文本
2) $& 插入完整的匹配项
3) $" (单引号)插入匹配项之后的文本

$$ 插入单个$字符

"axb cxd".replace(/x/g, "($`,$&,$")"); // a(a,x,b cxd)b c(axb c,x,d)d
""foo" and "bar"".replace(/"(.*?)"/g, "#$1#"); // #foo# and #bar#
2. replacement为函数

如果replacement为函数,它需要对替换匹配项的字符串进行处理。签名如下:

function replacement (search, group_1, ..., group_n, offset, input){}

search与前面介绍的$&(完整的匹配项)相同, offset表示找到匹配项的位置,input是正在被匹配的字符串

search: 正则表达式匹配到的字符串

group: 如果存在分组,该值为匹配到的分组值, 可变参数, 匹配到分组有多少个,这个参数就有多少个, 如果不存在,表示匹配到子字符所在原字符串的位置

offset(倒数第二个参数): 匹配到子字符所在原字符串的位置

input(最后一个参数): 原字符串

function replaceFunc (match) { return 2 * match; }
"3 peoples and 5 oranges".replace(/d+/g, replaceFunc); // 6 peoples and 10 oranges
六、检查value是否为对象
function isObject (value) {
    return value === Object(value);
}
七、Array.prototype.forEach()的thisValue

forEach函数存在两个参数,第一个是回调函数,第二个参数为第一个参数(回调函数)提供this

var obj = {
    name: "Jane",
    friends: ["Tarzan", "Cheeta"],
    loop: function () {
        "use strict";
        this.friends.forEach(function (friend) {
            console.log(this.name + " knows " + friend);
        }, this)
    }
}
八、使用给定的prototype创建新对象

使用方法: Object.create(proto, properties?)

参数:

proto: 新创建对象的原型

properties: 可选,通过描述符给新创建对象添加属性

var PersonProto = {
    describe: function () {
        return "Person named " + this.name;
    }
}

var jane = Object.create(PersonProto, {
    name: { value: "Jane", writable: true, configurable: true, enumerable: true }
});

jane.describe() // "Person named Jane"
九、获取对象的原型

调用方法: Object.getPrototypeOf(obj)

Object.getPrototypeOf(jane) === PersonProto // true
十、检查一个对象是否是另外一个对象的原型

语法: Object.prototype.isPrototypeOf(obj)

检查接受者是否是obj的原型(直接或者间接)

var A = {};
var B = Object.create(A);
var C = Object.create(B);
A.isPrototypeOf(C); // true

PersonProto.isPrototypeOf(jane); // true
十一、找到定义属性的对象

遍历对象obj的原型链, 返回键为propKey的自定义属性的第一个对象

function getDefiningObject(obj, propKey) {
    while (obj && !Object.prototype.hasOwnProperty.call(obj, propKey)) {
        obj = Object.getPrototypeOf(obj);
    }

    return obj;
}
十二、列出自有的属性键

可以列吃所有自有属性,也可以列出可枚举属性

1) Object.getOwnPropertyNames(obj) 返回obj的所有自有属性键
2) Object.key(obj) 返回obj所有可枚举的自有属性键
十三、通过属性描述符获取和设置属性的特性

Object.defineProperty(obj, propKey, propDesc)
创建或改变obj对象的propKey键的属性,并通过propDesc制定这个属性的特性,返回修改后的对象

Object.getOwnPropertyDescriptor(obj, PropKey)
返回obj对象的propKey键自有(非继承)属性描述符,如果没有该属性,则返回undefined.

十四、复数对象

需求:

1) 就有相同的原型
2) 具有相同的属性及属性特性
function copyObject (orig) {
    // 1. copy has same prototype as orig
    var copy = Object.create(Object.getPrototypeOf(orig));

    // 2. copy ass orig"s own properties
    function copyOwnPropertiesFromObjct (target, source) {
        var ownProperties = Object.getOwnPropertyNames(source);
        for (var property of ownProperties) {
            var sourcePropertyDescriptor = Object.getOwnPropertyDescriptor(source, property);
            Object.defineProperty(target, property, sourcePropertyDescriptor);
        }
    }

    copyOwnPropertiesFromObjct(copy, orig);
    return copy;
}
十五、继承的只读属性不能被赋值

如果一个对象obj从原型继承了不可写的属性foo, 在严格模式下,那么不能给obj.foo赋值。

var proto = Object.create({}, { foo : { value: "a", writable: false } });
// var proto = Object.defineProperty({}, "foo", { value: "a", writable: false });
var obj = Object.create(proto);

obj.foo = "b"; // b
obj.foo // a

// IIFE
(function () { "use strict"; obj.foo = "b" }());
// Uncaught TypeError: Cannot assign to read only property "foo" of object "#"

这符合赋值改变但不破坏继承属性的理念。如果一个继承属性是只读的,应该禁止所有修改操作

十六、枚举性影响下列操作
1) for-in // 循环所有可枚举属性(自有+继承)
2) Object.keys() // 列出所有自有可枚举属性
2) JSON.stringify() // 所有自有可枚举属性
十七、保护对象之防止扩展(Object.preventExtensions())

不能添加属性,可以删除属性, 可以修改已有自有属性

var obj = { foo: "a" };
Object.preventExtensions(obj);

obj.bar = "b"; // 宽松模式下,添加失败
obj.bar // undefined

(function () { "use strict"; obj.bar = "b"  }()) // 严格模式下,抛出异常
// Uncaught TypeError: Can"t add property bar, object is not extensible

// 删除属性
delete obj.foo; // a
obj.foo // undefined

判断某个对象是否可以扩展: Object.isExtensible(obj)

Object.isExtensible(obj); // false
Object.isExtensible({}); // true

注: 防止扩展时,返回false

十八、保护对象之封闭(Object.seal())

不能添加属性,且不能删除属性, 只能修改已有自有属性。
(封闭 === 防止扩展 + 不能删除属性)

var obj = { foo: "a" }

Object.getOwnPropertyDescriptor(obj, "foo"); // before sealing
// {value: "a", writable: true, enumerable: true, configurable: true}

Object.seal(obj);
Object.getOwnPropertyDescriptor(obj, "foo"); // after sealing
// {value: "a", writable: true, enumerable: true, configurable: false}

// 不能改变其属性特性
Object.defineProperty(obj, "foo", { enumerable: false });
// Uncaught TypeError: Cannot redefine property: foo

判断一个对象是否封闭: Object.isSealed(obj)

Object.isSealed(obj); // true
Object.isSealed({}); // false

注: 对象封闭时,返回true

十九、保护对象之冻结(Object.freeze())

不能添加属性,不能删除属性, 同时也修改已有自有属性。
(冻结 === 封闭 + 不能修改属性)

var point = { x: 17, y: -5 };
Object.freeze(point);

// 赋值操作,宽松模式下,执行失败;严格模式下,抛出异常。
point.x = 6
point.x // 17

(function () { "use strict"; point.x = 2; }());
// Uncaught TypeError: Cannot assign to read only property "x" of object "#"

(function () { "use strict"; point.z = 62; }());
// Uncaught TypeError: Can"t add property z, object is not extensible

(function () { "use strict"; delete point.x; }());
// Uncaught TypeError: Cannot delete property "x" of #

判断一个对象是否被冻结: Object.isFrozen(obj)

Object.isFrozen(obj); // true
Object.isFrozen({}); // false

注: 对象被冻结时,返回true

二十、保护是浅层的

所有保护对象的方法,只能保护基本类型的自有属性,对于对象不起作用(可以改变属性值为对象中对象的属性)

var obj = {
    foo: 1,
    bar: [1, 2, 3],
    cer: { a: "123" }
};
Object.freeze(obj);

obj.foo = 2; // no effect
obj.foo // 1

obj.bar.push("bar"); // 4: changes obj.bar
obj.bar // [1, 2, 3, "bar"]

obj.cer.b = "cer"; //cer: changes obj.cer
obj.cer // {a: "123", b: "cer"}
二十一、对象不是Object的实例

几乎所有的对象都是Object的实例,因为Object.prototype在这些对象的原型链上,但也有对象不属于这种情况。

Object.create(null) instanceof Object // false
Object.prototype instanceof Object // false

Object.getPrototypeOf(Object.create(null)) // null
Object.getPrototypeOf(Object.prototype) // null

这类对象位于原型链的末端,但typeof可以正确的把这些对象归类为对象:

typeof Object.create(null) // object
typeof Object.prototype // object
二十二、跨域对象检测

在Web浏览器中,每一个帧和窗口都拥有自己的域,具有独立的全局变量。这使得instanceof不可用于那些跨域的对象。

如果myvar是来自另一个域的数组,那么它的原型是那个域的Array.prototype。在当前域中,使用instanceof检测的是否为myvar的原型链上是否存在当前域的Array.prototype, 会返回false。

可以使用ECMAScript 5的函数Array.isArray()来判断是否为数组




  Test

  

  

二十三、备忘录: 对象的使用 1. 对象字面量
var jane = {
    name: "Jane",
    "not an indentifier": 123,
    describe: function () {
        return "Person named " + this.name;
    }
}

jane.describe() // Person named Jane
2. 点操作符(.):通过固定键值访问属性
obj.propKey // 访问
obj.propKey = value // 赋值
delete obj.propKey // 删除
3. 中括号操作符([]): 通过计算出的键值访问属性
obj["propKey"] // 访问
obj["propKey"] = value // 赋值
delete obj["propKey"] // 删除
4.获取和设置原型
Object.create(proto, propDescObj?) // 创建指定原型的对象
Object.getPrototypeOf(obj) // 获取原型
5.属性的遍历和检测
Object.keys(obj)

Object.getOwnPropertyNames(obj)

for (var propKey in obj)
Object.prototype.hasOwnProperty.call(obj, propkey)
6. 通过描述符获取和定义属性特性
// 定义
Object.defineProperty(obj, propKey, propDesc)
Object.defineProperties(obj, propDescObj)
Object.create(obj, propDescObj)

// 获取
Object.getOwnPropertyDescriptor(obj, propKey)
7. 保护对象: 防止扩展、封闭、冻结
// 防止扩展: 不能添加属性,能删除属性或修改属性值
Object.preventExtensions(obj)
Object.isExtensible(obj)

// 封装: 不能添加或者删除属性,能修改属性值
Object.seal(obj)
Object.isSealled(obj)

// 冻结: 不能添加或者删除属性,也不能修改属性值
Object.freeze(obj)
Object.isFrozen(obj)
8. 所有对象的方法
// 将对象转换为基本类型的值
Object.prototype.toString()
Object.prototype.valueOf()

// 返回本地语言环境的代表对象的字符串
Object.prototype.toLocalString()

// 原型式继承和属性
Object.prototype.isPrototypeOf(subObj)
Object.prototype.hasOwnProperty(propKey)
Object.prototype.propertyIsEnumerable(propKey)
二十四、通过字面量创建数组时,最后的逗号会被忽略
["a", "b"].length // 2

["a", "b",].length // 2

["a", "b", ,].length // 3
二十五、空缺数组

数组是由索引到值的映射,这意味这数组可以有空缺,索引的个数小于数组的长度说明数组为空缺数组。

var a = new Array(5);
a[1] = 1;
a[4] = 4;

// a[0], a[2], a[3]都未赋值,a为空缺数组
二十六、数组中某些方法会忽略空缺 1. 数组遍历方法 1) forEach()遍历时会跳过空缺
["a", , "b"].forEach(function (x, i) { console.log(i + ": " + x); });

// 0: a
// 2: b
2) every()也会跳过空缺(类似的还有some()方法)
["a", , "b"].every(function (x) { return typeof x === "string" });

// true
3) map()虽然会跳过空缺,但会保留空缺
["a", , "b"].map(function (x, i) { return i + "." + x; });
// ["0.a", , "2.b"]
4) filter()会去除空缺
["a", , "b"].filter(function (x) { return true; });
// ["a", "b"]
2. 其他方法 1) join()会把空缺、undefined、null转换成空的字符串
["a", , "b"].join("-");
// a--b

["a", , "b", undefined, "c", null,"d"].join("-");
//a--b--c--d
2) sort()排序时保留空缺
["a", , "b"].sort();
// ["a", "b", ,]
3. for-in循环

for-in循环可以正确列出属性建, 跳过空缺

for (var key in ["a", , "b"]) { console.log(key) }
// 0
// 2

for (var key in ["a", undefined, "b"]) { console.log(key) }
// 0
// 1
// 2
4. Function.prototype.apply()

apply()把每个空缺转化为undefined参数。

function f () { return Array.prototype.slice.call(arguments); }
f.apply(null, [,,,]);
// [undefined, undefined, undefined]

Array.apply(null, [,,,]);
// [undefined, undefined, undefined]
二十七、数组构造函数

Array.isArray(obj) 判断obj是否为一个数组,能够正确处理跨域对象。

二十八、数组的原型方法 1. 添加和删除元素(破坏性的) 1) Array.prototype.shift()

移除索引0处的元素并返回该元素。随后元素的索引依次减1:

var arr = ["a", "b"];
var first = arr.shift(); // "a"
console.log(arr); // ["b"]
2) Array.prototype.unshift(elem1?, elem2?, ...)

在数组前面插入指定的元素,返回新的数组长度

var arr = ["a", "b"];
arr.unshift("c", "d"); // 4
console.log(arr); // ["c", "d", "a", "b"]
3) Array.prototype.pop()

移除数组最后的元素并返回该元素

var arr = ["a", "b"];
var last = arr.pop(); // "b"
console.log(arr); // ["a"]
4) Array.prototype.push(elem1?, elem2?, ...)

在数组的最后追加指定的元素,返回新的数组长度

var arr = ["a", "b"];
arr.push("c", "d"); // 4
console.log(arr); // ["a", "b", "c", "d"]

小技巧: 可以使用apply函数将一个数组追加到另一个数组的后面

var arr1 = ["a", "b"];
var arr2 = ["c", "d"];
Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // ["a", "b", "c", "d"]
5) Array.prototype.splice(start, deleteCount?, elem1?, elem2?, ...)

从索引start位置开始,删除deleteCount个元素,并在该位置插入给定元素, 返回删除的元素组成的数组

var arr = ["a", "b", "c", "d"];
arr.splice(1, 2, "X"); // ["b", "c"]
console.log(arr); // ["a", "X", "d"]

注: start可以为负值,表示从倒数几位开始。deleteCount是可选的,如果省略,移除从start位置开始的所有元素

var arr = ["a", "b", "c", "d"];
arr.splice(-2); // ["c", "d"]
console.log(arr); // ["a", "d"]
2. 排序和颠倒元素顺序(破坏性的) 1) Array.prototype.reverse()

颠倒数组中的元素,并返回指向原数组的引用:

var arr = ["a", "b", "c"];
arr.reverse(); // ["c", "b", "a"]
console.log(arr); // ["c", "b", "a"]
2) Array.prototype.sort(compareFunction?)

数组排序, 并返回排序后的数组

var arr = ["banana", "apple", "pear", "orange"];
arr.sort() // ["apple", "banana", "orange", "pear"];
console.log(arr); // ["apple", "banana", "orange", "pear"];

注: 排序是通过将元素转换为字符串再进行比较,这意味着数字不是按照数值进行排序

[-1, -20, 7, 50].sort(); // [-1, -20, 50, 7]

可以通过compareFunction来解决该问题,该函数的签名为:

function compareFunction (a, b)

比较参数a和b的值,返回的规则如下:

小于0的整数, a < b, 则a排在b的前面

等于0, a === b

大于0的整数, a > b, 则a排在b的后面

注: 对于数字,一般的就简单的返回a-b, 可能导致数值溢出,为了防止这种情况,可能需要更复杂的逻辑

[-1, -20, 7, 50].sort(function (a, b) {
  return a < b ? -1 : (a > b ? 1 : 0);
}); // [-20, -1, 7, 50]
3. 合并、切分和连接(非破坏性) 1) Array.prototype.concat(arr1?, arr2?, ...)

创建一个新数组,其中包含接受者的所有元素,其次是arr1中的所有元素,依次类推,如果其中一个参数不是数组, 当中元素添加到数组中

var arr = ["a", "b"];
var newArray = arr.concat("c", ["d", "e"]); // ["a", "b", "c", "d", "e"]
console.log(newArray); // ["a", "b", "c", "d", "e"]
console.log(arr); // ["a", "b"]
2) Array.prototype.slice(start?, end?)

把数组中从start开始到end(不包含end)的元素复制到新数组中

["a", "b", "c", "d"].slice(1, 3) // ["b", "c"]

["a", "b", "c", "d"].slice(1) // ["b", "c", "d"]
["a", "b", "c", "d"].slice() // ["a", "b", "c", "d"]

注: 不包含end时,拷贝从start开始到最后的所有元素, 如果不传start和end参数,拷贝数组, start和end都可以是负值,表示从倒数开始

3) Array.prototype.join(separator?)

对所有数组元素应用toString()创建字符串, 并使用separator连接字符,如果缺少separator,默认使用","

[3, 4, 5].join("-") // 3-4-5
[3, 4, 5].join() // 3,4,5
[3, 4, 5].join("") // 345

注:如果数组中某个元素为undefined, null或者空缺, 调用join方法是,会将该元素转换成空字符串

[undefined, null, "a"].join("#") // ##a
[undefined, , "a"].join("#") // ##a
4. 值的查找(非破坏性) 1) Array.prototype.indexOf(searchValue, startIndex?)

从数组的startIndex位置开始查找searchValue,返回第一次出现searchValue的索引,没有找到,就返回-1。缺少startIndex, 查找整个数组

[3, 1, 17, 1, 4].indexOf(1); // 1
[3, 1, 17, 1, 4].indexOf(1, 2); // 3

注:查找时使用的是严格相等, 不能查找NaN

[NaN].indexOf(NaN); // -1
2) Array.prototype.lastIndexOf(searchValue, startIndex?)

从数组的startIndex位置开始反向查找searchValue,返回第一次出现searchValue的索引,没有找到,就返回-1。缺少startIndex, 查找整个数组。查找时使用的是严格相等

[3, 1, 17, 1, 4].indexOf(1); // 3
[3, 1, 17, 1, 4].indexOf(1, -3); // 1
[3, 1, 17, 1, 4].indexOf(1, 2); // 1
5. 迭代(非破坏性)

可以划分为三种迭代方法:

检查方法: 主要观察数组的内容(forEach, every, some)

转化方法: 从已有的数组中获得新数组(map, filter)

归约方法: 基于接受者的元素计算出结果(reduce, reduceRight)

1) Array.prototype.forEach(callback, thisValue?)

遍历数组中的元素

var arr = ["apple", "banana", "pear"];
arr.forEach(function (elem) {
    console.log(elem);
});
// apple
// banana
// pear

注: 存在一个缺陷,不支持break或者类似于提前终止循环的处理,除非throw Error。

2) Array.prototype.every(callback, thisValue?)

如果对每个元素。回调函数都返回true,则every方法返回true, 如果某个元素回调函数返回false, 则停止迭代。类似全称量词

// 检查数组中的元素是否全部为偶数
function isEven(x) { return x % 2 === 0 }
[2, 4, 6].every(isEven); // true
[2, 4, 5].every(isEven); // false

注: 如果为空数组,返回true,不会调用callback方法

[].every(function () { throw new Error("Empty array") }); // true
3) Array.prototype.some(callback, thisValue?)

如果对其中某个元素。回调函数返回true,则some方法返回true, 如果某个元素回调函数返回true, 则停止迭代。类似存在量词

// 检查数组中的元素是否存在偶数
function isEven(x) { return x % 2 === 0 }
[1, 3, 5].some(isEven); // false
[2, 2, 3].some(isEven); // true

注: 如果为空数组,返回false,不会调用callback方法

[].some(function () { throw new Error("Empty array") }); // false
4) Array.prototype.map(callback, thisValue?)

转化数组的每个元素都是原数组中元素调用回调函数callback后的结果

[1, 2, 3],map(function (elem) { return 2 * elem; }); // [2, 4, 6]
5) Array.prototype.filter(callback, thisValue?)

转化数组的元素都是原数组中元素调用回调函数callback返回true的元素

[1, 0, 3, 0].filter(function (x) { return !!x; }); // [1, 3]
6) Array.prototype.reduce(callback, initialValue?)

从左至右进行迭代,其中callback参数的结构为:

function callback(previousValue, currentValue, currentIndex, array);

除初次调用外,每次调用callback时,参数previousValue是上一次回调函数的返回值。初次调用回调函数有两种情况

提供显式initialValue参数,previousValue就是initialValue,而currentValue为数组中的第一个元素

未提供显式initialValue参数,previousValue就是第一个元素,而currentValue为数组中的第二个元素

reduce方法返回最后一次调用回调函数时的结果

function add (pre, cur) {
    return pre + cur;
}

[1, 10, -3].reduce(add); // 8

注: 只有一个元素的数组调用reduce,则返回该元素

function add (pre, cur) {
    return 2 * pre + 3 * cur;
}

function add2 (pre, cur) {
    return 1;
}

[7].reduce(add); // 7
[7].reduce(add2); // 7

注: 如果对空数组调用reduce, 必须指定initialValue, 否则会抛出异常

[].reduce(add);
// Uncaught TypeError: Reduce of empty array with no initial value
7) Array.prototype.reduceRight(callback, initialValue?)

工作原来与Array.prototype.reduce相似,但是是从右至左归约

二十九、类数组对象

javascript中存在一类对象看起来像数组,实际上不是,可以通过索引访问元素具有length属性,但没有数组的其他实例属性。

类数组对象主要有:特殊对象arguments、DOM节点列表和字符串。

我们可以使用泛型方法来使类数组对象防访问数组原型上的方法。

三十、正则表达式原子(分组)

分组的语法:

(<>) 捕获分组。任何匹配pattern的内容都可以通过反向引用访问或作为匹配结果。

(?:<>) 非捕获分组。仍然根据pattern匹配,但不保存捕获的内容。分组没有数字可以引用。

12...依次类推称为方向引用;他们指向之前匹配的分组。第一个数字必须不是0

/^(a+)-1$/.test("a-a"); // true
/^(a+)-1$/.test("aaa-aaa"); // true
/^(a+)-1$/.test("aaa-a"); // false

注:方向引用保证破折号前后的字符数一致

使用方向引用来匹配HTML标签

var tagName = /<([^>]+)>[^<]*/;
tagName.exec("bold")[1] // b

tagName.exec("text")[1] // strong
tagName.exec("text")[1] // TypeError: Cannot read property "1" of null
三十一、正则表达式量词

? 表示从未匹配或只匹配一次

表示从匹配零次或多次

表示匹配一次或多次

{n} 表示完全匹配n次

{n, } 表示匹配至少n次

{n, m} 表示匹配至少n次,最多m次

默认情况下,量词是贪婪匹配的,他们会尽可能多的匹配,如果想使用勉强匹配(尽可能少),可以通过量词后面加问号(?)

// 贪婪匹配
" ".match(/^<(.*)>/)[1] // a>  ".match(/^<(.*?)>/)[1] // a

注:*?是十分有用的模式,它可以匹配一切,直到后面的原子出现。
上述匹配HTML标签的正则可以改为

var tagName = /<(.+?)>.*?/;
tagName.exec("bold")[1] // b

tagName.exec("text")[1] // strong
tagName.exec("text")[1] // TypeError: Cannot read property "1" of null
三十二、正则表达式断言

^ 只匹配输入的开始位置

$ 只匹配输入结束位置

b 只匹配单词的边界。不要与[b]混淆,它匹配一个退格。

B 只匹配非单词边界

(?=<>) 正向肯定断言:只匹配pattern所匹配的接下来的内容。pattern只是用来向前查找,但会忽略匹配的pattern部分

(?!<>) 正向肯定断言:只匹配pattern不匹配的接下来的内容。pattern只是用来向前查找,但会忽略匹配的pattern部分

匹配单词边界

/ell/.test("hello"); // false
/ell/.test("ello"); // false
/ell/.test("a ell v"); // true

通过B匹配单词的内部元素

/BellB/.test("ell"); // false
/BellB/.test("ello"); // false
/BellB/.test("hello"); // true
三十三、正则表达式标识

标识是正则表达式字面量的后缀和正则表达式构造函数的参数;它修改正则表达是的匹配行为。

简称 全称 描述
g global 给定的正则表达式可以匹配多次,它会影响几种方法,尤其是String.prototype.replace()
i ignoreCase 试图匹配给定的正则表达式时忽略大小写
m multiline 在多行模式时,开始操作符^和结束操作符$匹配每一行,而不是输入的整个字符串

简称用于正则字面量后缀和构造函数参数,全称用于正则表达式对象的属性

三十四、正则表达式的实例属性

标识: 布尔值表示设置什么标志。

global(全局): 是否设置/g标识
ignoreCase(忽略大小写): 是否设置了/i标识
multiline(多行): 是否设置/m标识

多次匹配数据(设置了/g标识)

lastIndex: 下次继续查找的起始索引

var regex = /abc/i;
regex.ignoreCase; // true
regex.multiline; // false
三十五、RegExp.prototype.test: 是否存在匹配

test()方法用来检查正则表达式regex是否匹配字符串str:

regex.test(str)

如果没有设置/g标识,该方法只检查是否在str某处存在匹配;如果设置了/g标识,则该方法会多次匹配regex并返回true,属性lastIndex包含最后一次匹配之后的索引

// 未设置/g
var str = "_x_x";
/x/.test(str); // true
/a/.test(str); // false

// 设置/g
var regex = /x/g;
regex.lastIndex; // 0

regex.test(str); // true
regex.lastIndex; // 2

regex.test(str); // true
regex.lastIndex; // 4

regex.test(str); // false
三十六、String.prototype.search: 匹配位置的索引

search()方法查找str匹配regex的位置

str.search(regex)

如果存在匹配返回匹配位置的索引,否则,返回值为-1, 进行查找时,regex的global和lastIndex属性被忽略(其中lastIndex没有改变)

"abba".search(/b/); // true
"abba".search(/B/i); // true
"abba".search(/x/i); // true

注:如果search()的参数不是正则表达式,它被转换为正则:

"aaab".search("^a+b+$"); // 0
三十七、RegExp.prototype.exec: 捕获分组

在str匹配regex的同时捕获分组

var matchData = regex.exec(str);

如果没有匹配,matchData为null。否则,matchData为匹配结果,是带有两个附属属性的数组
(1) 数组元素

元素0是完整正则表达式的匹配结果。

元素n>1是捕获的分组N。

(2)属性

input 是输入的完整字符串

index 是查找到匹配处的索引

如果未设置/g标识,只返回第一次匹配的结果:

var regex = /a(b+)/;
regex.exec("_abbb_ab_");
// ["abbb", "bbb", index: 1, input: "_abbb_ab_"]
regex.lastIndex; // 0

如果设置了/g标识,可以反复调用exec()方法返回所有的匹配项,返回值null表示没有任何匹配。属性lastIndex表示下次匹配从哪里开始

var regex = /a(b+)/g;
var str = "_abbb_ab_";
console.log(regex.exec(str));
// ["abbb", "bbb", index: 1, input: "_abbb_ab_"]
console.log(regex.lastIndex); // 5

console.log(regex.exec(str));
// ["ab", "b", index: 6, input: "_abbb_ab_"]
console.log(regex.lastIndex); // 8

regex.exec(str); // null

使用循环遍历所有的匹配项

var regex = /a(b+)/g;
var str = "_abbb_ab_";
var match;
while(match = regex.exec(str)) {
    console.log(match[1]);
}

// bbb
// b
三十八、String.prototype.match: 捕获分组已经返回所有匹配的子字符串
var matchData = str.match(regex);

如果未设置/g标识,该方法类似与RegExp.prototype.exec()

"abba".match(/a(b+)/);
// ["abb", "bb", index: 0, input: "abba"]

如果设置了/g标识,则返回str中含有所有匹配的子字符串的数组;如果没有任何匹配,则返回null

"_abbb_ab_".match(/a(b+)/g);
// ["abbb", "ab"]
三十九、String.prototype.replace: 查找和替换

存在两个参数: search, replacement

search: 字符串或者正则表达式。

1) 字符串: 在字符串中找到匹配的字面量,只能替换一次出现的匹配项,暂不存在
    (replaceAll函数),想要替换多次出现的匹配项,必须使用正则表达式/g标识。
2) 正则表达式: 对输入的字符串进行匹配。如果想替换多次,必须使用/g标识

replacement: 字符串或者函数。

1) 字符串: 描述如何替换找到的匹配项), 使用该字符串替换匹配项,替换字符串中的$
符号允许使用完全匹配或者匹配分组进行替换
2) 函数: 执行替换并提供参数匹配信息.

"iixxxixx".replace("i", "o") // oixxxixx
"iixxxixx".replace(/i/, "o") // oixxxixx
"iixxxixx".replace(/i/g, "o") // ooxxxoxx

// 使用$符号
"iixxxixx".replace(/i+/g, "($&)") // (ii)xxx(i)xx
"iixxxixx".replace(/(i+)/g, "($1)") // (ii)xxx(i)xx

// 使用函数替换
function repl(search, group, offset, input) {
    return "{ search: " + search.toUpperCase() + ", group: " + group + ", offset: " + offset + " }";
}
"iixxxixx".replace(/(i+)/g, repl)
// { search: II, group: ii, offset: 0 }xxx{ search: I, group: i, offset: 5 }xx
1. replacement为字符串

内容用来逐字替换匹配项,唯一例外是特殊字符美元符号($),它会启动替换指令

分组:$n在匹配中插入分组n。n必须大于等于1($0没有任何特殊含义)

匹配的子字符串:

1) $` (反引号)插入匹配项之前的文本
2) $& 插入完整的匹配项
3) $" (单引号)插入匹配项之后的文本

$$ 插入单个$字符

"axb cxd".replace(/x/g, "($`,$&,$")"); // a(a,x,b cxd)b c(axb c,x,d)d
""foo" and "bar"".replace(/"(.*?)"/g, "#$1#"); // #foo# and #bar#
2. replacement为函数

如果replacement为函数,它需要对替换匹配项的字符串进行处理。签名如下:

function replacement (search, group_1, ..., group_n, offset, input){}

search与前面介绍的$&(完整的匹配项)相同, offset表示找到匹配项的位置,input是正在被匹配的字符串

search: 正则表达式匹配到的字符串

group: 如果存在分组,该值为匹配到的分组值, 可变参数, 匹配到分组有多少个,这个参数就有多少个, 如果不存在,表示匹配到子字符所在原字符串的位置

offset(倒数第二个参数): 匹配到子字符所在原字符串的位置

input(最后一个参数): 原字符串

function replaceFunc (match) { return 2 * match; }
"3 peoples and 5 oranges".replace(/d+/g, replaceFunc); // 6 peoples and 10 oranges
四十、标识符/g的一些问题

正则表达式设置了/g标识,有些方法必须多次(循环)调用才能放回所有结果。这些方法如下所示

RegExp.prototype.test()

RegExp.prototype.exec()

正则表达式作为迭代器成为结果序列的指针时,会被不正确地使用。这导致的问题如下:

1. 带有/g的正则表达式不能内联
// 反模式
var count = 0;
while (/a/g.test("babaa")) count++;

// 正确用法
var count = 0;
var regex = /a/g;
while (regex.test("babaa")) count++;

上述第一种情况中会创建无限循环,每次循环迭代都会创建一个新的正则表达式,导致重新开始迭代。

// 反模式
function extractQuoted(str) {
    var match;
    var result = [];
    while ((match = /"(.*?)"/g.exec(str)) !== null) {
        result.push(match[1]);
    }
    return result;
}
// 会导致无限循环

// 正确的方法
function extractQuoted(str) {
    var QUOTE_REGEX = /"(.*?)"/g;
    var match;
    var result = [];
    while ((match = QUOTE_REGEX.exec(str)) !== null) {
        result.push(match[1]);
    }
    return result;
}

extractQuoted(""hello", "world""); // ["hello", "world"]

注:为了避免上述这种无限循环的情况,最好的解决办法就是在任何情况下,都不使用内联正则表达式,这要求我们形成一个良好的编码习惯。

2. 带/g的正则表达式作为参数

需要多次调用test()和exec()方法时,把正则作为参数传递给方法时必须要小心。必须设置/g标识,为了安全起见,应该设置lastIndex为0

3. 共享带有/g的正则表达式

当你引用不是新创建的正则表达式时,在把它作为迭代器前,需要手动把lastIndex设置为0。由于迭代依赖lastIndex,这种正则表达式不能同时用于多个迭代。

function countOccurrences(regex, str) {
    var count = 0;
    while (regex.test(str)) count++;
    return count;
}

countOccurrences(/x/g, "_x_x"); // 2

// 问题一: 如果不加/g标识,会进入无限循环
countOccurrences(/x/, "_x_x"); // 无限循环

// 问题二: lastIndex未设置为0
var regex = /x/g;
regex.lastIndex = 2;
countOccurrences(regex, "_x_x"); // 1

// 修复上述两个问题
function countOccurrences(regex, str) {
    if (!regex.global) {
        throw new Error("Please set flag /g of regex");
    }

    var origLastIndex = regex.lastIndex; // store
    regex.lastIndex = 0;

    var count = 0;
    while (regex.test(str)) count++;

    regex.lastIndex = origLastIndex;
    return count;
}

// 更简单的方式,使用String.prototype.match
function countOccurrences(regex, str) {
    if (!regex.global) {
        throw new Error("Please set flag /g of regex");
    }

    return (str.match(regex) || []).length;
}
四十一、引用文本

给制定的字符串拼装成正则表达式,所有特殊是字符都需要进行转义。

function quoteText(text) {
    return text.replace(/[^$.*+?()[]{}|=!<>:-]/g, "$&");
}

console.log(quoteText("*All* (most?) aspects.")); // *All* (most?) aspects.
四十二、缺少断言(^、$)的正则表达式可以在任意位置匹配
/aa/.test("xaay"); // true
/^aa$/.test("xaay"); // false
四十三、匹配一切或者什么都不匹配

一种特殊情况:函数可以把正则表达式当做参数,用于过滤,如果缺少这个参数,需要提供一个可以匹配一切的默认值

1. 匹配一切

空的正则表达式可以匹配一切

new RegExp("").test("dfadsfdas"); // true
new RegExp("").test(""); // true

空的正则表达式应该是//, 但是它被解释为JavaScript的注释。所有我们可以使用最接近的字面量代替/(?:)/(空的非捕获分组)。这个分组可以匹配一切而且不捕获任何字符串。

new RegExp(""); // /(?:)/
2. 不匹配任何字符

空的正则表达式相反的正则表达式

var never = /.^/;
never.test("asdsad"); // false
never.test(""); // false
四十四、正则表达式备忘录 1. 原子

.(点) 匹配除了行结束符的一切字符。使用[sS]可以正则匹配一切

转义字符

d 匹配数字([0-9]); D 匹配非数字([^0-9])。
w 匹配拉丁字母数字的字符以及下划线([a-zA-Z0-9_]); W 匹配其他字符。
s 匹配所有空白字符(空格、制表、换行符等); S 匹配所有的非空白字符。
需要转移的字符: * . ? + $ ^ [ ] ( ) { } |  /

字符类(字符集合): [...]和[^...]

源字符: [abc](除了]-的所有匹配其本身的字符)
转义字符: [dw]
范围: [A-Za-z0-9]

分组

捕获分组: (...); 方向引用1
非捕获分组: (?:...)

2. 量词

贪婪匹配

? * +
{n}, {n, }, {n, m}

非贪婪: 把?放在任何贪婪量词后面

3. 断言

输入的开始和结束: ^ $。

单词的边界,非单词边界: b B。

正向肯定查找: (?=...) 匹配紧随其后的模式,但其会被忽略

正向否定查找: (?!...) 不匹配紧随其后的模式,但其会被忽略

4. 析取(或): | 5. 创建正则表达式

字面量: /abc/i (加载时编译)

构造函数: new RegExp("zxc", "igm"); (运行时编译)

6. 标识

global(全局): /g(影响正则表达式的一些方法)。

ignoreCase(忽略大小写): /i 。

multiline(多行): /m (^和$匹配每一行,而不是完整的输入)。

7. 方法

regexp.test(str): 是否存在匹配

不设置`/g`: 是否在某处存在匹配
设置`/g`: 存在多少次匹配就返回多少次true

str.search(regex): 在哪个所有存在匹配

regexp.exec(str): 捕获分组

不设置`/g`: 只捕获第一次匹配的分组
设置`/g`: 捕获所有匹配的分组

str.match(regexp): 捕获分组或返回所有匹配的字符串

不设置`/g`: 捕获分组
设置`/g`: 返回所有匹配的子字符串数组

str.replace(search, replacement): 查找和替换

search: 字符串和正则表达式(使用后者,设置/g)
replacement: 字符串(使用$1, 以此类推)或函数(arguments[1]是分组1, 以此类推)返回字符串

四十五、文字的编码和解码 1. encodeURI(uri)

在uri中我们用百分号来编码特殊字符,除了下列字符,其余的特殊字符都会被编码。

URI字符: ; , / ? : @ & = + $ #

下面字符也不会被编码: a-z A-Z 0-9 - _ . ! ~ * " ( )

encodeURI("http://example.com/Fur Elise/"); // http://example.com/Fur%20Elise/
2. encodeURIComponent(uri)

除了下列字符,所有字符都会被百分号编码(URI字符也会被编码):

下面字符也不会被编码: a-z A-Z 0-9 - _ . ! ~ * " ( )

encodeURIComponent("http://example.com/Fur Elise/"); // http%3A%2F%2Fexample.com%2FFur%20Elise%2F
3. decodeURI(encodeURI)

对已经进行encodeURI编码的uri进行解码操作

decodeURI("http://example.com/Fur%20Elise/"); // http://example.com/Fur Elise/
4. decodeURIComponent(encodeURIComponent)

对已经进行encodeURIComponent编码的uri进行解码操作, 所有的百分号编码都会被解码

decodeURIComponent("http%3A%2F%2Fexample.com%2FFur%20Elise%2F"); // http://example.com/Fur Elise/
四十六、Console在IE9中存在兼容性问题

在Internet Explorer9中存在一个bug。在IE9中,console对象只有当开发者工具栏被打开过至少一次,才会存在。
这意味着如果你的代码引用了console对象,同时又没有预先打开开发者工具栏,你可能会得到一个引用错误。
为了确保兼容性,最好在使用console对象之前先判断其是否存在。

四十七、模块系统 1. CommonJS模块(CommonJS Module, CJS)

CJS的主要化身是Node.js模块,其特点:

紧凑的语法

同步加载的设计

主要用于服务端

2. 异步模块定义(Asynchronous Module Definition, AMD)

AMD 最典型的实现就是Requirejs, 其特点:

AMD语法稍微复杂,但不通过eval()或者静态编译步骤就可以工作

异步加载的设计

主要用于浏览器

四十八、算法: ToPrimitive() —— 将值转换为原始值

要将任意值转换成数字或者字符串,首先会被转换成任意的原始值,然后在转换成最终的结果。

ECMAScript规范中有一个内部函数, ToPrimitive()(不能访问),能够实现这个功能。签名如下:

ToPrimitive(input, PreferredType?)

可选参数PreferredType表明转换后饿类型:它可以是NumberString,具体取决于ToPrimitive的结果是希望转换成数字还是字符串

如果PreferredTypeNumber,会执行以下步骤。

(1) 如果input是原始值,返回这个值。

(2) 否则,如果input是对象,调用input.valueOf()。如果结果是原始值,返回结果。

(3) 否则,调用input.toString()。如果结果是原始值,返回结果。

(4) 否则,抛出一个TypeError(说明输入转换原始值出错了)

如果PreferredTypeString,会执行以下步骤。

(1) 如果input是原始值,返回这个值。

(2) 否则,如果input是对象,调用input.toString()。如果结果是原始值,返回结果。

(3) 否则,调用input.valueOf()。如果结果是原始值,返回结果。

(4) 否则,抛出一个TypeError(说明输入转换原始值出错了)

PreferredType也可以省略,这种情况下,日期会被认为是String而其他值会被认为是Number。因此+操作符和===运算符可以操作ToPrimitive()。

ToPrimitive()实战
valueOf的默认实现会返回this,而toString()的默认实现会返回类型信息

var empty = {};
empty.valueOf() === empty; // true
empty.toString(); // "[object Object]"

Number() 跳过了valueOf()并且将toString()执行结果转换为数字,所以,它将"[object Object]"转换成了NaN

Number({}); // NaN

下面对象重写了valueOf(), 这会影响Number(), 但是不会对String()造成任何改变

var n = { valueOf: function () { return 123; } };
Number(n); // 123
String(n); // "[object Object]"

下面对象重写了toString()方法,因为结果会转换成数字,Number()返回了一个数字

var s = { toString: function () { return "7"; } };
String(s); // "7"
Number(s); // 7

注: 个人笔记,持续更新

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81643.html

相关文章

  • JavaScript易错知识点整理

    摘要:知识点变量作用域上方的函数作用域中声明并赋值了,且在之上,所以遵循就近原则输出等于。上方的函数作用域中被重新赋值,未被重新声明,且位于之下,所以输出全局作用域中的。上方利用方法进行对象的深拷贝可以避免源对象被篡改的可能。 前言 本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由...

    vincent_xyb 评论0 收藏0
  • JavaScript易错知识点整理

    摘要:知识点变量作用域上方的函数作用域中声明并赋值了,且在之上,所以遵循就近原则输出等于。上方的函数作用域中被重新赋值,未被重新声明,且位于之下,所以输出全局作用域中的。若执行则会输出。上方利用方法进行对象的深拷贝可以避免源对象被篡改的可能。 前言 本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值...

    2shou 评论0 收藏0
  • JavaScript:面试频繁出现的几个易错

    摘要:针对于面向对象编程的。因为面向对象就是针对对象例子中的守候来进行执行某些动作。这就是闭包的用途之一延续变量周期。把变量放在闭包里面和放在全局变量里面,影响是一致的。 1.前言 这段时间,金三银四,很多人面试,很多人分享面试题。在前段时间,我也临时担任面试官,为了大概了解面试者的水平,我也写了一份题目,面试了几个前端开发者。在这段时间里面,我在学,在写设计模式的一些知识,想不到的设计模式...

    VincentFF 评论0 收藏0
  • JavaScript文章

    摘要:我对知乎前端相关问题的十问十答张鑫旭张鑫旭大神对知乎上经典的个前端问题的回答。作者对如何避免常见的错误,难以发现的问题,以及性能问题和不好的实践给出了相应的建议。但并不是本身有问题,被标准定义的是极好的。 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我。 不论你是javascript新手还是老...

    mumumu 评论0 收藏0
  • 前端基础入门

    摘要:手把手教你做个人火的时候,随便一个都能赚的盆满钵满,但是,个人没有服务端,没有美工,似乎就不能开发了,真的是这样的吗秘密花园经典的中文手册。涵盖前端知识体系知识结构图书推荐以及入门视频教程,全的简直不要不要的了。 JavaScript 实现点击按钮复制指定区域文本 html5 的 webAPI 接口可以很轻松的使用短短的几行代码就实现点击按钮复制区域文本的功能,不需要依赖 flash。...

    shinezejian 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<