第5集丨ES6 江湖 —— 函数扩展

目录

  • 一、箭头函数
    • 1.1 基本用法
    • 1.2 与变量解构结合使用
    • 1.3 表达更加简洁
    • 1.4 rest 参数与箭头函数结合
    • 1.5 注意点
    • 1.6 this对象
      • 1.6.1 利于封装回调函数
      • 1.6.2 练习
    • 1.7 arguments 、 super、 new.target
    • 1.8 call() 、 apply() 、 bind()
  • 二、rest 参数
    • 2.1 rest 参数代替 arguments
    • 2.2 rest 参数改写数组 push
    • 2.3 rest 参数之后不能再有其他参数
    • 2.4 函数length 属性不包括 rest 参数
  • 三、函数name 属性
    • 3.1 匿名函数
    • 3.2 具名函数
    • 3.3 构造函数
    • 3.4 bind的函数
  • 四、函数参数的默认值
    • 4.1 基本用法
    • 4.2 不能用 let 或 const 再次声明
    • 4.3 不能有同名参数
    • 4.4 参数默认值是惰性求值
    • 4.5 与解构赋值默认值结合使用
    • 4.6 参数默认值的位置
    • 4.7 练习
  • 五、函数的 length 属性
  • 六、函数参数作用域
    • 6.1 情况一
    • 6.2 情况二
    • 6.3 情况三
    • 6.4 情况四
    • 6.5 案例

一、箭头函数

1.1 基本用法

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

  • 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
  • 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return 语句返回。
  • 由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
//例1
var f = v => v;// 等同于var f = function (v) {return v;};//例2
var f = () => 5;// 等同于var f = function () { return 5 };//例3
var sum = (num1, num2) => { let result = num1 + num2; return result
}// 等同于var sum = function(num1, num2) {let result = num1 + num2; return num1 + num2;};// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });

1.2 与变量解构结合使用

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

1.3 表达更加简洁

箭头函数使得表达更加简洁.

const isEven = n => n % 2 === 0;
const square = n => n * n;

上面代码只用了两行,就定义了两个简单的工具函数。如果不用箭头函数,可能就要占用多行,而且还不如现在这样写醒目。

  • 箭头函数的另一个用处是简化回调函数。
// 正常函数写法
[1,2,3].map(function (x) {return x * x;
});
// 箭头函数写法
[1,2,3].map(x => x * x);
// 正常函数写法
var result = values.sort(function (a, b) {return a - b;
});
// 箭头函数写法
var result = values.sort((a, b) => a - b);

1.4 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]]

1.5 注意点

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

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

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

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

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

1.6 this对象

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

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

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

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

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

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

上面代码中,转换后的 ES5 版本清楚地说明了,箭头函数里面根本没有自己的 this ,而是引用外层的 this

1.6.1 利于封装回调函数

箭头函数可以让 this 指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM 事件的回调函数封装在一个对象里面。

var handler = {id: '123456',init: function() {document.addEventListener('click',event => this.doSomething(event.type), false);},doSomething: function(type) {console.log('Handling ' + type  + ' for ' + this.id);}
};

上面代码的 init 方法中,使用了箭头函数,这导致这个箭头函数里面的 this ,总是指向 handler 对象。

1.6.2 练习

请问下面的代码之中有几个 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 ,就是函数 foothis ,所以 t1t2t3 都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的 this ,它们的 this 其实都是最外层 foo 函数的 this

1.7 arguments 、 super、 new.target

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

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

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

1.8 call() 、 apply() 、 bind()

另外,由于箭头函数没有自己的 this ,所以当然也就不能用 call() 、 apply() 、 bind() 这些方法去改变 this 的指向。

(function() {return [(() => this.x).bind({ x: 'inner' })()];
}).call({ x: 'outer' });
// ['outer']

上面代码中,箭头函数没有自己的 this ,所以 bind 方法无效,内部的this指向外部的 this

二、rest 参数

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

2.1 rest 参数代替 arguments

下面是一个 rest 参数代替 arguments 变量的例子。arguments 对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用 Array.prototype.slice.call 先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。

// arguments变量的写法
function sortNumbers() {return Array.prototype.slice.call(arguments).sort();
}
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

2.2 rest 参数改写数组 push

下面是一个利用 rest 参数改写数组 push 方法的例子。

