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

1,函数的扩展

(1)函数参数的默认值

在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
function create(x,y){
    y = y || 'andy';
    console.log(x,y);
}
create('Hello');//Hello andy
create('Hello','China');//Hello China
create('Hello','');//Hello andy
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function create(x,y='andy'){

    console.log(x,y);
}
create('Hello');//Hello andy
create('Hello','China');//Hello China
create('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}
参数变量是默认声明的,所以不能用 let const 再次声明。
function createNums(x=0){
    let x = 5;
    const x= 8;
}
上面代码中,参数变量 x 是默认声明的,在函数体中,不能用 let const 再次声明,否则会报错。
使用参数默认值时,函数不能有同名参数。
let x = 9;
function foo(p = x+1){
    console.log(p);
}
foo();//10
x = 10;
foo();//11
//如果参数默认值是变量,那么参数就不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的

参数默认值可以与解构赋值的默认值,结合起来使用。
function createNums({x,y=2}){
    console.log(x,y);
}
createNums({});// undefined, 2
createNums({x:1});//1 2
createNums({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);//1
console.log((function(x=1){}).length);//0
console.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);//0
console.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);//1
console.log((function(a,...b){}).length);//1
console.log((function(...b){}).length);//0
//console.log((function(a,...b,c){}).length);//注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

(6)扩展运算符

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列
console.log(...[1,2,3]);//1 2 3
console.log(1,...[2,3,4],5);//1 2 3 4 5
console.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方法,将数组转为函数的参数了

//es5
function f(x,y,z){
    //
    console.log();
}
var args = [1,2,3];
f.apply(null,args);

//es6
function f1(x,y,z){
    //...
}
var arg = [1,2,3];
f(...arg);
//下面是扩展运算符取代apply方法的一个实际的例子,应用Math.max方法,简化求出一个数组最大元素的写法
// es5
console.log(Math.max.apply(null,[1,2,3]));//3
//es6
console.log(Math.max(...[1,2,3,4]));//4
//等同于
console.log(Math.max(1,2,3,4));//4

//另一个例子是通过push函数,将一个数组添加到另一个数组的尾部

//es5
var arr1 = [1,2,3];
var arr2 = [4,5,6];
Array.prototype.push.apply(arr1,arr2);
console.log(arr1);//[1, 2, 3, 4, 5, 6]
//es6
var 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];
//es5
var arr4 = arr1.concat(arr2,arr3);
console.log(arr4);//[1, 2, 3, 4, 5, 6]
//es6
console.log([...arr1,...arr2,...arr3]);//[1, 2, 3, 4, 5, 6]

与解构赋值相结合
扩展运算符可以与解构赋值结合起来,用于生成数组。
const [n1,...rest] = [1,2,3];
console.log(n1);//1
console.log(rest);//[2,3]

const [n2,...values] = [];
console.log(n2);//undefined
console.log(values);//[]

const [n3,...v] = ['foo'];
console.log(n3);//foo
console.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);//4
console.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(){

}
//es5
console.log(f.name);//''
//es6
console.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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值