三、ECMAScript 6 语法简介(3)

本章概要

  • 箭头函数
    • 箭头函数的语法
    • 箭头函数与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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小熊猫呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值