map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回 的结果。
@param callback: 生成新数组元素的函数,它可以接收三个参数:
参数1 currentValue: 用于测试的当前值。
参数2 index(可选): 用于测试的当前值的索引。
参数3 array(可选): 调用 every 的当前数组。
@param thisArg(可选): 执行 callback 时使用的 this 值。
@return: 回调函数的结果组成了新数组的每一个元素。
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])
/*
map 把函数作用在数组的每一个元素上,并返回新的数组
*/
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const newArray = arr.map(x => x * x);
console.log(newArray); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
const numbers = [1, 4, 9];
const roots = numbers.map(Math.sqrt);
console.log(numbers); // [ 1, 4, 9 ]
console.log(roots); // [ 1, 2, 3 ]
// 在一个 String 上使用 map 方法获取字符串中每个字符所对应的 ASCII 码组成的数组
const map = Array.prototype.map;
const a = map.call('Hello World', function(x) {
return x.charCodeAt(0);
});
console.log(a);
reduce() 方法对数组中的每个元素执行一个由您提供的 reducer 函数(升序执行), 将其结果汇总为单个返回值。
@param callback: 执行数组中每一个值的函数,它可以接收 4 个参数:
参数1 accumulator: 累加器累计回调的返回值。
参数1 currentValue: 数组中正在处理的元素。
参数3 index(可选): 用于测试的当前值的索引。
参数4 array(可选): 调用 every 的当前数组。
@param initialValue(可选): 作为第一次调用 callback 函数时的第一个参数的值。
@return: 函数累计处理的结果。
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
/*
reduce 中的函数接收两个参数
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
*/
const arr = [1, 2, 3];
const sumOfArray = arr.reduce((x, y) => x + y);
console.log(sumOfArray); // 6
// 累加对象数组中包含的值
var initialValue = 0;
var sum = [{x: 1}, {x: 2}, {x: 3}].reduce(
(accumulator, currentValue) => accumulator + currentValue.x,
initialValue,
);
console.log(sum); // 6
// 将二维数组转化为一维
var flattened = [[0, 1], [2, 3], [4, 5]].reduce(
(acc, cur) => acc.concat(cur),
[],
);
console.log(flattened); // [0, 1, 2, 3, 4, 5]
// 计算数组中每个元素出现的次数
var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
var countedNames = names.reduce(function(allNames, name) {
if (name in allNames) {
allNames[name]++;
} else {
allNames[name] = 1;
}
return allNames;
}, {});
console.log(countedNames); // { Alice: 2, Bob: 1, Tiff: 1, Bruce: 1 }
// 使用扩展运算符和initialValue绑定包含在对象数组中的数组
var friends = [
{
name: 'Anna',
books: ['Bible', 'Harry Potter'],
age: 21,
},
{
name: 'Bob',
books: ['War and peace', 'Romeo and Juliet'],
age: 26,
},
{
name: 'Alice',
books: ['The Lord of the Rings', 'The Shining'],
age: 18,
},
];
var allbooks = friends.reduce((acc, cur) => [...acc, ...cur.books], [
'Alphabet',
]);
console.log(allbooks);
// [
// 'Alphabet',
// 'Bible',
// 'Harry Potter',
// 'War and peace',
// 'Romeo and Juliet',
// 'The Lord of the Rings',
// 'The Shining',
// ];
// 数组去重 1
var myArray = ['a', 'b', 'a', 'b', 'c', 'e', 'e', 'c', 'd', 'd', 'd', 'd'];
var myOrderedArray = myArray.reduce(function(accumulator, currentValue) {
if (accumulator.indexOf(currentValue) === -1) {
accumulator.push(currentValue);
}
return accumulator;
}, []);
console.log(myOrderedArray); // [ 'a', 'b', 'c', 'e', 'd' ]
// 数组去重 2
let arr = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4];
let result = arr.sort().reduce((init, current) => {
if (init.length === 0 || init[init.length - 1] !== current) {
init.push(current);
}
return init;
}, []);
console.log(result); // [ 1, 2, 3, 4, 5 ]
// 数组去重 3
let arr = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4];
let result = Array.from(new Set(arr));
console.log(result); // [ 1, 2, 3, 5, 4 ]
filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
@param callback: 用来测试每个元素的函数。返回 true 表示该元素通过测试,
保留该元素,false 则不保留。它可以接收三个参数:
参数1 element: 用于测试的当前值。
参数2 index(可选): 用于测试的当前值的索引。
参数3 array(可选): 调用 every 的当前数组。
@param thisArg(可选): 执行 callback 时使用的 this 值。
@return: 一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,
则返回空数组。
var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])
/*
filter 也是一个常用的操作,它用于把 Array 的某些元素过滤掉,然后返回剩下的元素。
和 map() 类似,Array 的 filter() 也接收一个函数。和 map() 不同的是,filter() 把
传入的函数依次作用于每个元素,然后根据返回值是 true 还是 false 决定保留还是丢弃
该元素。
*/
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const evenArry = arr.filter(x => x % 2 === 0); // 过滤出偶数
console.log(evenArry); // [ 2, 4, 6, 8 ]
const arr2 = ['A', '', 'B', null, undefined, 'C', ' '];
const validStringArray = arr2.filter(function(s) {
return s && s.trim();
});
console.log(validStringArray); // [ 'A', 'B', 'C' ]
/*
数组去重
self[index] == element
self 就是 arr3 本身
去除重复元素依靠的是 indexOf 总是返回第一个元素的位置,
后续的重复元素位置与 indexOf 返回的位置不相等,因此被 filter 滤掉了。
*/
const arr3 = [1, 1, 1, 2, 2, 3, 3, 3, 3];
const result = arr3.filter(function(element, index, self) {
return self.indexOf(element) === index;
});
console.log(result); // [ 1, 2, 3 ]
// 筛选排除所有较小的值
function isBigEnough(element) {
return element >= 10;
}
var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
console.log(filtered); // [ 12, 130, 44 ]
every()
方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。
它返回一个布尔值。
@param callback: 用来测试每个元素的函数,它可以接收三个参数:
参数1 element: 用于测试的当前值。
参数2 index(可选): 用于测试的当前值的索引。
参数3 array(可选): 调用 every 的当前数组。
@param thisArg(可选): 执行 callback 时使用的 this 值。
@return: 如果回调函数的每一次返回都为 truthy 值,返回 true ,否则返回 false。
arr.every(callback[, thisArg])
// 检测数组中的所有元素是否都大于 10。
function isBigEnough(element, index, array) {
return element >= 10;
}
console.log([12, 5, 8, 130, 44].every(isBigEnough)); // false
console.log([12, 54, 18, 130, 44].every(isBigEnough)); // true
console.log([12, 5, 8, 130, 44].every(x => x >= 10)); // false
console.log([12, 54, 18, 130, 44].every(x => x >= 10)); // true
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/every
// filename: test.js
function sayHello(name) {
var text = 'Hello ' + name;
var say = function() {
console.log(text);
};
say();
}
sayHello('Joe');
node --inspect-brk=9229 test.js
用 chrome
调试代码,断点设置在 say();
那一
行。
如上图所示,可以看到图中黄色背景的部分。say
对象的 [[Scopes]]
数组中有两个
对象,第一个就是一个 Closure
。这其实就是一个匿名对象,该 Closure
中只有一个
text
属性。
结论: say()
只是一个函数,不是 Closure
。Closure
只是 say()
函数中的
一个匿名对象。
function sayHello2(name) {
var text = 'Hello ' + name;
var say = function() {
console.log(text);
};
return say;
}
var say2 = sayHello2('Bob');
say2();
node --inspect-brk=9229 test.js
用 chrome
调试代码,断点设置在 say2();
那一
行。
如上图所示,可以看到图中黄色背景的部分。结论和 例 1
是一样的。
function say667() {
var num = 42;
var say = function() {
console.log(num);
};
num++;
return say;
}
var sayNumber = say667();
sayNumber();
node --inspect-brk=9229 test.js
用 chrome
调试代码,断点设置在 sayNumber();
那一行。
如上图所示,可以看到图中黄色背景的部分。这次有些不同,你会发现 num
的值是
43
而不是 42
。这说明了什么呢?这说明了 num
不是复制进去了,这好像就是 Closure
一直引用着 say667()
函数的堆栈,导致了 say667()
函数结束了,堆栈也没有被释放。
var gLogNumber = null;
var gIncreaseNumber = null;
var gSetNumber = null;
function setupSomeGlobales() {
var num = 42;
gLogNumber = function() {
console.log(num);
};
gIncreaseNumber = function() {
num++;
};
gSetNumber = function(x) {
num = x;
};
}
setupSomeGlobales();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5
var oldLog = gLogNumber;
setupSomeGlobales();
gLogNumber(); // 42
oldLog(); // 5
这个例子较为复杂,但是也很能说明问题,请务必自己动手调试。
node --inspect-brk=9229 test.js
用 chrome
调试代码,断点分别设置在
两次调用 setupSomeGlobales();
那一行的下一行。
上图是在第一个断点处的截图。可以看到,一共生成了 2 个 Closure
。如果你把
gLogNumber
,gIncreaseNumber
和 gSetNumber
全部展开,你会发现这 3 个是
指向相同的 Closure
。也就是说,执行了一次 setupSomeGlobales();
函数,生成
了 3 个函数,这 3 个函数中有一个指针指向了相同的 Closure
。
上图是在第 2 个断点处的截图。可以看到,重新执行一次 setupSomeGlobales();
函数,生成了新的 Closure
了,而旧的 Closure
由于 oldLog
还引用着,也还可以
看到。
oldLog
和 gLogNumber
也指向了两个不同的函数,这两个函数有着不同的
Closure
。这说明了,在 JavaScript
中,你如果把一个函数内部定义在另一个函数
内部,每次在调用外部的函数时,内部的函数都会被重新创建。
这个例子的问题估计只要写过 JavaScript
的人都出错过。
这个例子需要知道 var,let,const 的区别。
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + i;
result.push(function() {
console.log(item + ' ' + list[i]);
});
}
return result;
}
function testList() {
var fnlist = buildList([1, 2, 3]);
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList();
输出如下:
item2 undefined
item2 undefined
item2 undefined
node --inspect-brk=9229 test.js
用 chrome
调试代码,断点设置在 fnlist[j]();
那一行。
如上图所示,我们可以看到,fnlist
这个数组中,3
个匿名函数,都引用了相同的
Closure
。Closure
对象信息如下:
Closure {
i: 3
item: "item2"
list: [1, 2, 3]
}
正因为 3
个匿名函数引用了相同的 Closure
,所以输出了 3
行的
item2 undefined
。
那要怎么修改呢?把 var i
和 var item
改成 let i
和 let item
就行了。
这样,变量 i
和变量 item
的作用域就由函数级别
变成了块作用域
了。
function buildList(list) {
var result = [];
for (let i = 0; i < list.length; i++) {
let item = 'item' + i;
result.push(function() {
console.log(item + ' ' + list[i]);
});
}
return result;
}
function testList() {
var fnlist = buildList([1, 2, 3]);
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList();
输出如下:
item0 1
item1 2
item2 3
上图中可以看到,fnlist
数组中的每个函数中的 [[Scopes]]
数组中都多出了两个
Block
属性的匿名对象。
那在没有 let
和 const
之前,是怎么修改来解决问题的呢?可以在加一个匿名函数。
代码如下:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + i;
result.push(
(function(item, i) {
return function() {
console.log(item + ' ' + list[i]);
};
})(item, i),
);
}
return result;
}
function testList() {
var fnlist = buildList([1, 2, 3]);
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList();
输出如下:
item0 1
item1 2
item2 3
上图中可以看到,fnlist
数组中的每个函数中的 [[Scopes]]
数组中都多出了一个
Closure
属性的匿名对象。看起来似乎外层函数每多一个,[[Scopes]]
中的 Closure
数量就会增加。
可以在加一层看看,代码如下:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + i;
result.push(
(function(item, i) {
return (function(i) {
return function() {
console.log(item + ' ' + list[i]);
};
})(i);
})(item, i),
);
}
return result;
}
function testList() {
var fnlist = buildList([1, 2, 3]);
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList();
读者可自行调试查看结果。
function newClosure(someNum, someRef) {
var num = someNum;
var anArray = [1, 2, 3];
var ref = someRef;
return function(x) {
num += x;
anArray.push(num);
console.log(
'num: ' +
num +
'; anArray: ' +
anArray.toString() +
'; ref.someVar: ' +
ref.someVar +
';',
);
};
}
const obj = {someVar: 4};
const fn1 = newClosure(4, obj);
const fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;
这个例子中,fn1
和 fn2
两个函数内部都有独立的 Closure
。
那如果内部的函数没有用到外部的变量,这种情况下有闭包吗?
那让我们写段代码测试下。
function testOut() {
var testInner = function() {
console.log("I'm inner");
};
return testInner;
}
var f = testOut();
f();
node --inspect-brk=9229 test.js
用 chrome
调试代码,断点设置在 f();
那一行。
可以看到上图中并没有闭包。
用闭包来实现 private
的效果。
var myNamespace = (function() {
// A private counter variable
var myPrivateVar = 0;
// A private function which logs any arguments
var myPrivateMethod = function(foo) {
console.log(foo);
};
return {
// A public variable
myPublicVar: 'foo',
// A public function utilizing privates
myPublicFunction: function(bar) {
myPrivateVar++;
myPrivateMethod(bar);
},
};
})();
myNamespace.myPublicFunction(myNamespace.myPublicVar);
node --inspect-brk=9229 test.js
用 chrome
调试代码,断点设置在最后一行。
https://stackoverflow.com/questions/111102/how-do-javascript-closures-work
该文章中的代码测试环境为 nodejs
,浏览器下行为可能略有不同。
this
就只是一个指针,只能在函数内使用。函数在任何地方使用,都会有一个 this
指针,只是在不同的环境下,this
会指向不同的东西。
x = 1;
function test() {
console.log(this.x);
}
test(); // 1
这种情况下和 C++
中的 this
是类似的。
var obj = {
x: 2,
test() {
console.log(this.x);
},
};
obj.test(); // 2
函数作为构造函数使用:
x = 1;
function test() {
this.x = 2;
}
var obj = new test();
console.log(obj.x); // 2
console.log(x); // 1
call()
,apply()
和 bind()
都是函数的一个方法。
它们都是用来修改 this
指针的指向的。
name = 'xiaowang';
age = 17;
var obj = {
name: 'xiaozhang',
objAge: this.age,
myFun() {
console.log(this.name + ' age:' + this.age);
},
};
var db = {
name: '德玛',
age: 99,
};
obj.myFun.call(db); // 德玛 age:99
obj.myFun.apply(db); // 德玛 age:99
obj.myFun.bind(db)(); // 德玛 age:99
name = 'xiaowang';
age = 17;
var obj = {
name: 'xiaozhang',
objAge: this.age,
myFun(from, to) {
console.log(this.name + ' age:' + this.age, '来自 ' + from + ' 去往 ' + to);
},
};
var db = {
name: '德玛',
age: 99,
};
obj.myFun.call(db, 'US', 'China'); // 德玛 age:99 来自 US 去往 China
obj.myFun.apply(db, ['US', 'China']); // 德玛 age:99 来自 US 去往 China
obj.myFun.bind(db, 'US', 'China')(); // 德玛 age:99 来自 US 去往 China
fun.call(thisArg, arg1, arg2, ...)
注意:该方法的语法和作用与 apply()
方法类似,只有一个区别,就是 call()
方法接受的是一个参数列表,而 apply()
方法接受的是一个包含多个参数的数组。
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}
console.log(new Food('cheese', 5).name); // cheese
上面这个例子中的 Product.call(this, name, price)
把 Product
中的 this
指向了 Food
。
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product(name, price);
this.category = 'food';
}
console.log(typeof new Food('cheese', 5).name); // undefined
console.log(name, price); // cheese 5
上面这个例子中 Product
函数中的 this
在运行时指向了 global
。
func.apply(thisArg, [argsArray])
x = 1;
var obj = {
x: 2,
test() {
console.log(this.x);
},
};
obj.test(); // 2
obj.test.apply(); // 1
obj.test.apply(obj); // 2
apply()
参数为空时,this
指向 global
对象。
function.bind(thisArg[,arg1[,arg2[, ...]]])
bind()
方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被
bind
的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
var module = {
x: 42,
getX: function() {
return this.x;
},
};
// 这里的 this 指向 module
console.log(module.getX());
// 这里的 this 指向 global,由于 global 中没有 x,所以返回 undefined
const unboundGetX = module.getX;
let result = unboundGetX();
console.log(typeof result);
// 给 global 加上 x,
// 注意不能在 x 前面加上 var,const,let,加上了就不是 global 变量了
x = 123;
console.log(unboundGetX());
// bind 返回的函数 boundGetX 中的 this 就是指向 bind 的第一个参数。
const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
https://www.cnblogs.com/Shd-Study/p/6560808.html
http://www.ruanyifeng.com/blog/2010/04/using_this_keyword_in_javascript.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
js
会顺着 __proto__
属性一直向上找,直到 Object.prototype
根对象。js
不会顺着 prototype
属性向上找。class
和 function
有 prototype
。prototype
是给 new
出来的对象使用的。class A{}
这里的 A
是一个类。function B(){}
这里的 B
也是一个类。const C = {}
这里的 C
是一个对象,不是一个类。prototype
这个属性的,都称为一个类。class A{}
只是一个 function A(){}
的语法糖。test.js
node --inspect-brk=9229 test.js
开启调试。
class A {
constructor(key) {
this.key = key;
}
f1() {
console.log("I'm f1");
}
}
function B(key) {
this.key = key;
}
B.prototype = {
f2: function() {
console.log("I'm f2");
},
};
const C = {
key: 'CC',
f3() {
console.log("I'm f3");
},
};
function D() {
console.log("I'm D function");
}
const a1 = new A(10);
const a2 = new A(20);
const b1 = new B(10);
const b2 = new B(20);
const d1 = new D();
console.log('end');
按 F12
在 console
中查看。
输入 a = {}
和 Object.prototype
观察下。
a.__proto__ === Object.prototype
a.__proto__.constructor === Object
Object.prototype.constructor === Object
Object.prototype.__proto__ === null
Object.__proto__ === Function.prototype
Function.prototype.constructor === Function
Function.prototype.__proto__ === Object.prototype
Function.__proto__ === Function.prototype
https://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript
module.js
const a = {};
const b = {};
module.exports = a;
main.js
const a = require('./module.js');
exports
和 module.exports
其实就是两个指针。exports
和 module.exports
默认指向同一个对象 {}
。require
返回的是 module.exports
指向的内容。exports = a;
这个代码相当于 exports
指向了一个新的东西,和
module.exports
指向的东西不一样了。module.exports = a;
就是 module.exports
指向了 a
,那么 require
就会
返回 a
。module.exports = {a, b};
就是 module.exports
指向了新的对象,这个对象里面
包含 a
和 b
两个属性,那么 require
就会返回这个新的对象。exports
看上去好像没什么用啊??是的,确实没用,估计是为了可以少打几个
字母吧。exports
忘记了,只使用 module.exports
。module.js
const a = {};
const b = {};
module.exports = {
a,
b,
};
main.js
const m = require('./module.js');
const {a, b} = require('./module.js');
const {a} = require('./module.js');
const {b} = require('./module.js');
const m = require('./module.js');
是获取到 module.exports
返回的新的对象,
这个新的对象里面包含 a
和 b
两个属性,并把返回的对象保存到变量 m
。
const {a, b} = require('./module.js');
是把 require
返回的对象解构
了,分别
取出其中的 a
和 b
属性所对应的值,并分别保存到 a
和 b
两个变量。
const {a} = require('./module.js');
是把 require
返回的对象解构
了,只
取出其中的 a
属性所对应的值,并保存到 a
变量。
const {b} = require('./module.js');
是把 require
返回的对象解构
了,只
取出其中的 b
属性所对应的值,并保存到 b
变量。
Open up Chrome and type in the address bar : chrome://inspect
or about:inspect
指定在第一行设置断点,也就是一开始运行,就是暂停的状态。
node --inspect-brk=9229 app.js
node --inspect-brk ./node_modules/.bin/jest -i ./src/data-structures/list/__test__/List.test.js
node --inspect app.js
node app.js
使用 ps -ef | grep app.js
找到进程 ID 号。
然后用 kill -SIGUSR1 [PID]
给脚本进程发送 SIGUSR1
信号。
直接按 F5 就行了。
http://www.ruanyifeng.com/blog/2018/03/node-debugger.html https://medium.com/the-node-js-collection/debugging-node-js-with-google-chrome-4965b5f910f4