07-函数

个人整理的javaScript学习笔记!
主要参考廖雪峰老师的官方网站:https://www.liaoxuefeng.com/
笔记中引用的图片或者其他内容都不是原创。

函数定义和调用

函数的定义方式

//第一种方式
	function abs(x) {
		if( x >= 10 ){
			return x;
		} else{
			return -x;
		}
	}
	//第二种方式
	var abs = function(x){
		if( x >= 10 ){
			return x;
		} else{
			return -x;
		}
	};

请注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined。

调用函数
由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多(少)也没有问题,虽然函数内部并不需要这些参数:

abs(10, 'blablabla');					 // 返回10
abs(-9, 'haha', 'hehe', null); 	 // 返回9
abs(); 										 // 返回NaN

arguments
JavaScript还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array:

function foo(x){
	for(var i=0; i<arguments.length; i++){
		console.log( 'arg' + i + '=' + arguments[i] );
	}
}
foo(10,20,30);

利用arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值:

function abs(){
	if(arguments.length === 0){
		return 0;
	}
	var x = arguments[0];
	return x >= 0 ? x : -x;
}
abs();
abs(-10);
abs(1);

实际上arguments最常用于判断传入参数的个数。要把中间的参数b变为“可选”参数,就只能通过arguments判断,然后重新调整参数并赋值。

function foo(a,b,c){
	if(arguments.length ===2){
		c = b;
		b = null;
	}
}

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);
}
foo(10, 20, 30, undefined, null);

为了获取除了已定义参数a、b之外的参数,我们不得不用arguments,并且循环要从索引2开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的rest参数,有没有更好的方法?
ES6标准引入了rest参数,上面的函数可以改写为:

function foo(a,b,...rest){
	console.log('a = ' + a);
	console.log('b = ' + b);
	console.log(rest);
}
foo(10, 20, 30, undefined, null);

如果传入的参数连正常定义的参数都没填满,也不要紧,rest参数会接收一个空数组(注意不是undefined)。

实例练习
请用rest参数编写一个sum()函数,接收任意个参数并返回它们的和:

function sum(...rest) {
	var results = 0;
	for(var i=0; i<rest.length; i++){
		if(typeof rest[i] != 'number'){
			console.log('请传入数字!');
			return;
		} else{
			results += rest[i];
		}
	}
	console.log(results)
}

sum();
sum(1);
sum(2,3);

var i, args = [];
for (i=1; i<=100; i++) {
    args.push(i);
}
sum.apply(null, args);

定义一个计算圆面积的函数area_of_circle(),它有两个参数:r: 表示圆的半径;pi: 表示π的值,如果不传,则默认3.14

function area_of_circle(r, pi) {
		for(var i=0; i<arguments.legnth; i++){
			if( typeof arguments[i] != 'number'){
				return;
			}
		}
		pi = arguments[1] == undefined ? 3.14 : arguments[1] ; 
		return r * r * pi;
	}

变量作用域与解构赋值

在JavaScript中,用var申明的变量实际上是有作用域的。
如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量:

function foo() {
    var x = 1;
    x = x + 1;
}
x = x + 2; // ReferenceError! 无法在函数体外引用变量x

如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。换句话说,不同函数内部的同名变量互相独立,互不影响:

function foo() {
    var x = 1;
    x = x + 1;
}
function bar() {
    var x = 'A';
    x = x + 'B';
}

由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行:

function foo() {
    var x = 1;
    function bar() {
        var y = x + 1; // bar可以访问foo的变量x!
    }
    var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
}
	function foo(){
		var x = 1;
		function bar() {
			var x = 'A';
			console.log('x in bar() = ' + x)
		}
		console.log('x in foo() = ' + x);
		bar();
	}
	foo();

变量提升
JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:

function foo() {
		var x = 'Hello, ' + y;
		console.log(x);
		var y = 'Bob';
	}
	foo();

由于JavaScript的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。

全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'

JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误。

名字空间
全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:

var myObj = {};
myObj.name = 'myobject';
myObj.fn = function(){
	return 'myOjb.fn';
}

局部作用域
由于JavaScript的变量作用域实际上是函数内部,我们在for循环等语句块中是无法定义具有局部作用域的变量的:

	function foo(){
		for(var i=0; i=100; i++) {
			//
		}
		i +=100;
		console.log( i );
	}
	foo(); // 200;

为了解决块级作用域,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量:

function foo(){
		for(let i=0; i<100; i++) {
			//
		}
		i += 100;
		console.log( i );
	}
	foo(); // i is not defined at foo!!!

常量
由于var和let申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:

var PI = 3.14;

ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域:

const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; // 3.14

解构赋值
从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。
什么是解构赋值?我们先看看传统的做法,如何把一个数组的元素分别赋值给几个变量:

var array = ['hello', 'JavaScript', 'ES6'];
var x = array[0];
var y = array[1];
var z = array[2];

在ES6中,可以使用解构赋值,直接对多个变量同时赋值:

var [x, y, z] = ['hello', 'JavaScript', 'ES6']; 		   		// 如果浏览器支持解构赋值就不会报错
console.log('x = ' + x + ', y = ' + y + ', z = ' + z); 		// x, y, 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: 'xiaoming',
	age: 20,
	gender: 'male',
	passport: 'G-12345678',
   	school: 'No.4 middle school'
};
var {name, age, gender} = person;
console.log('name = ' + name + ', age = ' + age + ', gender = ' + gender);
//name = xiaoming, age = 20, gender = male

对一个对象进行解构赋值时,同样可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的:

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

解构赋值使用场景
1-解构赋值在很多时候可以大大简化代码;
2-快速获取当前页面的域名和路径;
3-如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中;

方法

在一个对象中绑定函数,称为这个对象的方法。
在JavaScript中,对象的定义是这样的:

var xiaoming = {
    name: '小明',
    birth: 1990
};

如果我们给xiaoming绑定一个函数,就可以做更多的事情。比如,写个age()方法,返回xiaoming的年龄:

var xiaoming = {
	name: '小明',
	birth: 1990,
	age: function(){
		var y = new Date().getFullYear();
		console.log( y - this.birth );
	}
}
xiaoming.age();

在一个方法内部,this是一个特殊变量,它始终指向当前对象,也就是xiaoming这个变量。所以,this.birth可以拿到xiaoming的birth属性。如果以对象的方法形式调用,比如xiaoming.age(),该函数的this指向被调用的对象,也就是xiaoming,这是符合我们预期的。如果单独调用函数,比如getAge(),此时,该函数的this指向全局对象,也就是window。

apply
要指定函数的this指向哪个对象,可以用函数本身的apply方法,它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数。用apply修复getAge()调用:

var xiaohua = {
    name: '小画',
    birth: 1990,
    age: function () {
        var y = new Date().getFullYear();
        console.log( this.birth );
    }
};

var fn = xiaohua.age;
fn();								//undefined
fn.apply(xiaohua, []);    //1990

另一个与apply()类似的方法是call(),唯一区别是:
apply()把参数打包成Array再传入;
call()把参数按顺序传入。
比如调用Math.max(3, 5, 4),分别用apply()和call()实现如下:
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值