博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ECMAScript6学习笔记-函数扩展
阅读量:2490 次
发布时间:2019-05-11

本文共 7984 字,大约阅读时间需要 26 分钟。

1.函数参数默认值

function func(a,b="world"){    console.log(a+" "+b);}func("hello");//hello worldfunc("hello","china");//hello chinafunc();//undefined world

2.参数默认值是惰性求职,每次调用都会重新计算表达式的值。

let x = 99;function foo(p = x + 1) {
console.log(p);}foo() // 100x = 100;foo() // 101

3.参数默认值可以与解构赋值的默认值,结合起来使用。

function foo({x, y = 5}) {  console.log(x, y);}foo({}) // undefined 5foo({x: 1}) // 1 5foo({x: 1, y: 2}) // 1 2foo() // TypeError: Cannot read property 'x' of undefined

另一个解构赋值默认值的例子。

function fetch(url, { body = '', method = 'GET', headers = {} }) {
console.log(method);}fetch('http://example.com', {})// "GET"fetch('http://example.com')// 报错

上面代码中,如果函数fetch的第二个参数是一个对象,就可以为它的三个属性设置默认值。这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。

function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
console.log(method);}fetch('http://example.com')// "GET"

练习

// 写法一function m1({x = 0, y = 0} = {}) {  return [x, y];}// 写法二function m2({x, y} = { x: 0, y: 0 }) {  return [x, y];}// 函数没有参数的情况m1() // [0, 0]m2() // [0, 0]// x 和 y 都有值的情况m1({x: 3, y: 8}) // [3, 8]m2({x: 3, y: 8}) // [3, 8]// x 有值,y 无值的情况m1({x: 3}) // [3, 0]m2({x: 3}) // [3, undefined]// x 和 y 都无值的情况m1({}) // [0, 0];m2({}) // [undefined, undefined]m1({z: 3}) // [0, 0]m2({z: 3}) // [undefined, undefined]

4.参数默认值的位置

通常情况下,设置默认值的参数应该是函数的尾参数,这样容易看出来省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是不可以省略的。

// 例一function f(x = 1, y) {  return [x, y];}f() // [1, undefined]f(2) // [2, undefined])f(, 1) // 报错f(undefined, 1) // [1, 1]// 例二function f(x, y = 5, z) {  return [x, y, z];}f() // [undefined, 5, undefined]f(1) // [1, 5, undefined]f(1, ,2) // 报错f(1, undefined, 2) // [1, 5, 2]

5.函数的length属性

函数参数设置默认值后,函数的length属性将返回没有设置默认值的函数的参数。

(function (a) {
}).length // 1(function (a = 5) {
}).length // 0(function (a, b, c = 5) {
}).length // 2

函数参数默认值不是尾参数,length属性也不会计入后面的参数。

(function (a = 0, b, c) {
}).length // 0(function (a, b = 1, c) {
}).length // 1

6.作用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。

var x = 1;function f(x, y = x) {
console.log(y);}f(2) // 2//调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。let x = 1;function f(y = x) {
let x = 2; console.log(y);}f() // 1函数f调用时,参数y = x形成一个单独的作用域。这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x。函数调用时,函数体内部的局部变量x影响不到默认值变量x。如果x在函数之前没有定义,就会报错。

如果参数的默认值是一个函数,该函数的作用域也遵守这个规则。

let foo = 'outer';function bar(func = () => foo) {  let foo = 'inner';  console.log(func());}bar(); // outer//函数bar的参数func的默认值是一个匿名函数,返回值为变量foo。函数参数形成的单独作用域里面,并没有定义变量foo,所以foo指向外层的全局变量foo,因此输出outer。

7.rest参数

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

function add(...values) {
let sum = 0; for (var val of values) { sum += val; } return sum;}add(2, 5, 3) // 10

rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

// 报错function f(a, ...b, c) {
// ...}

函数的length属性,不包括 rest 参数

(function(a) {
}).length // 1(function(...a) {
}).length // 0(function(a, ...b) {
}).length // 1

8.ES2016 规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

// 报错function doSomething(a, b = a) {
'use strict'; // code}// 报错const doSomething = function ({a, b}) {
'use strict'; // code};// 报错const doSomething = (...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属性

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

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

如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。

var f = function () {
};// ES5f.name // ""// ES6f.name // "f"

