定义和调用
用两种办法定义函数,两种完全等价,但是用赋值语句形式定义函数的时候,为了保证语句的完整性,需要在函数末尾加上;
。
const func1 = function(){};
function func2(){}
调用的时候如果不传入参数会收到undefined
。
const abs = function(x){return x > 0 ? x : -x;};
abs(); // NaN
function abs2(x){
if(x === undefined){
throw 'empty args';
}else{
return x > 0 ? x : -x;
}
}
abs2();
下面是创建一个匿名函数并且立即执行的例子。
// 理论上这么写是可以的,但是会报语法错误。
function(msg){console.log(msg);}('hello world')
(function(msg){console.log(msg);})('hello world'); // hello world
arguments
JavaScript还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array。
const obj = {
_name: 'jim',
name: function(_){
return !arguments.length ? this._name : (this._name = _, this);
}
}
obj.name(); // 'jim'
obj.name('peter'); // { _name: 'peter', name: [Function: name] }
obj.name() // 'peter'
实际上arguments最常用于判断传入参数的个数,可以把某个参数设置为可选参数。
const foo = function(a, b, c){
if(arguments.length === 2){
c = b;
b = null;
}
// ...
}
rest
由于JavaScript函数允许接收任意个参数,于是我们就不得不用arguments
来获取所有参,为了获取额外定义的参数,我们不得不使用``rest```参数,这个是ES6新增加的标准。
function sum(base, ...rest){
return base + rest.reduce((x, y) => x + y);
}
sum(10, 1, 2, 3); // 16
变量作用域
var
:作用域是函数内部, 并且会出现变量提升的现象,提升变量的声明,但是不会提升变量的赋值。由于JavaScript的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var申明函数内部用到的所有变量。
function foo(){
var x = 1;
console.log(x, y);
var y = 2;
}
foo() // 1 undefined
function foo(){
for(var i = 0; i < 10; i++){};
console.log(i);
}
foo() // 10
let
:局部,块级作用域,同时不存在变量提升。const
:声明一个常量。- 全局变量:JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误。
// brower
'console' in window // true
// node
'console' in global // true
- 名称空间:全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。
const myApp = {};
myApp.name = 'app'
myApp.hello = function(){console.log('hhh');}
方法
this
- 用
function
关键字定义函数的方法,this
指向的是该对象本身。如果方法中嵌套定义函数,在该函数中使用了this
,在strict模式下指向undefined
,非strict模式下指向全局变量。 - 必须用
obj.foo()
的形式调用函数,否者会造成this
指向不正确的情况。
const obj = {
name: 'jim',
age: 10,
sex: 0,
printName: function(){
console.log(this.name, this);
},
printAge: function(){
function format(){
return `${this.age}岁`;
}
console.log(format());
},
printSex: function(){
let that = this;
function format(){
return `${that.sex === 0 ? '男' : '女'}`
}
console.log(format());
}
}
obj.printName(); // jim
obj.printAge(); // undefined岁
obj.printSex(); // '男'
const printName = obj.printName;
printName(); //undefined
apply & call
- 可以改变函数中
this
的指向。
const add = function(x, y){return this.base + x + y;}
const obj = {base: 10};
add.apply(obj, [1, 2]); // 13, apply()把参数打包成Array再传入
add.call(obj, 1, 2); // 13, call()把参数按顺序传入
- 可以动态改变函数的行为。
const add = function(x, y){return x + y;}
const addLog = function(){
console.log('add', add.apply(null, arguments));
}
addLog(1, 2);
// add 3
高阶函数
JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。下面的self
指向数组本身。
map
:将输入的函数作用域数组的每一个元素上,并返回一个新的元素。
const a = [1, 2, 3];
const b = a.map((item, index, self) => item * index); // [0, 2, 6]
reduce
:Array的reduce()把一个函数作用在这个Array的[x1, x2, x3…]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算。
const a = [1, 2, 3];
const b = a.reduce((x, y, self) => 10 * x + y ); // 123
filter
:筛选。下面是一个删除空字符的例子和一个去除重复元素的例子。
const a = ['a', undefined, null, 'b', 'c', ' '];
const b = a.filter(item => item && item.trim());
const a = [1, 2, 3, 1, 5];
const b = a.filter((item, index) => a.indexOf(item) === index);
sort
:会直接对数组产生修改。
every
:数组中所有的元素是不是都满足某个条件。
find
:找到第一个满足要求的元素。
findIndex
:找到第一个满足要求的元素的index。
forEach
:遍历数组。
闭包
闭包就是把函数作为返回值。
- 延迟函数的执行。
const sum = (a, b) => a + b;
const lazySum = (a, b) => sum;
sum(1, 2); // 3
const foo = lazySum();
foo(1, 2); // 3
- 闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。d3.js中就有大量这样的用法。
const createCounter = (inital) => {
let x = inital || 0;
return {
inc: () => x += 1
}
}
const counter = createCounter();
counter.inc(); // 1
counter.inc(); // 2
- 闭包还可以把多参数的函数变成单参数的函数。
const makePower = n => x => Math.pow(x, n);
const power2 = makePower(2);
power2(4); // 16
- 通闭包看
let
和var
的区别。
const count1 = cnt => {
const arr = [];
// 每一次都创建一个新的变量
for(let i = 0; i < cnt; i++){
arr.push(() => i * i);
}
return arr;
}
const count2 = cnt => {
const arr = [];
for(var i = 0; i < cnt; i++){
arr.push(() => i * i);
}
return arr;
}
const count3 = cnt => {
const arr = [];
for(var i = 0; i < cnt; i++){
// 用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变。
arr.push((n => () => n * n)(i));
}
return arr;
}
const arr1 = count1(3), arr2 = count2(3), arr3 = count3(3);
arr1[0](); // 0
arr2[0](); // 9
arr3[0](); // 0
箭头函数
箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。由于this
在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略。
const obj = {
sex: 0,
printSex: function(){
const format = () => `${this.sex === 0 ? '男' : '女'}`;
console.log(format.call({sex: 1}));
}
}
obj.printSex() // 男
generator
因为generator可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数,利用这一点,写一个generator就可以实现需要用面向对象才能实现的功能。下面是一个计数器和生成斐波拉起数列的例子。
function* next_id(){
let id = 0;
while(true){
yield id++;
}
}
const id = next_id();
for(let i = 0; i < 10; i++){
console.log(id.next());
}
// {value: 0, done: false}
function* fib(max){
let a = 0,
b = 1;
for(let i = 0; i < max; i++){
yield b;
[b, a] = [a + b, b];
}
}
const f = fib(5)
for(let n of f){
console.log(n);
}
// 1, 1, 2, 3, 5