本章概要
- 箭头函数
- 箭头函数的语法
- 箭头函数与this
- Symbol
- 创建 Symbol
- Symbol与类型转换
- 作为属性名使用
- 共享的 Symbol
3.8 箭头函数
ES6 允许使用箭头“=>”定义函数,箭头函数的语法多变,根据实际的使用场景有多种形式,但都需要由函数参数、箭头和函数体组成。根据 JavaScript 函数定义的各种不同形式,箭头函数的参数和函数体可以分别采取多种不同的形式。
3.8.1 箭头函数的语法
单一函数、函数体只有一句语句的箭头函数定义形式如下:
let welcome = msg => msg;
/*
*相当于
function welcome(msg){
return msg;
}
*/
console.log(welcome("welcome you.")); // welcome you.
如果函数有多于一个的参数,则需要在参数的两侧添加一对圆括号。如下:
let welcome = (user, msg) => `${user}, ${msg}`;
/*
*相当于
function welcome(user, msg){
return user + ", " + msg;
}
*/
console.log(welcome("zhangasan", "welcome you.")); // zhangasan, welcome you.
如果函数没有参数,则需要使用一对空的圆括号。如下:
let welcome = () => "welcome you.";
/*
*相当于
function welcome(){
return "welcome you.";
}
*/
console.log(welcome("welcome you.")); // welcome you.
如果函数体有多条语句,则需要用花括号包裹函数体。如下:
let add = (a, b) => {
let c = a + b;
return c;
}
/*
*相当于
function add(a, b){
let c = a + b;
return c;
}
*/
console.log(add(5, 3)); // 8
如果要创建一个空函数,则需要写一对没有内容的圆括号代表参数部分,一对没有内容的话括号代表空的函数体。如下:
let emptyFunction = () => {};
/*
*相当于
function emptyFunction(){}
*/
如果箭头函数的返回值是一个对象字面量,则需要将该对象字面量包括在圆括号中。如下:
let createCar = (color, doors) => ({color: color, doors: doors});
/*
*相当于
function createCar(color, doors){
return {
color: color,
doors: doors
}
}
*/
console.log(createCar("black", 4)); // { color: 'black', doors: 4 }
将对象字面量包括在圆括号中是为了将其与函数体区分开。
箭头函数可以和对象解构结合使用,如下:
let personInfo = ({name, age}) => `${name}'s age is ${age} years old.`;
/*
*相当于
function personInfo({name, age}){
return `${name}'s age is ${age} years old.`;
}
*/
let person = {name: "zhangsan", age: 18};
console.log(personInfo(person)); // zhangsan's age is 18 years old.
3.8.2 箭头函数与this
JavaScript 中的 this 关键字是一个神奇的东西,与其它高级语言中的this 引用或 this 指针不同的是,JavaScript 中的 this 并不是指向对象本身,其指向是可以改变的,根据当前执行上下文的变化而变化。
<!DOCTYPE html>
<html>
<head>
<script>
var greeting = "Welcome";
function sayHello(user){
alert(this.greeting + ", " + user);
}
var obj = {
greeting: "Hello",
sayHello: sayHello
}
sayHello("zhangsan"); //Welcome, zhangsan
obj.sayHello("lisi"); //Hello, lisi
var sayHi = obj.sayHello;
sayHi("wangwu"); //Welcome, wangwu
</script>
</head>
</html>
代码解释:
(1)调用 sayHello(“zhangsan”) 时,相当于执行 window.sayHello(“zhangsan”),因此函数内部的this 指向的是 window对象,在代码第一行定义的全局变量 greeting 将自动成为 window 对象的属性,因此最后的结果是“Welcome, zhangsan”。
(2)调用obj.sayHello(“lisi”) 时,函数内部的 this 指向的是obj对象,而 obj 对象内部定义了 greeting属性,因此最后的结果是“Hello, lisi”。
(3)调用 sayHi(“wangwu”) 时,虽然函数是由 obj.sayHello 赋值得到的,但是在执行 sayHi() 函数时,当前的执行上下文对象是 window对象,相当于调用 window.sayHi(“wangwu”),因此,最后的结果是“Welcome, wangwu”。
<!DOCTYPE html>
<html>
<head>
<script>
var obj = {
greeting: "Hello",
sayHello: function(){
setTimeout(function(){
alert(this.greeting);
}, 2000);
}
}
obj.sayHello(); // undefined
</script>
</head>
</html>
代码解释:当调用 obj.sayHello() 时,只是执行了 setTimeout() 函数,2s后才开始执行 setTimeout() 函数参数中定义的匿名函数,而该匿名函数的执行上下文对象是 window ,因此 this 执行的是 window 对象,而在 window 对象中并没有定义 greeting 属性,所以找不到该属性,自然输出的是 undefined。
为了解决 this 指向的问题,可以使用函数对象的 bind() 方法,将 this 明确地绑定到某个对象上。
<!DOCTYPE html>
<html>
<head>
<script>
var greeting = "Welcome";
function sayHello(user){
alert(this.greeting + ", " + user);
}
var obj = {
greeting: "Hello",
sayHello: sayHello
}
var sayHi = obj.sayHello.bind(obj);
sayHi("wangwu"); //Hello, wangwu
var obj = {
greeting: "Hello",
sayHello: function(){
setTimeout((function(){
alert(this.greeting);
}).bind(this), 2000);
}
// 或者
//sayHello: function(){
// var that = this;
// setTimeout(function(){
// alert(that.greeting);
// }, 2000);
//}
}
obj.sayHello(); // Hello
</script>
</head>
</html>
使用 bind() 方式实际上是创建了一个新的函数,称为绑定函数,该函数的 this 被绑定到参数传入的对象。为了避免创建一个额外的函数,下面使用箭头函数来解决this的问题。
箭头函数中没有 this 绑定,必须通过查找作用域决定其值。如果箭头函数被非箭头函数包含,则this绑定的是最后一层非箭头函数的 this ;否则,this 的值会被设置为全局变量。使用箭头函数修改上述代码,如下:
<!DOCTYPE html>
<html>
<head>
<script>
var obj = {
greeting: "Hello",
sayHello: function(){
setTimeout(() => alert(this.greeting), 2000);
}
}
obj.sayHello(); // Hello
</script>
</head>
</html>
alert() 函数参数中的 this 与 sayHello() 方法中的 this 一致,而这个 this 指向的是 obj 对象,因此最后调用 obj.sayHello() 的结果是 Hello。
箭头函数中的 this 值取决于该函数外部非箭头函数的 this 值,且不能通过 call()、apply()、bind()方法改变 this 的值。
箭头函数注意事项:
- 没有 this、super、arguments 和 new.target 绑定。箭头函数中的 this、super、arguments 和 new.target 这些值由外围最近一层非箭头函数决定
- 不能通过new关键字调用。箭头函数不能被用作构造函数,也就是说,不可以使用 new 关键字调用箭头函数,否则程序会抛出一个错误
- 没有原型。由于不可以通过 new 关键字调用箭头函数,因此没有构建原型的需求,所以箭头函数不存在 prototype 这个属性
- 不可以改变 this 的绑定。函数内部的 this 值不可被改变,在函数的声明周期内始终保持一致
- 不支持 arguments 对象。箭头函数没有 arguments 绑定,所以只能通过命名参数和 rest 参数这两种形式访问函数的参数。
3.9 Symbol
在 ES5 及早期版本中,有 5 中 原始数据类型:字符串(String)、数值型(Number)、布尔型(Boolean)、null 和 undefined ,ES6 引入了一种新的原始数据类型——Symbol ,表示独一无二的值。
3.9.1 创建 Symbol
一个具有 Symbol 数据类型的值可以被称为 “符号类型值”。
在JavaScript 运行环境中,一个符号类型值是通过调用函数 Symbol() 函数创建的,这个函数动态地生成了一个匿名的、唯一的值。如下:
let sn1 = Symbol();
// 使用typeof操作符检测sn变量是否是Symbol类型
console.log(typeof sn1); // symbol
console.log(sn1); // Symbol()
let sn2 = Symbol();
console.log(sn1 === sn2); // false
需要注意的是,Symbol 是原始值,所以不能使用 new Symbol() 创建 Symbol 值,这会导致程序报错。每一个 Symbol 实例都是唯一且不可改变的。
Symbol() 函数可以接受一个可选的字符串参数,用于为新创建的 Symbol 实例提供描述,这个描述不可以用于属性访问,主要用于调试的目的,以及方便阅读代码。如下:
let sn1 = Symbol("sn1");
let sn2 = Symbol("sn2");
console.log(sn1); // Symbol(sn1)
console.log(sn2); // Symbol(sn2)
let sn3 = Symbol("sn1");
console.log(sn1 === sn3); // false
sn1 和 sn2 是两个 Symbol 值。如果不加参数,他们在 Console 窗口中的输出都是 Symbol(),不利于区分。有了参数以后,就等于为它们各自加上了描述,输出的时候就能够分清,到底是哪一个值。
如果调用 Symbol() 函数传入的参数是一个对象,就会调用该对象的 toString() 方法,将其转换为字符串,然后再生成一个 Symbol 值。如下:
let obj = {
toString(){
return "sn";
}
}
console.log(Symbol(obj)); // Symbol(sn)
3.9.2 Symbol与类型转换
自动转型是 JavaScript 中的一个重要的语言特性,利用这个特性能够在特定的场景下将某个数据强制转换为其它类型,然而 Symbol 类型比较特殊,其它类型没有与 Symbol 逻辑等价的值,因此不能将 Symbol 值与其它类型的值进行运算。
以下代码将一个 Symbol 值与一个字符串进行相加操作,运行时将提示“不能将一个Symbol值转换为字符串”(Cannot convert a Symbol value to a string)。
let sn = Symbol("sn");
"My sn is " + sn;
虽然 Symbol() 不能与字符串进行运算,但是可以显示地调用 String() 函数将 Symbol 值转换为字符串。如下:
let sn = Symbol("sn");
let str = String(sn);
console.log(str); // Symbol(sn)
console.log(sn.toString()); // Symbol(sn)
当使用 console.log() 方法输出 Symbol 的值是,它实际上也是调用 sn 的 toString() 方法。
有一个例外是,Symbol 可以参与逻辑运算,这是因为 JavaScript 将非空值都看成 true 。如下:
let sn = Symbol("sn");
console.log(Boolean(sn)); // true
console.log(!sn); // false
if(sn) console.log("true"); // true
3.9.3 作为属性名使用
Symbol 类型唯一合理的用法是用变量存储 Symbol 值,然后使用存储的值创建对象属性。由于每个 Symbol 值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。如下:
let sn = Symbol("sn");
// 第一种方式
let obj = {};
obj[sn] = "1111-11";
console.log(obj); // { [Symbol(sn)]: '1111-11' }
// 第二种方式
let obj = {
[sn]: "1111-11"
};
console.log(obj); // { [Symbol(sn)]: '1111-11' }
// 第三种方式
let obj = {};
Object.defineProperty(obj, sn, {value: "1111-11"});
console.log(obj[sn]); // 1111-11
需要注意的是,Symbol 作为对象属性名时不能使用点运算符,要用方括号。因为点运算符后面是字符串,所以不会读取变量 sn 所表示的 Symbol 的值,而是直接将 sn 作为字符串属性名。
Symbol 作为属性名时,该属性是公有属性而不是私有属性,可以在类的外部访问,但是不会出现在 for···in和 for···of 的循环中,也不会被 Object.keys() ,Object.getOwnProperNames() 函数返回。如果要读取一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 方法得到。
let sn = Symbol("sn");
let obj = {};
obj[sn] = "1111-11";
console.log(obj); //{ [Symbol(sn)]: '1111-11' }
for (let prop in obj) { //无输出
console.log(prop);
}
console.log(Object.keys(obj)); // []
console.log(Object.getOwnPropertyNames(obj)); // []
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(sn)]
console.log(Reflect.ownKeys(obj)); // [Symbol(sn)]
3.9.4 共享的 Symbol
有时候可能不希望在不同的代码中使用同一个 Symbol 值,为此,可以使用 Symbol.for() 方法创建一个可共享的 Symbol,该方法接受一个字符串参数,即要创建的 Symbol 的标识符,同时这个参数也被用作 Symbol 的描述。
ES6 提供了一个可以随时访问的全局 Symbol 注册表,当调用 Symbol.for() 方法时,它首先在 全局 Symbol 注册表中搜索以参数作为名称的 Symbol 值,如果找到了,则直接返回已有的 Symbol;如果没有找到,则创建一个新的 Symbol ,以参数作为 key ,注册到全局 Symbol 注册表中,然后返回新创建的 Symbol。如下:
let sn1 = Symbol.for("sn");
let sn2 = Symbol.for("sn");
let sn3 = Symbol("sn");
console.log(sn1); // Symbol(sn)
console.log(sn1 === sn2); // true
console.log(sn1 === sn3); // false
调用 Symbol.for() 和 Symbol() 方法都会生成新的 Symbol 值,区别在于前者会被注册到全局 Symbol 注册表中,之后调用以相同的key 调用 Symbol.for() 方法会返回同一个 Symbol 值;而后者每次调用都会创建一个新的 Symbol 值。
另一个与共享 Symbol 相关的方法是 Symbol.keyFor() ,该方法在全局 Symbol 注册表中搜索已注册的 Symbol 的 key。如下:
let sn1 = Symbol.for("sn");
console.log(Symbol.keyFor(sn1)); // sn
let sn2 = Symbol("sn");
console.log(Symbol.keyFor(sn2)); // undefined
在全局 Symbol 注册表中并不存在 sn2 这个 Symbol ,自然不存在与之相关的 key,所以返回 undefined。