如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。

const bar = function baz() {
};// ES5bar.name // "baz"// ES6bar.name // "baz"

Function构造函数返回的函数实例,name属性的值为anonymous。

(new Function).name // "anonymous"

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

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

10.箭头函数

ES6允许使用箭头“=>”定义函数

var f=v=>v;//等价于var f=function(v){
return v;}

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

var f = () => 5;// 等同于var f = function () {
return 5 };var sum = (num1, num2) => num1 + num2;// 等同于var sum = function(num1, num2) {
return num1 + num2;};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

var sum = (num1, num2) => { return num1 + num2; }

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

// 报错let getTempItem = id => { id: id, name: "Temp" };// 不报错let getTempItem = id => ({ id: id, name: "Temp" });

箭头函数可以与变量解构结合使用。

const full = ({ first, last }) => first + ' ' + last;// 等同于function full(person) {
return person.first + ' ' + person.last;}

箭头函数的一个用处是简化回调函数。

// 正常函数写法[1,2,3].map(function (x) {
return x * x;});// 箭头函数写法[1,2,3].map(x => x * x);

箭头函数可以和rest参数结合

const numbers = (...nums) => nums;numbers(1, 2, 3, 4, 5)// [1,2,3,4,5]const headAndTail = (head, ...tail) => [head, tail];headAndTail(1, 2, 3, 4, 5)// [1,[2,3,4,5]]

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

function foo() {  setTimeout(() => {    console.log('id:', this.id);  }, 100);}var id = 21;foo.call({ id: 42 });//setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{
id: 42}),所以输出的是42。

箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。

function Timer() {  this.s1 = 0;  this.s2 = 0;  // 箭头函数  setInterval(() => this.s1++, 1000);  // 普通函数  setInterval(function () {    this.s2++;  }, 1000);}var timer = new Timer();setTimeout(() => console.log('s1: ', timer.s1), 3100);setTimeout(() => console.log('s2: ', timer.s2), 3100);// s1: 3// s2: 0//Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,timer.s1被更新了 3 次,而timer.s2一次都没更新。

this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。

箭头函数转成 ES5 的代码如下。

// ES6function foo() {  setTimeout(() => {    console.log('id:', this.id);  }, 100);}// ES5function foo() {  var _this = this;  setTimeout(function () {    console.log('id:', _this.id);  }, 100);

除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments、super、new.target。

function foo() {  setTimeout(() => {    console.log('args:', arguments);  }, 100);}foo(2, 4, 6, 8)// args: [2, 4, 6, 8]

箭头函数内部,还可以再使用箭头函数。

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]

11.双冒号运算符

函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面

foo::bar;// 等同于bar.bind(foo)

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

var method = obj::obj.foo;// 等同于var method = ::obj.foo;

12.尾调用

尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

function f(x){
return g(x);}

函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

function f() {
let m = 1; let n = 2; return g(m + n);}f();// 等同于function f() {
return g(3);}f();// 等同于g(3);

上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除f(x)的调用帧,只保留g(3)的调用帧。

这就叫做“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。
注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

function addOne(a){
var one = 1; function inner(b){
return b + one; } return inner(a);}//上面的函数不会进行尾调用优化,因为内层函数inner用到了外层函数addOne的内部变量one。

转载地址:http://vrarb.baihongyu.com/

你可能感兴趣的文章
关于list对象的转化问题
查看>>
VOPO对象介绍
查看>>
suse创建的虚拟机,修改ip地址
查看>>
linux的挂载的问题,重启后就挂载就没有了
查看>>
docker原始镜像启动容器并创建Apache服务器实现反向代理
查看>>
docker容器秒死的解决办法
查看>>
管理网&业务网的一些笔记
查看>>
openstack报错解决一
查看>>
openstack报错解决二
查看>>
linux source命令
查看>>
openstack报错解决三
查看>>
乙未年年终总结
查看>>
子网掩码
查看>>
第一天上班没精神
查看>>
启动eclipse报错:Failed to load the JNI shared library
查看>>
eclipse安装插件的两种方式在线和离线
查看>>
linux下源的相关笔记(suse)
查看>>
linux系统分区文件系统划分札记
查看>>
Linux(SUSE 12)安装Tomcat
查看>>
Linux(SUSE 12)安装jboss4并实现远程访问
查看>>