未在严格模式下运行,下一篇在strict模式下
函数
函数时由时间驱动的或者当它被调用时刻重复执行的代码块。
//格式
function 函数名(参数列表) {
函数体; // 每个字句之间用;号隔开,可以避免不必要的麻烦..
return 返回值;
}
//示例
function add(x,y) {
return x+y;
}
console.log(add(2,3)) // 5
函数表达式
使用表达式来定义函数,表达式中的函数名可以省略,如果这个函数名不省略,也只能用在函数内部。
// 匿名函数
const add = function (x,y) {
return x+y;
}
console.log(add(5,4)); // 9
//有名字的函数表达式
const sub = function fn(x,y) {
return x-y;
}
console.log(sub(5,4));
// console.log(fn(5,4)) // fn只能在内部使用,sub既可以在内部又可以在外部使用
const sum = function (n) {
if (n === 1) {
return 1;
}
return n + sum(n-1);
}
console.log(sum(3)) // 这种定义的变量也可以在内部使用
函数、匿名函数、函数表达式的差异
函数和匿名函数。本质上一致,都是函数对象,只不过函数有自己的标识符-函数名,匿名函数需要记住其他的标识符而已。
区别在于:普通函数会声明提升,函数表达式不会。
示例,完成一个map函数,可以对一个数组的元素进行某种处理
const map = function (iterable, fn) {
newarr = [];
for (x of iterable) {
newarr.push(fn(x)); // push类似python的append
}
return newarr;
}
console.log(map([1,2,3,4], function (x) {return x+1}))
箭头函数
箭头函数相当于匿名函数,它是一种更加精简的格式。
将上例中的匿名函数更改为箭头函数
// 以下三种等价
console.log(map([1,2,3,4], (x) => {return x+1}));
console.log(map([1,2,3,4,], x => {return x+1}));
console.log(map([1,2,3,4], x => x+1));
箭头函数参数
- 如果一个函数没有参数,使用()
- 如果只有一个参数,参数列表可以省略()
- 多个参数不能省略(), 且使用逗号隔开
箭头函数返回值
- 如果函数体部分有多行,就需要使用{},如果有返回值一定使用return
- 如果函数体部分只有一行,可以同时省略大括号和return
- 只要有return语句,最好就不能省略{}
- 如果只有一条非return语句,加上大括号,函数就变成无返回值了,相当于return undefined
函数参数
普通参数
一个参数占一个位置,支持默认参数
const add = (x=100,y) => x+y
console.log(add()); // NaN,因为相当于add(100, undefined)
console.log(add(1)); // NaN, 相当于add(1, undefined)
console.log(add(1,2)); // 3, 相当于add(1, 2)
注意:
- JavaScript中没有关键字传参
- JavaScript只做参数位置的对应
- JavaScript并不限制默认参数的位置
上面示例是一个不好的示范,建议默认参数写在后面.
可变参数
JavaScript中用…表示可变参数
const a = function (...args) {
console.log(args);
for (x in args) {
console.log(x, args[x]);
}
}
arguments对象
函数的所有参数会保存在一个arguments的键值对对象中。
const a = function (a,...args) {
console.log(a);
console.log(args);
console.log(arguments);
for (x in args) {
console.log(x, args[x])
}
}
ES6之前,arguments是唯一可变参数的实现。
ES6开始,不推荐,建议使用可变参数。为了兼容而保留。
注意:使用箭头函数,取到的arguments不是我们想要的,如下
((x,...args) => {
console.log(args); // 数组
console.log(x);
console.log(arguments); // 不是传入的值
})(...[1,2,3,4])
参数解构
和python类似,js提供了参数解构,依然使用了…符号来解构
const add = (x,y) => {console.log(x,y); return x+y};
console.log(add(...[100,200])); // 100, 200, 300
console.log(add(...[100,200,1])); // 100 200 300
console.log(add(...[100])); // 100 undefined NaN
JS支持参数解构,不需要解构后的值个数和参数个数对应。
函数返回值
js中的返回值区别于python
const add = (x,y) => {return x,y};
console.log(add(1,2)); // 2
这里涉及到一个概念,表达式的值
类C的语言,都有一个概念–表达式的值
- 赋值表达式的值:等号右边的值。
- 逗号表达式的值:类C语言,都支持逗号表达式,逗号表达式的值就是最后一个表达式的值。
下面几个栗子:
a = (x=5, y=6,true);
console.log(a); // true
b = (123, true, z="test");
console.log(b); //test
function c() {
return x=5, y=6, true, "ok";
}
console.log(c()); //ok
所以,JavaScript的函数返回值依然是单值。
作用域
在JavaScript中,用var申明的变量实际上是有作用域的。
如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函体内起作用。换句话说,不同函数内部的同名变量互相独立,互补独立。
function foo() {
var x = 1;
x = x+1;
}
function bar() {
var x = "A";
x = x+"B";
}
由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行。
变量提升
变量提升意味着变量和函数的声明会在编译阶段放在内存中。注意是只会提升声明,而不提升初始化。
function foo() {
var x = "hello," + y; //仅仅提升了声明,而未提升了初始化
console.log(x);
var y = 100;
}
foo() // hello,undefined
由于javascript这一怪异的特性,我们在函数内部定义变量时,请严格遵守"在函数内部首先申明所有变量"这一规则。最常见的做法就是用一个var申明函数内部用到的所有变量。
function foo() {
var
x = 1, // x初始化为1
y = x + 1, // y初始化为2
z, i; // z和i为undefined
// 其他语句:
for (i=0; i<100; i++) {
...
}
}
全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,javascript默认有一个全局对象window,全局作用域的变量实际上被绑定到到window的一个属性。
var x = 10;
alert(x); //alert为警告函数
alert(window.x);
javascript实际上只有一个全局作用域,任何变量(函数也视为变量)如果没有在当前作用域中找到,就会继续向外找,如果在全部作用域中也没找到,则报ReferenceError错误。
命名空间
全局变量会绑定在window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法就是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
var MY = {};
MY.name = "ben";
MY.age = 19;
MY.foo = function () {
return func;
}
把自己的代码全部放入唯一的命名空间MY中,会大大减少全局变量冲突的可能。
许多著名的javascript库就是这么干的,例如jQuery、YUI、underacore等等。
局部作用域
let可以声明一个块级作用域的变量:
function foo() {
var sum = 0;
for (let i=0;i<100;i++) {
sum += i;
}
i += 1; // ReferenceError
}
foo()
作用域中声明的变量,向内可见,向外不可见。
注意
解构赋值
解构方法
从ES6开始,JavaScript引入了结构赋值,可以同时对一组变量进行赋值。
a = [1,2,3];
let [,c,d] = a; // 解构赋值,可以忽略某些元素
console.log(c,d); // 2, 3
let [x,y,z,m] = [1,2,3] // 对于数组元素
console.log(x,y,z,m) //1 2 3 undefined
支持默认值和可变变量,注意可变变量解包的元素必须放在最后
let [a,...args] = [1,2,3,4]
console.log(a, args) // 1 [ 2, 3, 4 ]
let [b,...c,d] = [1,2,3,4] // 报错
let [m=10,...n] = [1,2,3]
console.log(m,n) // 1 [2,3]
如果数组本身还有嵌套,也可以通过下面的形式进行结构赋值,注意嵌套层次和位置要保持一致。
var [a,[b,c]] = [1,[2,3]];
console.log(a,b,c) //1, 2, 3;
如果需要从一个对象中取出若干属性,也可以结构赋值,便于快速获取对象的指定属性。只需要指定想要属性, 不需要属性值一一对应。
'use strict';
var a = {name:"ben",age:20};
var {name} = a; //只需要指定想要属性, 不需要属性值一一对应
console.log(name);
对一个对象进行解构赋值时,同样可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的
var a = {name:'ben',age:12,address:{city:1,street:2,zipcode:3}};
var {name,address:{city}} = a; // 层次关系要一致
console.log(name, city);
使用解构赋值,如果对应的属性不存在,变量将被赋值为undefined,这和引用一个不存在的属性获得undefined是一致的。
var a = {name:"ben",age:20};
var {name,address} = a;
console.log(name,address); // ben undefined
解析赋值还可以使用默认值,这样就避免了不存在的属性返回undefined的问题。
var a = {name:"ben",age:20};
var {name,address={city:1}} = a;
console.log(name,address); // ben {city:1}
注意:
有些时候,如果变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误:
var x,y;
[x,y] = [1,2];
console.log(x,y)
var a,b;
{a,b} = {a:1,b:1}; // 语法错误
这是因为JavaScript引擎把{开头的语句当做可块处理,于是=不再合法。解决办法是用()括起来
var a,b
({a,b} = {a:1,b:1})
console.log(a,b)
使用场景
解构赋值可以大大简化代码。例如交换两个变量x和y的值
var x=1,y=2; //分号一定要写,不仅仅是个好习惯!!!
[x,y] = [y,x]; //
console.log(x,y)
快速获取当前页面的域名和路径:
var {hostname,pathname} = location;
使用解构赋值可以减少代码量,但是需要支持ES6解构赋值特性的现代浏览器中才能正常运行。目前支持解构赋值的浏览器包括Chrome、FireFox、Edge等。
异常处理
抛出异常
JS的异常语法和Java相同,使用throw关键字抛出。类似python的raise关键字
使用throw关键字可以抛出任意对象的异常(允许自定义的错误消息)
throw 1
throw new Error("new error")
throw "123"
throw [1,2,3]
throw {a:1}
throw () => {}
异常的捕获
语句 | 作用 |
---|---|
try语句 | 测试代码块的错误 |
catch语句 | 处理错误 |
throw语句 | 创建自定义错误 |
finally语句 | 无论是否触发异常,最后都会执行 |
try {
... //异常的抛出
} catch(error) {
... //异常的捕获与处理
} finally {
... //结束处理
}
示例
try{
// throw 1
// throw new Error("new error")
// throw "123"
// throw [1,2,3]
// throw {a:1}
throw () => {};
}
catch (error) {
console.log(error); //打印错误
console.log(typeof(error)); //打印错误类型
console.log(error.constructor.name); //打印错误制造者的名字
}
finally {
console.log("====end====")
}