JavaScript 之函数
定义函数
function abs(x) { //匿名函数 if (x >= 0) { return x; } else { return -x; } } //没有return会返回undefined var abs = function (x) { //可以通过abs调用 if (x >= 0) { return x; } else { return -x; } }; //js不限定传入的参数数目,多了不会被使用 abs(10, 'blablabla'); // 返回10 abs(); //NaN //arguments指向当前调用者传入的所有参数,常用于判断传入参数长度arguments.length function foo(x) { console.log('x = ' + x); // 10 for (var i=0; i<arguments.length; i++) { console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30 } } foo(10, 20, 30); //rest参数允许接收多个参数 function foo(a,b,...rest) { console.log(a); console.log(b) console.log(rest); } foo(1,2,3,4,5) // a = 1 // b = 2 // Array [ 3, 4, 5 ] foo(1); // 结果: // a = 1 // b = undefined // Array []
变量作用域
变量提升
//先扫描整个函数,把变量声明提升到函数头部 function foo() { var x = 'Hello, ' + y; console.log(x); var y = 'Bob'; } foo(); function foo() { var y; // 提升变量y的申明,此时y为undefined var x = 'Hello, ' + y; console.log(x); y = 'Bob'; }
全局作用域
不在任何函数内定义的变量就具有全局作用域。JavaScript默认有全局对象window
,变量会被绑定到window
的一个属性上:var course = 'Learn JavaScript'; alert(course); // 'Learn JavaScript' alert(window.course); // 'Learn JavaScript' //alert()函数其实也是window的一个变量
变量如果在当前函数作用域中未找到,会到全局作用域寻找,若还为找到,则返回
ReferenceError
。名字空间
为了减少命名冲突,可以把定义的变量和函数绑定到一个全局变量上。
// 唯一的全局变量MYAPP: var MYAPP = {}; // 其他变量: MYAPP.name = 'myapp'; MYAPP.version = 1.0; // 其他函数: MYAPP.foo = function () { return 'foo'; };
局部作用域
function foo() { var sum = 0; for (let i=0; i<100; i++) { //let替代var可以申明一个块级作用域的变量 sum += i; } // SyntaxError: i += 1; }
常量
const PI = 3.14; //代替以前的PI=3.14只是不修改
解构赋值
var [x, y, z] = ['hello', 'JavaScript', 'ES6']; let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']]; let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素 var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school', address: { city: 'Beijing', street: 'No.1 Road', zipcode: '100001' } }; var {name, age, passport} = person; //取出特定属性值 var {name, address: {city, zip}} = person; //zip为undefined,addresss为了让city和zip获得嵌套的address对象的属性,查看address返回Uncaught ReferenceError // 把passport属性赋值给变量id: let {name, passport:id} = person; // 如果person对象没有single属性,默认赋值为true: var {name, single=true} = person; // 变量已经被声明了,会发生错误 var x, y; {x, y} = { name: '小明', x: 100, y: 200}; // 语法错误: Uncaught SyntaxError: Unexpected token,解决办法 ({x, y} = { name: '小明', x: 100, y: 200}); //可以交换变量 var x=1, y=2; [x, y] = [y, x] //快速获取域名和路径 var {hostname:domain, pathname:path} = location; //如果函数接收对象作为参数,可直接传入对象的属性 function buildDate({year, month, day, hour=0, minute=0, second=0}) { return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second); } buildDate({ year: 2017, month: 1, day: 1 }); // Sun Jan 01 2017 00:00:00 GMT+0800 (CST)
方法
绑定到对象上的函数称为方法。
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: '小明', birth: 1990, age: getAge }; xiaoming.age(); // 25, 正常结果 getAge(); // NaN 此时this指向全局对象windows //以下方法也是不行的 var fn = xiaoming.age; // 先拿到xiaoming的age函数 fn(); // NaN 必须用obj.xxx()形式调用才行 'use strict'; //此模式下会this会指向undefined var xiaoming = { name: '小明', birth: 1990, age: function () { var that = this; // 在方法内部一开始就捕获this function getAgeFromBirth() {//若不捕获会出错 var y = new Date().getFullYear(); return y - that.birth; // 用that而不是this } return getAgeFromBirth(); } }; xiaoming.age(); // 25
在独立的函数调用中,根据是否为
strict
模式,this指向undefined或者window,使用apply可以指定函数this指向那个对象。function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: '小明', birth: 1990, age: getAge }; xiaoming.age(); // 25 getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
与
apply()
类似的方法是call()
,区别如下:apply()
把参数打包成Array
再传入;call()
把参数按顺序传入。
对于普通函数的调用,
this
绑定为null
。JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
高阶函数
一个函数可以接受另一个函数作为参数,这种函数称为高阶函数。
'use strict'; function add(x, y, f) { return f(x) + f(y); } var x = add(-5, 6, Math.abs); // 11
- map
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
- reduce
var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { return x + y; }); // 25 arr.reduce(function (x, y) { return x * 10 + y; }); // 13579 function string2int(s) { //字符串'13579' var ss=s.split(""); var cs=ss.map(function(x){ return +x; }); var r=cs.reduce(function(x,y){ return 10*x+y; }); return r; //13579 }
- filter
filter()
把传入的函数依次作用于每个元素,然后根据返回值是true
还是false
决定保留还是丢弃该元素。var arr = [1, 2, 4, 5, 6, 9, 10, 15]; var r = arr.filter(function (x) { return x % 2 !== 0; }); r; // [1, 5, 9, 15] var arr = ['A', '', 'B', null, undefined, 'C', ' ']; var r = arr.filter(function (s) { return s && s.trim(); // 注意:IE9以下的版本没有trim()方法 }); r; // ['A', 'B', 'C'] //filter还可以接受回调函数 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; }); //元素去重 r = arr.filter(function (element, index, self) { return self.indexOf(element) === index; });
- sort
// apple排在了最后:因为小写字母ASCII在大写字母之后 ['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple'] // 无法理解的结果:默认将所有元素转为String再排序 [10, 20, 1, 2].sort(); // [1, 10, 2, 20] let num=[10,20,1,2]; num.sort(function (x,y) { //return x-y; //从小到大排序 return y-x; //大到小 }); console.log(num);
sort()
会对原数组修改,返回的也是原数组。 - Array
every()
方法可以判断数组的所有元素是否满足测试条件。let arr2 = ['Apple', 'pear', 'orange']; console.log(arr2.every(function (s) { return s.length > 0; })); // true, 因为每个元素都满足s.length>0
find
方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined。console.log(arr2.find(function (s) { return s.toLowerCase() === s; })); // 'pear', 因为pear全部是
findIndex()
和find()
类似,也是查找符合条件的第一个元素,不同之处在于findIndex()会返回这个元素的索引,如果没有找到,返回-1。console.log(arr.findIndex(function (s) { return s.toLowerCase() === s; })); // 1, 因为'pear'的索引是1
- forEach()和map()类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。
arr.forEach(console.log); // 依次打印每个元素
闭包
函数作为返回值。返回函数不要引用任何循环变量,或者后续会发生变化的变量。
function count() { var arr = []; for (var i=1; i<=3; i++) { arr.push(function () { return i * i; }); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2]; //f1(),f2(),f3()都是16 //这样修改可以保持结果正确 function count() { var arr = []; for (var i=1; i<=3; i++) { arr.push((function (n) { return function () { return n * n; } })(i)); } return arr; } //创建匿名函数,会立即执行,调用f1即可,若如上一种形式需要f1(); arr.push((function (x) { return x*x; }(i)));
使用闭包,可以实现私有变量的封装。
function create_counter(initial) { let x=initial||0; return { inc:function () { x+=1; return x; } } } let c1=create_counter(); console.log(c1.inc()); console.log(c1.inc())
将多参数函数变为但参数函数。
function make_pow(n) { return function (x) { return Math.pow(x,n); } } let pow2=make_pow(2); let pow3=make_pow(3); console.log(pow2(5)); console.log(pow3(5));
箭头函数
相当于匿名函数,简化了函数定义。
(x, y, ...rest) => { var i, sum = x + y; for (i=0; i<rest.length; i++) { sum += rest[i]; } return sum; } //this总是指向词法作用域 var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象 return fn(); } }; obj.getAge(); // 29
generator
generator可以在执行过程中多次返回。
function* fib(max) { var t, a = 0, b = 1, n = 0; while (n < max) { yield a; [a, b] = [b, a + b]; n ++; } return; } var f = fib(5); f.next(); // {value: 0, done: false} f.next(); // {value: 1, done: false} f.next(); // {value: 1, done: false} f.next(); // {value: 2, done: false} f.next(); // {value: 3, done: false} f.next(); // {value: undefined, done: true} //true表示生成器已经结束 for (var x of fib(10)) { console.log(x); // 依次输出0, 1, 1, 2, 3, ... }