ES6转ES5, javascript---第二季之函数的扩展

来源:互联网 发布:王者荣耀1元60点卷淘宝 编辑:程序博客网 时间:2024/04/29 18:13

1,函数的扩展

(1)函数参数的默认值

在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
function create(x,y){    y = y || 'andy';    console.log(x,y);}create('Hello');//Hello andycreate('Hello','China');//Hello Chinacreate('Hello','');//Hello andy
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function create(x,y='andy'){    console.log(x,y);}create('Hello');//Hello andycreate('Hello','China');//Hello Chinacreate('Hello','');//Hello
function createNums(x=0,y=0){    this.x = x;    this.y = y;}var nums = new createNums();console.log(nums);//{x:0,y:0}
参数变量是默认声明的,所以不能用letconst再次声明。
function createNums(x=0){    let x = 5;    const x= 8;}
上面代码中,参数变量x是默认声明的,在函数体中,不能用letconst再次声明,否则会报错。
使用参数默认值时,函数不能有同名参数。
let x = 9;function foo(p = x+1){    console.log(p);}foo();//10x = 10;foo();//11//如果参数默认值是变量,那么参数就不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的

参数默认值可以与解构赋值的默认值,结合起来使用。
function createNums({x,y=2}){    console.log(x,y);}createNums({});// undefined, 2createNums({x:1});//1 2createNums({x:1,y:5});//1 5//createNums();//Cannot read property 'x' of undefined//上面代码使用了对象的解构赋值默认值,而没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值而生成。//如果函数foo调用时参数不是对象,变量x和y就不会生成,从而报错。如果参数对象没有y属性,y的默认值2才会生效。

(2)参数默认值的位置

//通常情况下,定义了默认值的参数,应该是函数的尾参数。//因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。function foo(x=1,y){    console.log([x,y]);}foo();//[1, undefined]foo(1);//[1, undefined]foo(3);//[3, undefined]//foo(,2);//报错foo(undefined,2);//[1, 2]//如果传入undefined,将触发该参数等于默认值,null则没有这个效果。foo(null,5);//[null, 5]

(3)函数的length属性

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
console.log((function(a){}).length);//1console.log((function(x=1){}).length);//0console.log((function(x,y=1){}).length);//1//这是因为length属性的含义是,该函数预期传入的参数个数。//某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest参数也不会计入length属性。console.log((function(...args){}).length);//0//如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。console.log((function(x=0,b,c){}).length);//0console.log((function(x,y=1,z,g){}).length);//1


(4)作用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。
等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
var x = 1;function foo(x,y=x){    console.log(x,y);}foo(3);//3 3//上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。//在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是3。let y = 1;function foo1(z=y){    let y = 2;    console.log(z);}foo1();//1//上面代码中,函数f调用时,参数z = y形成一个单独的作用域。//这个作用域里面,变量y本身没有定义,所以指向外层的全局变量y。函数调用时,函数体内部的局部变量y影响不到默认值变量y。//如果此时,全局变量x不存在,就会报错function foo2(y=x){    let x = 2;    console.log(y);}foo2();//下面这种写法也会报错var x  =2;function f(x=x){    //...to do}f();//上面代码中,参数x = x形成一个单独作用域。实际执行的是let x = x,由于暂时性死区的原因,这行代码会报错”x 未定义“。

(5)rest参数

ES6 引入 rest 参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。
rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values){    let sum = 0;    for(var val of values){        sum += val;    }    return sum;}console.log(add(1,2,3));//6//上面代码的add函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。//函数的length属性,不包括 rest 参数。console.log((function(a){}).length);//1console.log((function(a,...b){}).length);//1console.log((function(...b){}).length);//0//console.log((function(a,...b,c){}).length);//注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

(6)扩展运算符

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列
console.log(...[1,2,3]);//1 2 3console.log(1,...[2,3,4],5);//1 2 3 4 5console.log([...document.getElementsByTagName('b')]);//[b, b, b, b]//该运算符主要用于函数调用function push(array,...items){    array.push(...items);}function  add(x,y){    return x+y;}var nums = [1,2];console.log(add(...nums));//3//上面代码中,array.push(...items)和add(...numbers)这两行,都是函数的调用,//它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列
替换数组的apply方法
//由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了//es5function f(x,y,z){    //    console.log();}var args = [1,2,3];f.apply(null,args);//es6function f1(x,y,z){    //...}var arg = [1,2,3];f(...arg);//下面是扩展运算符取代apply方法的一个实际的例子,应用Math.max方法,简化求出一个数组最大元素的写法// es5console.log(Math.max.apply(null,[1,2,3]));//3//es6console.log(Math.max(...[1,2,3,4]));//4//等同于console.log(Math.max(1,2,3,4));//4//另一个例子是通过push函数,将一个数组添加到另一个数组的尾部//es5var arr1 = [1,2,3];var arr2 = [4,5,6];Array.prototype.push.apply(arr1,arr2);console.log(arr1);//[1, 2, 3, 4, 5, 6]//es6var arr3 = [7,8,9];arr2.push(...arr3);console.log(arr2);//[4, 5, 6, 7, 8, 9]//上面代码的ES5写法中,push方法的参数不能是数组,所以只好通过apply方法变通使用push方法。//有了扩展运算符,就可以直接将数组传入push方法

(7)扩展运算符的应用