function push(array, ...items) {items.forEach(function(item) {array.push(item);console.log(item);});
}
var a = [];
push(a, 1, 2, 3)

2.3 rest 参数之后不能再有其他参数

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

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

2.4 函数length 属性不包括 rest 参数

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

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

三、函数name 属性

函数的name属性,返回该函数的函数名。这个属性早就被浏览器广泛支持,但是直到 ES6,才将其写入了标准。

3.1 匿名函数

需要注意的是,ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5name 属性,会返回空字符串,而 ES6name 属性会返回实际的函数名。

function foo() {}
foo.name // "foo"var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"

3.2 具名函数

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

const bar = function baz() {};
// ES5
bar.name // "baz"
// ES6
bar.name // "baz"

3.3 构造函数

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

(new Function).name // "anonymous"

3.4 bind的函数

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

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

四、函数参数的默认值

ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。

function log(x, y) {y = y || 'World';console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

这种写法的缺点在于,如果参数 y 赋值了,但是对应的布尔值为 false ,则该赋值不起作用。就像上面代码的最后一行,参数 y 等于空字符,结果被改为默认值。

为了避免这个问题,通常需要先判断一下参数 y 是否被赋值,如果没有,再等于默认值。

if (typeof y === 'undefined') {y = 'World';
}

4.1 基本用法

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

function log(x, y = 'World') {console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

4.2 不能用 let 或 const 再次声明

参数变量是默认声明的,所以不能用 let 或 const 再次声明。

function foo(x = 5) {let x = 1; // errorconst x = 2; // error
}

4.3 不能有同名参数

使用参数默认值时,函数不能有同名参数。

// 不报错
function foo(x, x, y) {// ...
}
// 报错
function foo(x, x, y = 1) {// ...
}
// SyntaxError: Duplicate parameter name not allowed in this context

4.4 参数默认值是惰性求值

一个容易忽略的地方是,参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。

下面代码中,参数 p 的默认值是 x + 1 。这时,每次调用函数 foo,都会重新计算 x + 1 ,而不是默认 p 等于 100

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

4.5 与解构赋值默认值结合使用

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

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

上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数 foo 的参数是一个对象时,变量x y 才会通过解构赋值生成。如果函数 foo 调用时没提供参数,变量 xy 就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。

function foo({x, y = 5} = {}) {console.log(x, y);
}
foo() // undefined 5

4.6 参数默认值的位置

通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。

// 例一
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]

上面代码中,有默认值的参数都不是尾参数。这时,无法只省略该参数,而不省略它后面的参数,除非显式输入 undefined

如果传入 undefined ,将触发该参数等于默认值, null 则没有这个效果。

function foo(x = 5, y = 6) {console.log(x, y);
}
foo(undefined, null)
// 5 null

4.7 练习

作为练习,请问下面两种写法有什么差别?

// 写法一
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]

五、函数的 length 属性

指定了默认值以后,函数的 length 属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后, length 属性将失真。

length 属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,后文的 rest 参数也不会计入 length 属性。

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

如果设置了默认值的参数不是尾参数,那么 length 属性也不再计入后面的参数了。

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

六、函数参数作用域

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

6.1 情况一

var x = 1;
function f(x, y = x) {console.log(y);
}
f(2) // 2

上面代码中,参数 y 的默认值等于变量 x 。调用函数 f 时,参数形成一个单独的作用域。在这个作用域里面,默认值变量 x 指向第一个参数 x ,而不是全局变量 x ,所以输出是 2

6.2 情况二

let x = 1;
function f(y = x) {let x = 2;console.log(y);
}
f() // 1

上面代码中,函数 f 调用时,参数 y = x 形成一个单独的作用域。这个作用域里面,变量 x 本身没有定义,所以指向外层的全局变量 x 。函数调用时,函数体内部的局部变量 x 影响不到默认值变量 x

6.3 情况三

如果此时,全局变量 x 不存在,就会报错。

function f(y = x) {let x = 2;console.log(y);
}
f() // ReferenceError: x is not defined

下面这样写,也会报错。

var x = 1;
function foo(x = x) {// ...
}
foo() // ReferenceError: x is not defined

上面代码中,参数 x = x 形成一个单独作用域。实际执行的是 let x = x ,由于暂时性死区的原因,这行代码会报错”x 未定义“。

6.4 情况四

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

