JavaScript全面学习(函数)
1.定义函数的两种方法:
function abs(x) { if (x >= 0) { return x; } else { return -x; } }
或者
var abs = function (x) { if (x >= 0) { return x; } else { return -x; } }; //记得要加分号,因为这是赋值abs变量
2.调用函数
abs(10, 'blablabla'); // 返回10 abs(-9, 10, 'hehe', null); // 返回9,不受多个参数的影响,因为第一个参数就return了 abs(); // x为undefined,返回NaN
3.关键字arguments
,类似于array.
function foo(x) { alert(x); // 10 for (var i=0; i<arguments.length; i++) { alert(arguments[i]); // 10, 20, 30 } } foo(10, 20, 30);
arguments
最常用于判断传入参数的个数:
if (arguments.length === 0) //如果参数个数为0...
4.用rest获取所有参数:
function foo(a, b) { var i, rest = []; if (arguments.length > 2) { for (i = 2; i<arguments.length; i++) { rest.push(arguments[i]); // 在rest数组后添加元素 } } console.log('a = ' + a); console.log('b = ' + b); console.log(rest); }
一般写成如下更加简洁明了,与上面等效:
function foo(a, b, ...rest) { console.log('a = ' + a); console.log('b = ' + 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 []
注意rest前面有3个点,如果没有的话,相当于rest就是第三个参数:
function foo(a, b,rest) { console.log('a = ' + a); console.log('b = ' + b); console.log(rest); } foo(1, 2, 3, 4, 5); // 结果: // a = 1 // b = 2 // 3 foo(1); // 结果: // a = 1 // b = undefined // undefined
5.接受任意个参数并返回它们的和:
function sum(...rest) { var sum = 0; for(var i of rest) { sum += i; } return sum; }
6.JavaScript在行末自动添加分号
7.不同函数内部的同名变量互相独立,互不影响
8.内部函数可以访问外部函数定义的变量,反过来则不行:
function foo() { var x = 1; function bar() { var y = x + 1; // bar可以访问foo的变量x! } var z = y + 1; // ReferenceError! foo不可以访问bar的变量y! }
9.JavaScript会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部,但不会提升变量的赋值:
'use strict'; function foo() { var x = 'Hello, ' + y; alert(x); var y = 'Bob'; } foo(); //结果显示Hello, undefined,说明y没有值,但是已经声明了
10.JavaScript默认有一个全局对象window
,全局作用域的变量实际上被绑定到window
的一个属性:
'use strict'; var course = 'Learn JavaScript'; alert(course); // 'Learn JavaScript' alert(window.course); // 'Learn JavaScript' 与上面等效
11.let解决块级作用域
'use strict'; function foo() { for (var i=0; i<100; i++) { // } i += 100; // 仍然可以引用变量i }
'use strict'; function foo() { var sum = 0; for (let i=0; i<100; i++) { sum += i; } i += 1; // SyntaxError,无法引用i }
12.关键字const
来定义常量,const
与let
都具有块级作用域:
'use strict'; const PI = 3.14; PI = 3; // 某些浏览器不报错,但是无效果! PI; // 3.14
13.this指的是被调用函数的当前对象 ,多层内嵌会使指示错误,可以在函数开头用var that = this;来捕捉固定this
要指定函数的this
指向哪个对象,可以用函数本身的apply
方法,它接收两个参数,第一个参数就是需要绑定的this
变量,第二个参数是Array
,表示函数本身的参数。
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, 参数为空
对普通函数调用,我们通常把this
绑定为null
。
另一个与apply()
类似的方法是call()
,唯一区别是:
-
apply()
把参数打包成Array
再传入; -
call()
把参数直接按顺序传入。
14.map函数用法,()里调用其他函数
function pow(x) { return x * x; } var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81] 令数组的所有元素都执行()里的函数
还能这样
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
15.reduce函数用法:必须接收两个参数,reduce()
把结果继续和序列的下一个元素做累积计算,例如求和:
var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { return x + y; }); // 25
其他例子
var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { return x * 10 + y; // 把return值当成下一个x,与下一个y循环函数 }); // 13579
16.箭头函数
function (x) { return x * x; } //相当于箭头函数x => x * x
如果包含多条语句,这时候就不能省略{ ... }
和return
如果参数不是一个,就需要用括号()
括起来:
// 两个参数: (x, y) => x * x + y * y // 无参数: () => 3.14 // 可变参数: (x, y, ...rest) => { var i, sum = x + y; for (i=0; i<rest.length; i++) { sum += rest[i]; } return sum; }
如果要返回一个对象(单表达式),要外加一个括号,写成:
x => ({ foo: x })
回顾前面的例子,
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
箭头函数使this
总是指向词法作用域,也就是外层调用者obj,如果是之前的直接创建函数,this就会指向window
由于this
在箭头函数中已经按照词法作用域绑定了,所以,用call()
或者apply()
调用箭头函数时,无法对this
进行绑定,即传入的第一个参数被忽略:
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); //箭头函数里的this已经绑定原对象obj, // 此时的this.birth仍然是1990, // 而不会将this绑定到传入的 birth:2000 } }; obj.getAge(2015); // 25
17.split() 方法用于把一个字符串分割成字符串数组,如果要把字符串数组变成“数”组,要乘以1,可以如下写
var arr=string.split('').map(x => x * 1)
18.parseInt()函数可解析一个字符串,并返回一个整数。
语法:parseInt(string, radix);
string 必需。要被解析的字符串。
radix 可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。
如果省略该参数或其值为 0,则数字将以 10 为基础来解析。
如果它以 “0x” 或 “0X” 开头,将以 16 为基数。
如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。
parseInt("10"); //返回 10 10进制 parseInt("19",10); //返回 19 (10+9) 10进制 parseInt("11",2); //返回 3 (2+1) 2进制 parseInt("17",8); //返回 15 (8+7) 8进制 parseInt("1f",16); //返回 31 (16+15) 16进制 parseInt("010"); //未定:返回 10 或 8
19.filter方法
var arr = [1, 2, 4, 5, 6, 9, 10, 15]; var r = arr.filter(function (x) { return x % 2 !== 0; //把arr中的偶数过滤掉,留下x%2!==0的 }); r; // [1, 5, 9, 15]
var arr = ['A', '', 'B', null, undefined, 'C', ' ']; var r = arr.filter(function (x) { return x && x.trim(); // 把false的空字符串过滤掉 }); //注意:IE9以下的版本没有trim()方法 arr; // ['A', 'B', 'C']
var arr = ['A', 'B', 'C']; var r = arr.filter(function (element, index, self) { // 可以输入3个参数 console.log(element); // 依次打印'A', 'B', 'C' console.log(index); // 依次打印0, 1, 2 console.log(self); // self就是变量arr 会输出3次Array [ "A", "B", "C" ] return true; });
var r = arr.filter(function (element, index, self) { return self.indexOf(element) === index; }); //过滤掉arr中重复的元素 indexOf() 方法返回某个指定的字符串值在字符串中首次出现的位置,注意是首次
选出数组里面的素数:
function get_primes(arr) { var i; var res = arr.filter(function(x){ if(x<2) return false; //排除掉1 for(i=2; i*i<=x; i++){ if(x%i === 0){return false;} //从2开始往上面的数一个个地除,直到确认是不是素数 } return true; //是素数就return true。 }); return res; }
20.对于两个元素x
和y
,如果认为x < y
,则返回-1
,如果认为x == y
,则返回0
,如果认为x > y
,则返回1
21.闭包(不大懂意义):返回一个函数,不立即执行
function lazy_sum(arr) { var sum = function () { return arr.reduce(function (x, y) { return x + y; }); } return sum; } var f = lazy_sum([1, 2, 3, 4, 5]); // function sum() //调用lazy_sum()时,返回的并不是求和结果,而是返回求和函数 f(); // 15 //调用函数f时,才是真正计算求和的结果
当我们调用lazy_sum()
时,每次调用都会返回一个新的函数,即使传入相同的参数(每次调用的结果互不影响):
var f1 = lazy_sum([1, 2, 3, 4, 5]); var f2 = lazy_sum([1, 2, 3, 4, 5]); f1 === f2; // false
返回的函数并没有立刻执行,而是直到调用了f()
才执行:
function count() { var arr = []; for (var i=1; i<=3; i++) { // 这里正常思维是最终i=4,但是没想到4最后居然进入循环里面运算了??最终return 4*4 arr.push(function () { return i * i; }); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2]; f1(); // 16 f2(); // 16 f3(); // 16 等到3个函数都返回时,它们所引用的变量i已经变成了4
正确写法:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) { // 在里面再创建一个函数,用该函数的参数绑定循环变量当前的值
return function () {
return n * n;
}
})(i)); // 这样是用i去立即执行函数 注意括号的括法,(function (x) { return x * x }) (i);
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); // 1
f2(); // 4
f3(); // 9
借助闭包,可以封装一个私有变量。例如用JavaScript创建一个计数器:
'use strict'; function create_counter(initial) { var x = initial || 0; //在没有初始值initial的时候给一个初始值 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
换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来:
function make_pow(n) { return function (x) { return Math.pow(x, n); // 计算x的n次方 } } // 创建两个新函数: var pow2 = make_pow(2); var pow3 = make_pow(3); pow2(5); // 25 相当于Math.pow(5, 2)
pow3(7); // 343 相当于Math.pow(7, 3)
只需要用函数,就可以用计算机实现运算,而不需要0
、1
、2
、3
这些数字和+
、-
、*
、/
这些符号:
'use strict'; // 定义数字0: var zero = function (f) { return function (x) { return x; } }; // 定义数字1: var one = function (f) { return function (x) { return f(x); } }; // 定义加法: function add(n, m) { return function (f) { return function (x) { return m(f)(n(f)(x)); } } } // 计算数字2 = 1 + 1: var two = add(one, one); // 计算数字3 = 1 + 2: var three = add(one, two); // 计算数字5 = 2 + 3: var five = add(two, three); // 给3传一个函数,会打印3次: (three(function () { console.log('print 3 times'); }))(); // 给5传一个函数,会打印5次: (five(function () { console.log('print 5 times'); }))();
22.(意义不明啊!)generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。
编写一个产生斐波那契数列的函数:
function* fib(max) { //注意多出的*号 var t, a = 0, b = 1, n = 1; while (n < max) { yield a; t = a + b; a = b; b = t; n ++; } return a; } fib(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window} // fib(5)仅仅是创建了一个generator对象,还没有去执行它
//可以这样调用: 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: true}
next()方法每次遇到yield x;
就返回一个对象{value: x, done: true/false}
,然后“暂停”。返回的value
就是yield
的返回值,done
表示这个generator是否已经执行结束了。
一般这样调用:
for (var x of fib(6)) { // 注意要6,坑啊 console.log(x); // 依次输出0, 1, 1, 2, 3 }
下面是天书:
用generator的话,AJAX可以大大简化代码:
try { r1 = yield ajax('http://url-1', data1); r2 = yield ajax('http://url-2', data2); r3 = yield ajax('http://url-3', data3); success(r3); } catch (err) { handle(err); }
用一个对象来保存状态:
var fib = { a: 0, b: 1, n: 0, max: 5, next: function () { var r = this.a, t = this.a + this.b; this.a = this.b; this.b = t; if (this.n < this.max) { this.n ++; return r; } else { return undefined; } } };
自增,并保存数字:
'use strict'; function* next_id() { var i=1; while(true) { yield i++; } }