Es6中函数的扩展(二)

  • rest参数

    ES6引入rest参数(形式为…变量名),用于获取函数的多余参数,这样就可以省略arguments对象了,rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中

	function add (...values) {
		let sum - 0;
	
		for (let val of values) {
			sum += val	
		}	
		
		return sum
	}
	add(1, 3, 5, 7)
	// 16

上面代码,add可以传入任意个数参数进行求和

rest参数和arguments对象的对比

	// arguments 
	function sortNumbers () {
		return Array.prototype.slice.call(arguments).sort();
	}

	// rest
	function sortNumbers (...numbers) {
		numbers.sort()
	}

上面代码的两种写法,比较后可以发现,rest参数的写法更自然也更简洁

arguments对象不是数组,只是一个类数组,所以使用数组方法必须先用Array.prototype.slice.call先将其转为数组。

而rest对象就不需要了,它是一个真正的数组

利用rest改写push方法

	let a = [];
	function push (array, ...items) {
		items.forEach((item) => {
			array.push(item);
		})
		return array;
	}
	push(a, 1, 3, 5, 7)

注意, rest参数之后不能再有其他参数(rest必须是最后一个参数)

	function foo (a, ...b, c) {

	}

函数的length属性不包含rest

	(function (a) {}).length // 1
	(function (...rest) {}).length // 0
	(function (a, ...rest) {}).length // 1
	(function (a, ...rest, b) {}).length // error 
  • 严格模式

    从ES5开始,允许函数内部使用严格模式

	function test () {
		'use strict'
		// 整个函数体内部运用严格模式的规定
	}

ES2016规定,如果函数使用默认值,扩展运算符,解构赋值内部就不可以再显示的运用严格模式,否则报错。

	// 报错
	function doSomething(a, b = a) {
	  'use strict';
	  // code
	}
	
	// 报错
	const doSomething = function ({a, b}) {
	  'use strict';
	  // code
	};
	
	// 报错
	const doSomething = (...a) => {
	  'use strict';
	  // code
	};
	
	const obj = {
	  // 报错
	  doSomething({a, b}) {
	    'use strict';
	    // code
	  }
	};

这样的规定是因为,严格模式下,都要使用于函数参数和函数体。

但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样有一个不合理的地方。

只有从函数体之中,才能知道参数是否应该被严格模式下执行,但是参数优先于函数体执行

	function test (value = 070) {
		'use strict'
		return value
	}
	

上面代码,参数value的默认值是八进制的070,但是严格模式下不能用前缀0表示八进制,所以应该报错,但是实际上,js内部会先执行成功value = 070,然后进入函数体,发现严格模式,这时会报错。

虽然可以先解析函数体代码,再执行参数,但这样无疑是增加了复杂性,因此标准引入,函数使用扩展,默认值,解构赋值就不能的使用严格模式

但是有两种方法可以变通

使用全局严格模式

	'use strict'
	
	function test (a, b = a) {
		
	} 

使用无参数立即执行函数

	(function () {
		'use strict'
		return function (value = 42) {
			return value;
		}
	}) ();
	
  • name属性

函数的name属性,返回该函数的函数名

	function foo () {}
	foo.name 
	// foo

ES5中,如果将一个匿名函数赋值给一个变量,name会返回空字符串,
但ES6中,会返回改变量的名

	let foo = function () {}
	// es5
	foo.name 
	// ''

	// es6
	foo.name
	// foo

如果讲一个具名函数赋值给一个变量,则es5和es6的name户型都返回这个具名函数的原本名字

	let f = function foo () {}
	
	// es5
	f.name
	// foo

	// es6
	f.name
	// foo

Function构造函数返回的函数实例,name名为anonymous

	(new Function).name
	// anonymous

bind返回的函数,name属性值会加上bound前缀

	function foo () {}
	foo.bind({}).name
	// bound foo

	(function () {}).bind({}).name 
	// bound

  • 箭头函数

    ES6允许使用 (=>) 定义函数

	let test = () => {}
	// 等同于
	function test () {}
	let test = function () {}

如果参数部分不需要,或者需要多个参数时,使用() 来代替

	let f = () => 3;
	// 等同于
	let f = function () { return 3 }

	let f = (x, y) => x + y;
	// 等同于
	let f = function (x, y) { return x + y }

如果箭头函数的语句大于一条,需要使用{}包起来,并且需要现实的写return

	let f = (x, y) => {
		console.log(x, y);
		return x + y;
	}
	f(5, 21)

由于大括号被解释为代码块,如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则报错

	// error
	let p = () => {car: 'BMW', wife: 'fanfan'}
		
	// correct
	let p = () => ({car: 'BMW', wife: 'fanfan'})

