函数的命名规范与变量类似,不再赘述。
JavaScript 的函数可以嵌套,也就是函数内部可以再定义函数。
// 参数列表不是必须的,可空 function fun(a, b, c, str) { // do something return str; //没有return,函数执行完也会返回结果,为undefined。 } // 第二种函数定义,匿名函数,配合事件使用 var num = function(x) { // do something return x; }; // 在这种方式下,函数定义成匿名函数,无函数名。但函数赋值给了变量 abs, // 所以通过变量 abs 就可以调用该函数。 // 以上两种定义完全等价。
// 1.直接调用 function fun(a, b) { return a+b; } var result = fun(3, 5); // 2.事件调用 <button onclick='fun(3, 5)'>点我调用方法</button>s
自执行函数就是指无需手动调用,在声明函数时自动调用的函数类型。
// 声明自执行函数的语法结构有三种 // 1.!function(形参列表){}(实参列表); !function(num1, num2) { var sum = num1 + num2; }(1, 2); // 2.(function(形参列表){}(实参列表)); // 这种写法使用圆括号将匿名函数及之后的圆括号包裹成为一个整体。 (function(num1, num2) { var sum = num1 + num2; }(1, 2)); // 3.(function(形参列表){})(实参列表); (fuction(num1, num2) { var sum = num1 + num2; })(1, 2);
arguments
参数arguments
关键字只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments
类似 Array
但它不是一个 Array
:
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); //x = 10 arg 0 = 10 arg 1 = 20 arg 2 = 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
实际上 arguments
最常用于判断传入参数的个数。
// foo(a[, b], c) // 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null: function foo(a, b, c) { if (arguments.length === 2) { // 实际拿到的参数是a和b,c为undefined c = b; // 把b赋给c b = null; // b变为默认值 } // ... }
arguments
对象除了可以保存实参列表外,还有一个重要的属性 callee
,用于返回 arguments
所在函数的引用。arguments.callee()
可以调用自身函数执行,也被称为递归,因此 arguments.callee()
是递归调用。
// 在 strict 模式下 callee 不可用 var num = 1; function func() { console.log(num); num++; if(num <= 5) { // 当 num<=5 时,函数递归调用本身 arguments.callee(); // 表示调用函数本身,效果与 func() 相同 } }
要把中间的参数 b
变为“可选”参数,就只能通过 arguments
判断,然后重新调整参数并赋值。
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); }
为了获取除了已定义参数 a、b 之外的参数,我们不得不用 arguments
,并且循环要从索引2开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的 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
,所以,不再需要 arguments
我们就获取了全部参数。如果传入的参数连正常定义的参数都没填满,也不要紧,rest
参数会接收一个空数组(注意不是 undefined
)。
在 JavaScript 中,变量只有函数作用域,也即函数内声明的变量只有函数内部能用。
var
声明变量,都是全局变量,在整个 JavaScript 文件可用;var
声明的变量为局部变量,只在当前函数可用;var
声明的变量依然为全局变量,在整个 JavaScript 文件可用;JavaScript 默认有一个全局对象 window
,全局作用域的变量实际上被绑定到 window
的一个属性:
'use strict'; var course = 'Learn JavaScript'; alert(course); // 'Learn JavaScript' alert(window.course); // 'Learn JavaScript' // 因此,直接访问全局变量 course 和访问 window.course 是完全一样的。
全局变量会绑定到 window
上,不同的 JavaScript 文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
// 唯一的全局变量MYAPP: var MYAPP = {}; // 其他变量: MYAPP.name = 'myapp'; MYAPP.version = 1.0; // 其他函数: MYAPP.foo = function () { return 'foo'; }; // 把自己的代码全部放入唯一的名字空间 MYAPP 中,会大大减少全局变量冲突的可能。
由于 JavaScript 的变量作用域实际上是函数内部,我们在 for
循环等语句块中是无法定义具有局部作用域的变量的:
'use strict'; function foo() { for (var i=0; i<100; i++) { } i += 100; // 仍然可以引用变量i }
为了解决块级作用域,ES6 引入了新的关键字 let
,用 let
替代 var
可以申明一个块级作用域的变量:
'use strict'; function foo() { var sum = 0; for (let i=0; i<100; i++) { sum += i; } i += 1;// SyntaxError: }
ES6 标准引入了新的关键字 const
来定义常量,const
与 let
都具有块级作用域:
'use strict'; const PI = 3.14; PI = 3; // 某些浏览器不报错,但是无效果! PI; // 3.14
解构赋值
从 ES6 开始,JavaScript 引入了解构赋值,可以同时对一组变量进行赋值。
// 1.传统做法:把一个数组的元素分别赋值给几个变量 var array = ['hello', 'JavaScript', 'ES6']; var x = array[0]; var y = array[1]; var z = array[2]; // 2.在 ES6 中,可以使用解构赋值,直接对多个变量同时赋值 'use strict'; var [x, y, z] = ['hello', 'JavaScript', 'ES6']; // x, y, z分别被赋值为数组对应元素: console.log('x = ' + x + ', y = ' + y + ', z = ' + z);
对数组元素进行解构赋值时,多个变量要用 [...]
括起来。
如果数组本身还有嵌套,也可以通过下面的形式进行解构赋值,注意嵌套层次和位置要保持一致:
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']]; x; // 'hello' y; // 'JavaScript' z; // 'ES6'
解构赋值还可以忽略某些元素:
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素 z; // 'ES6'
如果需要从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性:
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school' }; var {name, age, passport} = person; // name, age, passport分别被赋值为对应属性:
对一个对象进行解构赋值时,同样可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的:
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, address: {city, zip}} = person; name; // '小明' city; // 'Beijing' zip; // undefined, 因为属性名是zipcode而不是zip // 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性: address; // Uncaught ReferenceError: address is not defined
使用解构赋值对对象属性进行赋值时,如果对应的属性不存在,变量将被赋值为 undefined
,这和引用一个不存在的属性获得 undefined
是一致的。如果要使用的变量名和属性名不一致,可以用下面的语法获取:
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school' }; // 把passport属性赋值给变量id: let {name, passport:id} = person; name; // '小明' id; // 'G-12345678' // 注意: passport不是变量,而是为了让变量id获得passport属性: passport; // Uncaught ReferenceError: passport is not defined
解构赋值还可以使用默认值,这样就避免了不存在的属性返回 undefined
的问题:
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678' }; // 如果person对象没有single属性,默认赋值为true: var {name, single=true} = person; name; // '小明' single; // true
有些时候,如果变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误:
// 声明变量: var x, y; // 解构赋值: {x, y} = { name: '小明', x: 100, y: 200}; // 语法错误: Uncaught SyntaxError: Unexpected token = // 这是因为 JavaScript 引擎把 { 开头的语句当作了块处理, // 于是 = 不再合法。解决方法是用小括号括起来: ({x, y} = { name: '小明', x: 100, y: 200});
解构赋值在很多时候可以大大简化代码。例如,交换两个变量 x
和 y
的值,可以这么写,不再需要临时变量:
var x=1, y=2; [x, y] = [y, x]; // 快速获取当前页面的域名和路径: var {hostname:domain, pathname:path} = location;
如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中。例如,下面的函数可以快速创建一个 Date
对象:
function buildDate({year, month, day, hour=0, minute=0, second=0}) { return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second); } // 它的方便之处在于传入的对象只需要 year、month 和 day 这三个属性: buildDate({ year: 2017, month: 1, day: 1 }); // Sun Jan 01 2017 00:00:00 GMT+0800 (CST) // 也可以传入 hour、minute 和 second 属性: buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 }); // Sun Jan 01 2017 20:15:00 GMT+0800 (CST)
this
关键字this
关键字指向当前函数调用语句所在的作用域。简记:谁调用函数,this
指向谁。
function func() { // 直接在 window 对象中使用 func() 调用函数,this 指向 window 对象 console.log(this); } func();
在一个对象中绑定函数,称为这个对象的方法。
比如我们有一个函数 \(f(x) = x^2\) ,要把这个函数作用在一个数组[1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用 map
实现如下:
'use strict'; 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] document.write(results);
ES6 标准新增了一种新的函数:Arrow Function(箭头函数)。为什么叫Arrow Function?因为它的定义用的就是一个箭头:
x => x * x // 上面的箭头函数相当于: function (x) { return x * x; }
箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连 { ... }
和 return
都省略掉了。还有一种可以包含多条语句,这时候就不能省略 { ... }
和 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; }
如果要返回一个对象,就要注意,如果是单表达式,这么写的话会报错:
x => { foo: x } // SyntaxError: // 因为和函数体的{ ... }有语法冲突,所以要改为: x => ({ foo: x }) // ok:
new Number()
、new Boolean()
、new String()
创建包装对象;parseInt()
或 parseFloat()
来转换任意类型到 number
;String()
来转换任意类型到 string
,或者直接调用某个对象的 toString()
方法;boolean
再判断,因为可以直接写 if (myVar) {...}
;typeof
操作符可以判断出 number
、boolean
、string
、function
和 undefined
;Array
要使用 Array.isArray(arr)
;null
请使用 myVar === null
;typeof window.myVar === 'undefined
';typeof myVar === 'undefined'
。