let foo = 'outer';
function bar(func = () => foo) {let foo = 'inner';console.log(func());
}
bar(); // outer

6.5 案例

var x = 1;
function foo(x, y = function() { x = 2; }) {var x = 3;y();console.log(x);
}
foo() // 3
x // 1

上面代码中,函数 foo 的参数形成一个单独作用域。这个作用域里面,首先声明了变量 x ,然后声明了变量 yy 的默认值是一个匿名函数。这个匿名函数内部的变量 x ,指向同一个作用域的第一个参数 x 。函数 foo 内部又声明了一个内部变量 x ,该变量与第一个参数 x 由于不是同一个作用域,所以不是同一个变量,因此执行 y 后,内部变量 x 和外部全局变量 x 的值都没变。

如果将 var x = 3var 去除,函数 foo 的内部变量 x 就指向第一个参数x,与匿名函数内部的 x 是一致的,所以最后输出的就是 2 ,而外层的全局变量 x 依然不受影响。

var x = 1;
function foo(x, y = function() { x = 2; }) {x = 3;y();console.log(x);
}
foo() // 2
x // 1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/8317.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git常用命令总结

文章目录 1. 创建仓库和初始化2. 添加和提交更改3. 分支管理4. 查看状态和历史5. 撤销更改6. 远程仓库 当使用 Git 进行版本控制时,以下是一些常用的 Git 命令的详细总结: 1. 创建仓库和初始化 git init: 在当前目录创建一个新的 Git 仓库。执行后会在当…

【C++】-多态的语法细节详解

💖作者:小树苗渴望变成参天大树🎈 🎉作者宣言:认真写好每一篇博客💤 🎊作者gitee:gitee✨ 💞作者专栏:C语言,数据结构初阶,Linux,C 动态规划算法🎄 如 果 你 …

android 高版本sd卡目录读写权限

1、从安卓11不允许访问sd目录,官方说明如下: https://developer.android.com/about/versions/11/privacy/storage?hlzh-cn 2、使用MediaStore方法 一手遮天 Android - 存储: Android 11 通过 MediaStore 管理文件 - webabcd - 博客园 (cnblogs.com) …

GPT-3.5:ChatGPT的奇妙之处和革命性进步

🌷🍁 博主 libin9iOak带您 Go to New World.✨🍁 🦄 个人主页——libin9iOak的博客🎐 🐳 《面试题大全》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~&#x1f33…

苹果开发“Apple GPT”AI科技迎来新格局

根据彭博社的马克・古尔曼(Mark Gurman)报道,苹果内部正在开发“Apple GPT”人工智能项目,足以媲美 OpenAI 的 ChatGPT ,预计明年推出。就在彭博社消息发出之后,苹果股价上涨了2.3%,市值顶峰时增…

Auspiciousness

登录—专业IT笔试面试备考平台_牛客网 题目大意:有一个含有2n张牌的牌堆,当手牌为空时, 从牌堆顶抽一张牌,然后猜牌堆顶的牌和手牌顶的牌的大小关系,并抽牌,如果猜对了继续循环,否则游戏直接结…

利用事件委托和冒泡 判断点击了哪个元素或该元素的子级元素

概述&#xff1a; 通过 event.composedPath() 拿到事件冒泡路径&#xff0c;再循环匹配 获取所需的dom <template><div click"handleClick"><div class"child child1"><button>Click Me</button><span>Click Me1<…

题目4 命令执行(保姆级教程)

url&#xff1a;http://192.168.154.253:84/ #打开http://XXX:81/&#xff0c;XXX为靶机的ip地址 审题 1、打开题目看到有一个提示&#xff0c;此题目需要通过利用命令执行漏洞执行Linux命令获取webshell&#xff0c;最后从根目录下key.php文件中获得flag 2、开始答题 第一步&…

Nginx在springboot中起到的作用

面试时这样回答&#xff1a; 在Spring Boot项目中使用Nginx可以有以下用途&#xff1a; 1. 反向代理&#xff1a;Nginx可以作为反向代理服务器&#xff0c;将外部请求转发到后端的Spring Boot应用&#xff0c;并可以实现负载均衡、高可用、缓存等功能&#xff0c;提高系统的性…

css设置八等分圆