下面是一种特殊情况

	let p = () => {a : 1}
	p()

上面代码,原意是让他返回一个{ a : 1 }对象,但是js会理解成代码块,所以执行的其实是a :1。
这时,a可以被解释为语句的标签,因此实际执行的语句是1;
然后函数没有返回值,就这样结束了!

如果函数不需要返回值,且只有一行语句。可以采用这种写法,不需要写大括号

	let p = () => void console.log('hello => fn');

箭头函数可以与解构赋值一起使用

	let p = ({first, last}) => first + '' + last;
	
	// 等于
	let p = function (person) {
		return person.first + '' + person.last
	}

箭头函数表达起来通俗易懂,代码量大大减少,并且习惯后爱不释手

	let foo = n => n * n
	let bar = n => n * 2

箭头函数只要用来简化会提到函数
过去的回调

	(function () {
		// code
		return function () {
			// code
			return function () {
				// code
				return function () {
					// code
					// ...
				}
			}
		}
	} ())

使用了箭头函数之后

	(function () {
		// code
		return () => {
			//code
			return () => {
				// code
				return () => {
					// ...
				}
			}
		}
	} ())
	// 正常函数
	list.map(function (index) {
		return index * 2
	})

	// 箭头函数
	list.map(index => index * 2)
	let result = arr.sort(function (a, b) {
		return a - b;
	})
	
	let result = arr.sort((a, b) => a - b)

使用rest参数与箭头函数结合

	let numbers = (...rest) => rest;
	numbers(1, 2, 3, 4, 5);
	// [1, 2, 3, 4, 5]

	let numbers1 = (first, ...last) => [first, last];
	number1(1, 2, 3, 4, 5)
	// [1, [2, 3, 4, 5]]

使用注意点

  1. 箭头函数内部没有this,它的this来源于定义时被给予的,只要定义的环境没有this,那么箭头函数的this就是undefined
  2. 箭头函数不能用作构造函数,不能new
  3. 箭头函数不可以使用arguments对象,可以使用rest替代
  4. 箭头函数不可以使用yield,也就是不能使用Generator函数

第一点尤为重要,this是可变的,但是在箭头函数中,定义之后他就是固定的。

下面一个例子阐述箭头函数与普通函数的区别

	
	function foo () {
		setTimeout(() => {
			console.log(this.id)
		})
	}
	var id = 30;
	foo.call({id: 21})
	// 21

上面代码执行结果是21, 因为箭头函数的this依赖于定义时的环境(对象),所以它找的就是定义时的this,当foo被call了之后会改变this指向。

如果函数是正常执行,会打印window.id
也就是30.

注意:这里不能用let 因为声明在后,使用在前。
在一个就是如果foo()直接执行时找不到this.id的
因为let的变量不归你window所有

箭头函数可以让里面的this,在绑定定义时指定自己的作用域,而不是在执行时

	function timer () {
		this.s1 = 0;
		this.s2 = 0;
		// 箭头函数
		setInterval(() => {
			this.s1 ++
		}, 1000)
		// 普通函数
		setInterval(function () {
			this.s2 ++
		})	
	}
	
	let timer = new Timer();
	console.log(setTimeout(() => {timer.s1}, 1000))
	// 3
	console.log(setTimeout(() => {timer.s2}, 1000))
	// 0

上面的timer函数中有两个方法,一个箭头函数一个普通函数,这两个函数在执行的时候箭头函数的this指向了timer,普通函数的this指向了window。

所以在打印s1 的时候是3
打印s2的时候是0

箭头函数使用的是timer中s1
普通函数使用的是window里的s2,虽然window没有这个s2但是不报错。

箭头函数的优点是用作回调时,总是可以绑定固定的this。

	let handler = {
		id: '666666',
		
		foo () {
			document.addEventListener('click', event => {
				this.bar(event.type)
			}, false)
		}

		bar (type) {
			console.log('bound' + type + this.id)
		}
	}

这样写的好处在于,这个箭头函数的this永远指向handler,因为普通函数这是调用this.bar会报错。

this指向document,但是经过了箭头函数的改动,指向就会变为handler

trhis指向的固定化,实际并不是因为箭头函数内部有绑定this的机制,而是箭头函数没有自己的this,它完全依赖于外部this。
所以它不可以使用构造函数

箭头函数转换为es5

	function foo () {
		setTimeout(() => {
			console.log(this.id)
		}, 1000)
	}
	
	function foo () {
		let _this = this;
		
		setTimeout( function () {
			console.log(this.id)
		}, 1000)
	}

