函数
- js中的函数可以没有return,此时会返回
undefined。
- JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数
- 少于定义的参数也可以,这时返回函数本身这个对象,不会报错
- js中的函数都是绑定在一个变量上的
访问/引用函数可以有时候可以直接通过函数名,不需要()
无函数名,结果直接赋值给变量的函数
利用arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值
arguments.length
得到传入参数的个数arguments[?]
得到一个参数 0开始 ```javascript function abs() { if (arguments.length === 0) {
} var x = arguments[0]; return x >= 0 ? x : -x; }return 0;
abs(); // 0 abs(10); // 10 abs(-9); // 9
<a name="OrhYX"></a>
## rest
- rest作用类似于arguments。`...rest`表示多余参数都作为数组元素保存在rest数组中
- rest时ES6的特性
```javascript
function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
foo(1, 2, 3, 4, 5); 1对应a,2对应b 之后全部参数都是rest数组中的元素
foo(1) 这时1对应a,b和rest没有对应参数,则b为undefined,rest为空数组
return的坑
js中很多时候都会自动加分号,所以return与返回值最好写在一行,不能写在一行时就使用大括号(这时必须返回对象类型)
return { ... }
变量提升
avaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部
- 即在函数内即便使用变量的语句在变量定义语句之前,也不会报错。但是不会对变量的值进行提升,提升变量的值仍旧为
undefined
- 即在函数内即便使用变量的语句在变量定义语句之前,也不会报错。但是不会对变量的值进行提升,提升变量的值仍旧为
js中最外层的函数都是全局函数,所以也是全局对象window的一个属性
名字空间
全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现
- 减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中 ```javascript // 当前js唯一的全局变量MYAPP: var MYAPP = {};
// 其他变量: MYAPP.name = ‘myapp’; MYAPP.version = 1.0;
// 其他函数: MYAPP.foo = function () { return ‘foo’; };
<a name="lO9HC"></a>
## 局部变量的作用域
- **js函数中的变量不像java那样只局限于当前的块中,js函数中的变量在整个函数中都能访问到**
- 如函数中的循环语句定义的变量,在循环结束仍旧能访问
- **而同样循环中开头定义的重新定义变量语句不会生效,变量在循环中相当于静态效果**
```javascript
function foo() {
for (var i=0; i<100; i++) {
}
console.log(i)
}
foo()
let
-
const
const也是ES6中的特性,用于定义一个常量,const常量具有块级作用域
const常量生成后无法进行修改,固定初始值,修改则报错。(某些浏览器虽然不报错但是无效果就是)
方法
给对象的一个属性绑定一个函数,则该函数是一个方法
- 方法内部具有一个
this
变量,指向当前对象 ```javascript function getAge() { var y = new Date().getFullYear(); return y - this.birth; }
var xiaoming = { name: ‘小明’, birth: 1990, age: getAge
};
或者 var xiaoming = { name: ‘小明’, birth: 1990, age: function () { var y = new Date().getFullYear(); return y - this.birth; } };
<a name="ZCW2h"></a>
# 高阶函数
- 一个函数就接收另一个函数作为参数,这种函数就称之为高阶函数
- **高阶函数接收函数参数时可以传入函数名作参数,也可以使用匿名函数作为传参。**
- **使用匿名函数作为传参时又称为回调函数**
- 注意如`alert` `console.log`等是函数,自然也能作为高阶函数的传参
```javascript
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
return x % 2 !== 0;
});
--------------------------------
function c(x) {
return x % 2 !== 0;
}
var r = arr.filter(c);
Array中的实用高阶
map()
map()是Array中的一个函数,接收一个函数作为参数,一个数组调用map后返回每个元素都被传入函数处理后的数组
function pow(x) { return x * x; } var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce()
也是数组的函数,Array的reduce()把一个函数作用在这个Array的[x1, x2, x3…]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是:
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x + y;
}); //返回25:1+3=4 4+5=9 9+7=16 16+9=25
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x * 10 + y;
}); // 13579 把数组元素整合为一个数
filter()
- 用于把Array的某些元素过滤掉,然后返回剩下的元素。Array的filter()也接收一个函数,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
- true保留,false丢弃
- filter()接收回调函数时,其实可以有多个参数。通常我们仅使用第一个参数,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身
```javascript var arr=….; r = arr.filter(function (element, index, self) { return self.indexOf(element) === index; //去除重复元素,r最终得到无重复元素的数组 });var arr = ['A', 'B', 'C']; var r = arr.filter(function (element, index, self) { console.log(element); // 依次打印'A', 'B', 'C' console.log(index); // 依次打印0, 1, 2 console.log(self); // self就是变量arr return true; });
<a name="p4UqJ"></a>
### sort()
- sort()也是Array的方法,但是这个排序是会将数组元素转为ASCII码进行排序,这是个坑
- 如10和2排序,转为字符串后逐个字符进行比较,因为'10'的第一个字符1在ASCII中小于2,所以最终10会在2前面
- sort()也是一个高阶函数,可以接收一个函数,这样我们可以自己来实现:数字时根据大小排序,字符串时根据ascii排序,忽略大小写排序...等多种功能 见[链接](https://www.liaoxuefeng.com/wiki/1022910821149312/1024328479098336)
<a name="RTUky"></a>
### every()
- 用于字符串数组,字符串数组每一个元素都进行传参函数,如果每一个元素都得到true,那整个数组的every返回true,有一个不通过就返回false
<a name="SlgtW"></a>
### find()
- 返回符合传参函数要求的第一个元素,没有则返回undefined
<a name="TGNgV"></a>
### findIndex()
- 同find(),但是返回的是索引,没有则返回-1
<a name="eFr3O"></a>
### forEach()
- 该高阶函数不会返回数组元素或者数组,所以一般用作遍历用,可以自己实现更多功能
```javascript
var arr = ['Apple', 'pear', 'orange'];
arr.forEach(console.log); // 依次打印每个元素
闭包
- 函数返回一个函数。因为不能直接返回函数,所以我们可以使用匿名函数绑定在一个变量上返回变量
- 返回的变量是匿名函数对象,高阶函数的相关参数和变量都保存在返回的函数中。此即闭包
- 调用闭包函数时才是函数执行,返回闭包时函数并未执行
- 每次调用高阶函数返回的闭包函数都不是同一个对象
```javascript
function lazy_sum(arr) {
var sum = function () {
} return sum; } var f = lazy_sum([1, 2, 3, 4, 5]); console.log(f) //[Function: sum] 返回的是一个函数,即f等同于一个函数对象 f(); //现在才是执行sum,lazy_sum的内容保存到了sum()中,所以sum()能正常运行return arr.reduce(function (x, y) { return x + y; });
<a name="hgvJ5"></a>
## 闭包引用共享的问题
- 这是“调用闭包函数时才是函数执行,返回闭包时函数并未执行”所引起的问题。如果返回多个闭包函数,多个闭包函数执行不同操作,但是最终可能结果一样
```javascript
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () { //注意这里是存入一个函数,不是存入结果,i*i函数还没有进行执行
return i * i;
});
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
//结果并非1,4,9 三者都是16
- 全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个ii函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。*(引用的是同一个内存地址)
- 返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
- 如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
```javascript
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
} return arr; }arr.push((function (n) { return function () { return n * n; } })(i));
var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2];
f1(); // 1 f2(); // 4 f3(); // 9
注意这里用了一个“创建一个匿名函数并立刻执行”的语法:<br />(**function** (x) { **return** x * x; })(3); _// 9_ <br />理论上讲,创建一个匿名函数并立刻执行可以这么写:<br />**function** (x) { **return** x * x } (3); <br />但是由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来:<br />(**function** (x) { **return** x * x }) (3); <br />通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:<br />(**function** (x) { **return** x * x; })(3);
<a name="GZdSX"></a>
## 闭包实现封装
- 在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
- 实现一个计时器
```javascript
'use strict';
function create_counter(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
它用起来像这样:
var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3
var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13
闭包实现类似多态的效果
'use strict';
function make_pow(n) {
return function (x) {
return Math.pow(x, n);
}
}
// 创建两个新函数:
var pow2 = make_pow(2); //实现平方计算
var pow3 = make_pow(3); //实现立方计算
console.log(pow2(5)); // 25
console.log(pow3(7)); // 343
箭头函数
- 箭头函数即匿名函数的简化,是ES6中的特性
- =>前为参数,后面为函数体,多条语句时使用{},多个参数时使用()
- 如果要返回一个对象,就要注意,如果是单表达式,不能
x => { foo: x }
而是x => ({ foo: x })
(x, y) => x * x + y * y 等同于 function(x,y) { return x * x+y*y; }
- 箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj
- 如果使用箭头函数,以前的那种hack写法:var that = this; 就不再需要了
- 由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略
```javascript
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
}; return fn(); } }; 现在,箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj:return new Date().getFullYear() - this.birth; // this指向window或undefined
- 由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略
```javascript
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象 return fn(); } }; obj.getAge(); // 25
```javascript
var obj = {
birth: 1990,
getAge: function (year) {
var b = this.birth; // 1990
var fn = (y) => y - this.birth; // this.birth仍是1990
return fn.call({birth:2000}, year);
}
};
obj.getAge(2015); // 25