1.定义函数
function abs(x){
if (x>=0){
return x;
}else{
return -x;
}
}
如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined
由于javascript的函数也是一个对象,abs()函数实际上是一个函数对象,而函数名abs可以视为指向该函数的变量
第二种定义函数的方式
var abs=function(x){
if(x>=0){
return x;
}else{
return -x;
}
};
这样,函数是一个匿名函数,他没有函数名。但是这个匿名函数赋值给了变量abs,所以通过变量abs就可以调用该函数。
上述的定义完全等价,第二种方式按照完整性语法需要在函数体末尾加一个;,表示赋值语句结束。
abs(10);//返回10
abs(10,'hehe');//返回9,多余的参数没有问题
abs();//返回NaN,此时函数收到的x是undefined,计算结果为NaN
2 arguments只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。
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可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值。
function abs() {
if (arguments.length === 0) {
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
abs(); // 0
abs(10); // 10
abs(-9); // 9
3 rest参数
由于javascript函数允许接收任意多个参数,于是需要用arguments来获取所有参数。
function foo(a,b){
var i,rest=[];
if(arguments.length>2){
for(i=2;i<arguments.length;i++){
rest.push(arguments[i]);
}
}
console.log('a'+a);
console.log('b'+b);
console.log(rest);//获得额外的参数
}
ES6标准引入了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参数只能写在最后,前面用…表示。传入的参数先绑定a,b,多余的参数以数组的形式交给变量rest。
4 注意return语句
javascript引擎有一个在航模自动添加分号的机制,这可能带来意想不到错误。
function foo(){
return {name:'foo'};
}
foo();
由于引擎在行模自动加分号的机制,上面的代码变成了
function foo(){
return ; //自动添加了分号,相当于return undefined
{name:'foo'};//这个语句没有执行到
}
正确的多行写法:
function foo(){
return {
name:'foo'
};
}
5 变量作用域
变量提升: javascript函数有个特点,它会先扫描整个函数体语句,把所有声明的变量提升到函数顶部。
function foo(){
var x='hello,'+y;//不会报错,因为在之后声明了
alert(x);
var y='lwg';
}
foo();
显示hello,undefined,说明变量y的值是undefined.这正是因为javascript引擎自动提升了变量y的声明,但是不会提升变量y的赋值。
所以上面的代码相当于
function foo(){
var y;
var x='hello, '+y;
alert(x);
y='lwg';
}
所以应该在函数内部首先声明所有变量。
6 全局作用域
javascript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性
var course='learn javascript';
alert(course);//learn javascript
alert(window.course);//learn javascript
javascript实际上只有一个全局作用域,任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域也没有找到,则会报Reference Error 错误。
名字空间
全局变量会绑定到window上,不同的javascript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名空间冲突,并且很难发现。
减少冲突的一个办法就是把自己的所有变量和函数全部绑定到一个全局变量中,例如:
//唯一的一个全局变量myapp
var myapp={};
//其他变量
myapp.name='myapp';
myapp.version=1.0;
//其他函数
myapp.foo=function(){
return 'foo';
};
7 局部作用域
function foo() {
for (var i=0; i<100; i++) {
//
}
i += 100; // 仍然可以引用变量i
}
因为在for循环的内部无法定义局部作用域的变量,所以为了解决块级作用域,引入了新的关键字let
function foo(){
var sum=0;
for(let i=0;i<100;i++)
{
sum+=i;
}
i+=1;//SyntaxError
}
新的关键字const来定义常量,const和let都具有块级作用域
const PI=3.14;
PI=3;//某些浏览器不报错,但是无效果
PI;//3.14
8 方法
var xiaoming={
name:'小明',
birth:1990,
age:function(){
var y=new Date().getFullYear();
return y-this.birth;
}
};
this是一个特殊变量,它始终指向当前对象。
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25, 正常结果
getAge(); // NaN
如果函数以对象的方式调用,比如xiaoming.age(),该函数的this指向被调用的对象,也就是xiaoming。
如果单独调用函数,比如getAge(),此时该函数的 this指向全局对象,也就是window。
如果这儿写
var fn=xiaoming.age;
fn();//NaN
要保证this指向正确,必须用obj.**()形式调用
在严格模式下,函数this会指向undefined,这样错误及时暴露出来,并没有解决this指向正确的位置。
如果重构一下:
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - this.birth;
}
return getAgeFromBirth();
}
};
xiaoming.age(); // 错误
还是错误,因为this只是在age方法的函数内部指向小明,在函数内部定义的函数,this又指向了undedined。
修复的办法:
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
9 apply
使用函数本身的apply方法,可以指定函数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, 参数为空
另一个与apply()类似的方法是call(),唯一的区别是:
apply()把参数打包成Array再传入
call()把参数按顺序传入
对普通函数调用,通常把this绑定为null
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
10 高阶函数
function add(x,y,f){
return f(x)+f(y);
}
11 map/reduce
由于map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果
function pow(x){
return x*x;
}
var arr=[1,2,3,4,5,6,7];
arr.map(pow);//[1,4,9,16,25,36,49,]
map()传入的参数是pow,即函数对象本身。
map()作为高阶函数,事实上把运算规则抽象了。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
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
12 filter
filter是一个常用的操作,用于把Array的某些元素过滤掉,然后返回剩下的元素。 filter把传入的函数依次作用于每个元素,然后根据返回值是真还是假决定保留还是丢弃该元素
一个数组中,删除偶数,只保留奇数
var arr=[1,2,3,4,5,7,8];
var r=arr.filter(function (x){
return x%2!==0;
});
r;//1 3 5 7
13 sort 算法
对于两个元素x和y,如果认为x
// 看上去正常的结果:
['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft'];
// apple排在了最后:
['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple']
// 无法理解的结果:
[10, 20, 1, 2].sort(); // [1, 10, 2, 20]
第二个排序时因为字符串根据ASCII码进行排序,小写字母a在大写字母之后。
第三个排序的结果是因为默认把所有元素先转换为string再排序,结果10排在了2前面,因为字符1比字符2的ASCLL码小。
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
}); // [1, 2, 10, 20]
如果倒序排序,可以把大的数放前面
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return 1;
}
if (x > y) {
return -1;
}
return 0;
}); // [20, 10, 2, 1]
默认情况下,对字符串的排序,是按照ASCLL的大小比较的。忽略大小写,可以把排序的全部转成大写。
sort()方法会直接对Array进行修改,它返回的结果仍然是当前的Array
var a1 = ['B', 'A', 'C'];
var a2 = a1.sort();
a1; // ['A', 'B', 'C']
a2; // ['A', 'B', 'C']
a1 === a2; // true, a1和a2是同一对象
14 箭头函数
x>=x*x;
//箭头函数相当于
function(x){
return x*x;
}
箭头函数相当于匿名函数,并且简化了函数定义。
另一种箭头函数,包含多条语句,这时候就不能省略{}和return
x=>{
if(x>0){
return x*x;
}else{
return -x*x;
}
}
如果参数不是一个,就需要用()括起来
// 两个参数:
(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;
}
箭头函数和匿名函数有个明显的区别:
箭头函数内部的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(); // 25
由于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);
}
};
obj.getAge(2015); // 25
15 generator
generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。