由此可以看出箭头函数的this依赖于外部this

	function foo() {
	  return () => {
	    return () => {
	      return () => {
	        console.log('id:', this.id);
	      };
	    };
	  };
	}
	
	var f = foo.call({id: 1});
	
	var t1 = f.call({id: 2})()(); // id: 1
	var t2 = f().call({id: 3})(); // id: 1
	var t3 = f()().call({id: 4}); // id: 1

上面函数的this只有一个,就是foo函数,因为内部全部都是箭头函数,他们都依赖与foo的this

除了this,箭头函数还不可以使用arguments,super,new.target

	function foo () {
		setTimeout(() => {
			console.log(arguments)
		}, 1000)
	}

	foo(1, 3, 5, 7)
	// [1, 3, 5, 7]

上面代码中,箭头函数内部的变量arguments,其实是函数foo的arguments

箭头函数没有this的原因,也无法使用call(), apply(), bind()

	(function () {
		return [
			(() => this.id.bind({id: 32}) ())
		]
	} ()).call({id: 21})
	// 21

由于箭头函数无法绑定bind,所以内部this指向外部的this

this在对象中,使用要非常小心,但是箭头函数改变了这一特性,此时箭头函数的this永远指向外层对象

不适合场景
箭头函数使得this从静态变为动态,下面两个场合不适合使用箭头函数

第一个是定义对象的方法,且方法中使用了this

	let obj = {
		car: 'BMW',
		foo: () => {
			this.car = 'SRL'
		}
	}
	

如果foo是个普通函数,调用obj.foo时this指向obj, 那么car就可以轻易拿到。

但是使用箭头函数,那么他的this依赖于外部,但是对象恰恰没有this这种机制,导致箭头函数的this指向window,最后会导致报错。

第二种是需要动态的this时

	let button = document.querySelector('button');
	button.onclick = () => {
		this.classList.add('button');
	}

本意是想当点击button按钮的时候添加一个类名,但使用了箭头函数之后,他的this不再是调用者的this了,而是全局window,导致最后结果不如意,所以这种情况用使用普通函数

箭头函数内部,还可以再使用箭头函数。下面是一个 ES5 语法的多重嵌套函数。

	function insert(value) {
        return {
            into: function (array) {
                return {
                    after: function (afterValue) {
                        array.splice(array.indexOf(afterValue) + 1, 0, value);
                        return array;
                    }
                };
            }
        };
    }
    insert(2).into([1, 3]).after(1); //[1, 2, 3]

使用箭头函数改写

	let insert = (value) => ({
        into: (array) => ({
            after: (afterValue) => {
                array.splice(array.indexOf(afterValue) + 1, 0, value);
                return array;
            }
        })
    });
    insert(2).into([1, 3]).after(1); //[1, 2, 3]

函数调用,依赖另一个函数的功能,使用箭头函数实现

	let p1 = a => a + 1;
	let p2 = a => a * 2;
	p2(p1(3));
	// 8

箭头函数还有一个功能,就是可以很方便地改写 λ 演算。

	// λ演算的写法
	fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v)))
	
	// ES6的写法
	var fix = f => (x => f(v => x(x)(v)))
	               (x => f(v => x(x)(v)));

函数参数的尾逗号

ES2017允许函数的最后一个参数有尾逗号
此前函数的定义和调用时,都不允许最后一个添加逗号

	function bar (
		car,
		wife
	) 
	{

	}
	bar('BMW''fanfan')

如果你在参数后面添加逗号就会报错,但ES6完善了这一点

如果像上面这样,将参数写成多行(即每个参数占据一行),以后修改代码的时候,想为函数clownsEverywhere添加第三个参数,或者调整参数的次序,就势必要在原来最后一个参数后面添加一个逗号。这对于版本管理系统来说,就会显示添加逗号的那一行也发生了变动。这看上去有点冗余,因此新的语法允许定义和调用时,尾部直接有一个逗号。

	function foo (
		wife,
		car,
	) {}

	foo('fanfan', 'BMW')

这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。

Function.prototype.toString()

ES2019对函数实例的toString()方法做出了修改

toString()方法返回函数代码本身,以前会省略注释,空格

	function /* foo comment */ foo () {}

	foo.toString()
	// function foo () {}

上面代码中,函数foo原始代码包含注释,函数名foo和圆括号 之前有空格,但是原来的toString()把他们省略了

但是es6又把他们重新召唤回来了

	function /* foo comment */ foo () {}

	foo.toString()
	// 'function /* foo comment */ foo () {}'

Catch参数的省略
js语言的try…catch结构,以前必须在catch后紧跟参数,但有时会用不到这个参数。

	try {

	} catch (error) {

	}

ES2019做出修改允许catch语句省略参数

	try {
	
	} catch {

	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值