9,函数
9.1,函数的定义
方式一:
function abs(x) {
if(x>0){
return x;
}else{
return -x;
}
}
方式二:
var abs = function (x) {
if(x>0){
return x;
}else{
return -x;
}
}
JavaScript的函数也是一个对象,上述定义的abs()
函数实际上是一个函数对象,而函数名abs
可以视为指向该函数的变量。
方式二相当于function (x) { ... }
是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量abs
,所以,通过变量abs
就可以调用该函数。
9.2,函数的参数
由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数:
abs(10, '1'); // 返回10
abs(-9, '1', '2', null); // 返回9
传入的参数比定义的少也没有问题:
abs();//NaN
//此时abs(x)函数的参数x将收到undefined,计算结果为NaN。
arguments
针对参数传递的随意性,arguments在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数(包括有效的参数)
function abs(x) {
if(arguments.length===2){
console.log("没想到我是第二个参数吧"+arguments[1]);
}
if(x>0){
return x;
}else{
return -x;
}
}
abs(1,null);//输出:没想到我是第二个参数吧null
rest
ES6标准引入了rest参数,可以获取多余的参数
rest参数只能写在最后,前面用...
标识,从运行结果可知,传入的参数先绑定a
、b
,多余的参数以数组形式交给变量rest
,所以,不再需要arguments
我们就获取了全部参数。
如果传入的参数连正常定义的参数都没填满,也不要紧,rest参数会接收一个空数组(注意不是undefined
)。
//使用严格检查模式
'use strict'
function abs(x,...rest) {
console.log("没想到我是第二个参数吧"+rest[0]);
console.log("没想到我是第三个参数吧"+rest[1]);
if(x>0){
return x;
}else{
return -x;
}
}
abs(1,null,true);//没想到我是第二个参数吧null 没想到我是第三个参数吧true
return
有坑 不要分行写 JavaScript引擎有一个在行末自动添加分号的机制
9.3,变量作用域与解构赋值
var 的作用域
函数体内独立性
如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量:
function f() {
var x =1;
console.log(x);
}
console.log(x); //ReferenceError: x is not defined
如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。换句话说,不同函数内部的同名变量互相独立,互不影响:
function f1() {
var x =1;
console.log(x); //1
}
function f2() {
var x=2;
console.log(x); //2
}
函数嵌套,由内而外
由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行:
function foo() {
var x = 1;
function bar() {
var y = x + 1; // bar可以访问foo的变量x!
}
var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
}
加入内层函数与外层函数发生了重名,则会遵循由内而外的机制,内层函数变量,会屏蔽外层的变量
9.4,变量提升
约定规范:在函数内部首先申明所有变量
所有的变量命名都会被自动提取到函数头部
9.5,全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window
,全局作用域的变量实际上被绑定到window
的一个属性:
var x =1;
console.log(window.x);//1
其实在9.1中方式二定义的函数,就是把abs变量绑定到了window的属性上了。
9.6,名字空间
因为全局定义的变量,会被自动绑定到window对象上,所以很容易存在命名冲突,因此自定义全局命名空间,就能够避免名字冲突的问题
//定义自己的命名空间
var mynamespace ={
};
//把所有的全局属性绑定到自己定义的命名空间中,调用时带上命名空间
mynamespace.ele1=1;
mynamespace.ele2=2;
console.log(mynamespace.ele1);//1
9.7,局部作用域
为了解决块级作用域,ES6引入了新的关键字let
,用let
替代var
可以申明一个块级作用域的变量:
总结:JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称ES6)中新增了块级作用域。
块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。
function f3() {
var x=1;
//函数体作用域
for(let x=1;x<5;x++){
//块级作用域
console.log(x);
}
console.log(x);
}
f3(); //1,2,3,4,1
{
//块级作用域
let x=1;
console.log(x);//1
}
console.log(x);//ReferenceError: x is not defined
9.8,常量
由于var
和let
申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:(约定俗成)!!!
ES6则引入新的关键字:const
//定义常量
const PI =3.14;
PI=5;//不能再次赋值
9.9 ,顶层对象
在浏览器环境指的是window对象,在 Node 指的是global对象。
ES5 中,顶层对象的属性与全局变量是等价的;ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
ES6中:
var、function声明的全局变量,依旧是顶层对象的属性;
let、const、class声明的全局变量,不属于顶层对象的属性。
9.10,解构赋值
从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。
可以使用解构赋值,直接对多个变量同时赋值:
//解构赋值-数组 变量取值由位置决定
var [x,y,z]=[1,2,true,false];//多余的值会被忽略
console.log('x:'+x+' y:'+ y +' z:'+z);//x:1 y:2 z:true
var [x1,y1,z1]=[,,true,false];//可以省略某些值的赋值,以逗号为分割
console.log('x1:'+x1+' y1:'+ y1+' z1:'+z1);//x:undefined y:undefined z:true
//注意,对数组元素进行解构赋值时,多个变量要用[...]括起来。
// 如果数组本身还有嵌套,也可以通过下面的形式进行解构赋值,注意嵌套层次和位置要保持一致:
let [x2, [y2, z2]] = ['hello', ['JavaScript', 'ES6']];
x2; // 'hello'
y2; // 'JavaScript'
z2; // 'ES6'
//变量要与属性同名
//如果需要从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性:
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
var {name, age, passport} = person;
console.log(name);
console.log(age);
console.log(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;解构失败,变量的值等于undefined
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678'
};
// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
// 声明变量:
// var x4, y4;
// 解构赋值:
var {x4, y4} = { name: '小明', x: 100, y: 200};
// 语法错误: Uncaught SyntaxError: Unexpected token
// //js引擎会将{}理解为代码块
//字符串
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length : len} = 'hello';
len // 5 类似数组的对象都有一个length属性
//数值和布尔值
// 解构赋值时,如果等号右边是数值和布尔值,则会先转为对象;
// undefined、null无法转为对象,会报错。
var {toString:num}=123;
console.log(num===Number.prototype.toString);//true
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
9.10.1,解构赋值的应用
交换两个变量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);
}
buildDate({ year: 2017, month: 1, day: 1 });
// Sun Jan 01 2017 00:00:00 GMT+0800 (CST)
使用解构赋值可以减少代码量,但是,需要在支持ES6解构赋值特性的现代浏览器中才能正常运行。目前支持解构赋值的浏览器包括Chrome,Firefox,Edge等。