合并数组
var arr1 = [1,2];var arr2 = [3,4];var arr3 = [5,6];//es5var arr4 = arr1.concat(arr2,arr3);console.log(arr4);//[1, 2, 3, 4, 5, 6]//es6console.log([...arr1,...arr2,...arr3]);//[1, 2, 3, 4, 5, 6]
与解构赋值相结合
扩展运算符可以与解构赋值结合起来,用于生成数组。
const [n1,...rest] = [1,2,3];console.log(n1);//1console.log(rest);//[2,3]const [n2,...values] = [];console.log(n2);//undefinedconsole.log(values);//[]const [n3,...v] = ['foo'];console.log(n3);//fooconsole.log(v);//[]//果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错const [...n4,n5] = [1,2,3,4,5];//报错const [n6,...n7,n8] = [1,2,3];//报错
字符串
扩展运算符还可以将字符串转为真正的数组
console.log([...'andy']);//["a", "n", "d", "y"]//上面的写法,有一个重要的好处,那就是能够正确识别32位的Unicode字符console.log('x\uD83D\uDE80y'.length);//4console.log([...'x\uD83D\uDE80y'].length);//3
实现了Iterator接口的对象
任何Iterator接口的对象,都可以用扩展运算符转为真正的数组
var nodelist = document.getElementsByTagName('b');var array = [...nodelist];console.log(array);//[b, b, b, b]//上面代码中,getElementsByTagName方法返回的是一个nodeList对象。//它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了Iterator接口//对于那些没有部署Iterator接口的类似数组的对象,扩展运算符就无法将其转为真正的数组let arraylike = {    '0':'a',    '1':'b',    length:2}let arr = [...arraylike];//上面代码中,arrayLike是一个类似数组的对象,但是没有部署Iterator接口,扩展运算符就会报错。//这时,可以改为使用Array.from方法将arrayLike转为真正的数组

Map和Set结构,Generator函数
扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构
let map = new Map([    [1,'a'],    [2,'b'],    [3,'c']]);let arr = [...map.keys()];console.log(arr);//[1, 2, 3]

(8)严格模式
从ES5开始,函数内部可以设定为严格模式
function doSomething(a, b) {  'use strict';  // code}
《ECMAScript 2016标准》做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,
那么函数内部就不能显式设定为严格模式,否则会报错这样规定的原因是,函数内部的严格模式,同时适用于函数体代码和函数参数代码。
但是,函数执行的时候,先执行函数参数代码,然后再执行函数体代码。
这样就有一个不合理的地方,只有从函数体代码之中,才能知道参数代码是否应该以严格模式执行,
但是参数代码却应该先于函数体代码执行
// 报错function doSomething(a, b = a) {  'use strict';  // code}

两种方法可以规避这种限制。第一种是设定全局性的严格模式,这是合法的。

'use strict';function doSomething(a, b = a) {    // code}
第二种是把函数包在一个无参数的立即执行函数里面。

const doSomething = (function () {    'use strict';    return function(value = 42) {        return value;    };}());

(9)name属性

function foo(){}console.log(foo.name);//foo//需要注意的是,ES6 对这个属性的行为做出了一些修改。//如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。var f = function(){}//es5console.log(f.name);//''//es6console.log(f.name);//f//如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字const n1 = function bar(){}console.log(n1.name);//bar//Function构造函数返回的函数实例,name属性的值为anonymous。console.log((new Function).name);//anonymous//bind返回的函数,name属性值会加上bound前缀。function a1(){}console.log(a1.bind({}).name);//bound a1

(10)箭头函数

ES6允许使用“箭头”(=>)定义函数
var f = v =>v;//等同于var f = function(v){    return v;};
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
var f1 = ()=>5;//等同于var f1 = function(){    return 5;};var sum = (n1,n2) => n1+n2;//等同于var sum = function(n1,n2){    return n1+n2;}
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回
var sum = (n1,n2) => {return n1+n2};//由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。var getid = id => ({id:id,name:'andy'});console.log(getid(1));//{id: 1, name: "andy"}//箭头函数可以与变量解构结合使用const full = ({n1,n2}) => n1 +' '+ n2;//等同于function full(person){    return person.n1 +' '+person.n2;}

//箭头函数更加简洁const square = n => n*n;console.log(square(5));//25
箭头函数的一个用处是简化回调函数
//正常函数写法var n = [1,2,3,4].map(function(x){    return x*x;});console.log(n);//[1, 4, 9, 16]//箭头函数写法var n1 = [1,2,3].map(x => x*x);console.log(n1);//[1, 4, 9]//下面是rest参数与箭头函数结合的例子const num = (...nums) => nums;console.log(num(1,2,3,4,5));//[1, 2, 3, 4, 5]const arr = (n1,...n) => [n1,n];console.log(arr(1,2,3,4));//[1,[2,3,4]]

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。

上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

//箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域function f(){    setTimeout(() => {        console.log('id:',this.id);    },100)}var id = 2;f.call({id:3});//箭头函数可以让this指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM事件的回调函数封装在一个对象里面var handler = {    id : '1',    init:function(){        document.addEventListener('click',        event => this.doSomething(event.type),false);    },    doSomething:function(type){        console.log('Handing'+type+'for'+this.id);    }}//上面代码的init方法中,使用了箭头函数,这导致这个箭头函数里面的this,总是指向handler对象。//另外,由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向









0 0
原创粉丝点击