现需要上图样式的布局&#xff0c;我通过两张向右方的图片&#xff0c;通过定位和旋转完成了布局。 问题&#xff1a; 由于是通过旋转获取到的样式&#xff0c;实际的盒子是一个长方形&#xff0c;当鼠标移入对应的箭头时选中的可能是其他盒子&#xff0c;如第一张设计稿可以看…

C++ make_shared pros and cons

高效内存分配 std::shared_ptr<Node> ptr(new Node);上面的代码存在两次内存分配 new Node为ptr分配控制块的内存用于存放引用计数等信息 如果使用std::make_shared只会存在一次内存分配&#xff0c;它会一次性申请足够大的空间用于存储Node和控制块 异常安全 void …

Ngrok 的绝佳替代品,内网穿透神器 Serveo

什么是 Serveo Serveo 是一个免费的内网穿透服务&#xff0c;Serveo 可以将本地计算机暴露在互联网上&#xff0c;官方声称其为 Ngrok 的绝佳替代品。 Serveo 其最大优点是使用现有的 SSH 客户端&#xff0c;无需安装任何客户端软件即可完成端口转发。 Serveo 工作原理很简单…

解密数字孪生:解决实际问题的神奇技术

数字孪生是一种将现实世界与数字世界相连接的创新技术&#xff0c;通过将实际物体或系统的数据和行为模拟到数字平台上&#xff0c;实现真实与虚拟之间的交互和信息共享。数字孪生的应用不仅仅局限于虚拟现实&#xff08;VR&#xff09;和仿真领域&#xff0c;它在解决实际问题…

Socket发送数据---winsock库和boost库

一个是通过winsock库提供的api实现,一个是boost库实现,两个方法都可以,因为项目是vc++6.0实现的,不支持boost库,只能使用winsock库,vc++6.0太老,局限性大。 通过Winsock库提供的API 通过UDP #include<winsock2.h> #include<vector> #include<WS2tcpip.h…

泰晓科技发布 Linux Lab v1.2 正式版

导读近日消息&#xff0c;Linux Lab 是一套用于 Linux 内核学习、开发和测试的即时实验室&#xff0c;官方称其“可以极速搭建和使用&#xff0c;功能强大&#xff0c;用法简单”。 自去年 12 月份发布 Linux Lab v1.1 后&#xff0c;v1.2 正式版目前已经发布于 GitHub 及 Gite…

为什么 Linux 内核协议栈会丢弃 SYN 数据包

最近了解到 SYN 数据包丢弃的问题&#xff0c;网上有一些资料&#xff0c;这里记录分享一下。 serverfault上的重要信息 tcp - No SYN-ACK Packet from server - Server Fault 信息如下&#xff1a; My embedded system with LwIP is the client and I have server1 and ser…

文艺类《匠心》简介及投稿要求

文艺类《匠心》简介及投稿要求 《匠心》期刊简介&#xff1a; 主管单位&#xff1a;内蒙古画报社 主办单位&#xff1a;内蒙古画报社 国际刊号&#xff1a;ISSN:1672-9099 国内刊号&#xff1a;CN:15-1383/J 发行周期&#xff1a;月刊;收录网站&#xff1a;中国知网收录 …

LiveGBS流媒体平台GB/T28181常见问题-TOKEN有效期是多久如何设置token有效期StreamToken和URLToken

LiveGBS中TOKEN有效期是多久如何设置token有效期StreamToken和URLToken 1、获取TOKEN2、TOKEN有效期3、默认token有效期3、自定义token加密key3.1、token_key3.2、stream_token_key 4、如何配置一直有效的token4.1、URLToken4.2、StreamToken 5、动态有效期6、流地址鉴权开启后…

git的使用

git使用 Git操作基本指令&#xff1a;分支操作&#xff1a;操作远程仓库&#xff1a;文件操作比较文件差异删除文件撤销文件重命名文件 操作标签SSH密钥git错误示例1、git status&#xff08;或任何其他git命令&#xff09;然后会出现fatal: Not a git repository (or any of t…

智能管理轻松搞定!文件批量改名并按数量平均分类,自动新建文件夹保存!

我们面对大量文件需要整理和管理时&#xff0c;批量改名和分类是一项繁琐而重要的任务。为了帮助您高效完成这项工作&#xff0c;我们介绍了一种智能方法&#xff1a;按数量平均分类并自动保存文件&#xff0c;让您轻松整理文件夹内容。 首先第一步&#xff0c;我们要进入文件…