美文网首页
理解 JavaScript 编程(ECMAScript 6)(一

理解 JavaScript 编程(ECMAScript 6)(一

作者: rollingstarky | 来源:发表于2020-05-07 23:13 被阅读0次

一、Block Binding

在大多数基于 C 的编程语言中,变量通常会在声明时创建。而对于 JavaScript 语言,变量创建的时间点则取决于具体的声明方式。
JavaScript 中经典的使用 var 声明变量的方式容易引起困惑,因此 ECMAScript 6 中引入了块级别的变量绑定(block-level binding)。

var 关键字

var 关键字对于变量的声明,会默认该声明位于函数顶部(位于函数外部时为全局作用域),而不去管声明语句实际出现的位置。称为 hoisting

function getValue(condition) {
  if (condition) {
    var value = "blue"
    console.log("condition is true and value is " + value)
  } else {
    console.log("condition is false and value is " + value)
  }
  console.log("outside if, value is " + value)
}

getValue(true)
// condition is true and value is blue
// outside if, value is blue
getValue(false)
// condition is false and value is undefined
// outside if, value is undefined

习惯上会认为,上述代码中只有 condition 为 True 时变量 value 才会被创建;实际上 value 变量存在于函数的各个部分,只是在 condition 为 False 时未被初始化(undefined)。

上面的代码会被 JavaScript 引擎视作如下形式:

function getValue(condition) {
  var value;
  if (condition) {
    value = "blue";
    console.log("condition is true and value is " + value)
  } else {
    console.log("condition is false and value is " + value)
  }
  console.log("outside if, value is " + value)
}
块级声明和 let 语句

由块级声明创建的变量无法被该代码块以外的部分访问。

块作用域(Block scopes)一般创建于以下位置:

  • 函数内部
  • 代码块内部(被大括号 {} 包裹的部分)

块作用域符合大部分基于 C 的编程语言的工作方式。

let 关键字会将变量的作用域限制在当前代码块内部。

function getValue(condition) {
  if (condition) {
    let value = "blue"
    console.log("condition is true and value is " + value)
  } else {
    console.log("condition is false and value is " + value)
  }
  console.log("outside if, value is " + value)
}

getValue(true)
// condition is true and value is blue
// ReferenceError: value is not defined
No Redeclaration

如果同一作用域内已有相同名称的变量被声明,则 let 语句会报错。

var count = 30
let count = 40
// SyntaxError: Identifier 'count' has already been declared

但是在不同作用域中,类似的情况则不会报错:

var count = 30
if (true) {
  let count = 40
}

const 关键字用于声明常量,常量的值一旦确定后即不可再变更,因此在声明的同时必须赋值以完成初始化。

const maxItems = 30
const name;
// SyntaxError: Missing initializer in const declaration

需要注意的是,常量的“不可变”仅针对变量与值的绑定关系,而并不限制值本身的改动。即使用 const 声明某个对象,则对象本身的改动不被禁止。

const person = {
  name: "Nicholas"
}
person.name = "Greg"
person.name
// 'Greg'
person = {
  name: "Grep"
}
// TypeError: Assignment to constant variable.
循环中的块级绑定

var:

for (var i = 0; i < 10; i++) {
  // do nothing
}
console.log(i)
// 10

let:

for (let i = 0; i < 10; i++) {
  // do nothing
}
console.log(i)
// ReferenceError: i is not defined

var 声明语句的特性(loop 变量可以从 loop 外部访问)使得在循环中创建函数的行为会产生问题。

var funcs = []

for (var i = 0; i < 10; i++) {
  funcs.push(function() {
    console.log(i)
  })
}

funcs.forEach(function(func) {
  func()
})
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10

解决的办法是使用如下代码:

var funcs = []

for (var i = 0; i < 10; i++) {
  funcs.push((function(value) {
    return function() {
      console.log(value)
    }
  }(i)))
}

funcs.forEach(function(func) {
  func()
})
// 0
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9

有了块级声明以后,上述需求可以被简单地实现(只需要将第一段代码中的 var 关键字改为 let 即可):

var funcs = []

for (let i = 0; i < 10; i++) {
  funcs.push(function() {
    console.log(i)
  })
}

funcs.forEach(function(func) {
  func()
})
// 0
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9

二、函数

参数带默认值的函数

在 ECMAScript 5 及以前版本的 JavaScript 中,通常使用如下模式创建带默认参数的函数:

function makeRequest(url, timeout, callback) {
  timeout = timeout || 2000
  callback = callback || function() {}
  // the rest code
}

但上述 || (或)操作符的使用存在一定问题,如传递给 timeout 参数的值为 0 时,timeout || 2000 表达式的值为 2000 而不是 0,导致程序出现意想不到的结果。改进如下:

function makeRequest(url, timeout, callback) {
  timeout = (typeof timeout !== "undefined") ? timeout : 2000
  callback = (typeof callback !== "undefined") ? callback : function() {}
  // the rest code
}

在 ECMAScript 6 中,为函数的参数提供默认值的方式则非常简单直观:

function makeRequest(url, timeout = 2000, callback = function() {}) {
  // the rest code
}

表达式作为参数默认值

function getValue() {
  return 5
}

function add(first, second = getValue()) {
  return first + second
}

console.log(add(1, 1))  // 2
console.log(add(1))  // 6

甚至可以使用如下代码:

function getValue(value) {
  return value + 5
}

function add(first, second = getValue(first)) {
  return first + second
}

console.log(add(1, 1))  // 2
console.log(add(1))  // 7
匿名参数

ECMAScript 5 中的匿名参数(通过 arguments 对象获取所有参数,包含定义函数时未显式指定的参数):

function pick(object) {
  let result = Object.create(null)
  for (let i = 1, len = arguments.length; i < len; i++) {
    result[arguments[i]] = object[arguments[i]]
  }
  return result
}

let book = {
  title: "Understanding ECMAScript 6",
  author: "Nicholas C. Zakas",
  year: 2016
}

let bookData = pick(book, "author", "year")

console.log(bookData.author)  // "Nicholas C. Zakas"
console.log(bookData.year)  // 2016

注意 for 循环是从 i=1 即第二个参数开始的。

Rest Parameters
上述 pick 函数可以利用 ECMAScript 6 支持的 Rest Parameters 特性重写为如下形式:

function pick(object, ...keys) {
  let result = Object.create(null)

  for (let i = 0, len = keys.length; i < len; i++) {
    result[keys[i]] = object[keys[i]]
  }
  return result
}

let book = {
  title: "Understanding ECMAScript 6",
  author: "Nicholas C. Zakas",
  year: 2016
}

let bookData = pick(book, "author", "year")

console.log(bookData.author)  // "Nicholas C. Zakas"
console.log(bookData.year)  // 2016
函数构造器
var add = new Function("first", "second", "return first + second")

console.log(add(1, 1))  // 2

ECMAScript 6 使得函数构造器可以支持默认参数和 rest parameters 等特性:

var add = new Function("first", "second = first", "return first + second")

console.log(add(1, 1))  // 2
console.log(add(1))  // 2

var pickFirst = new Function("...args", "return args[0]")

console.log(pickFirst(1, 2))  // 1
函数的两种调用方式
function Person(name) {
  this.name = name
}

var person = new Person("Nicholas")
var notAPerson = Person("Nicholas")

console.log(person)  // Person { name: 'Nicholas'  }
console.log(notAPerson)  // undefined

JavaScript 有两个针对函数的内部方法:[[Call]][[Construct]]

当函数不通过 new 关键字调用时,[[Call]] 方法执行,接着运行函数体中的代码;
当函数通过 new 关键字调用时,[[Construct]] 方法执行,创建一个新的对象实例并绑定给 this,之后继续执行函数体中的代码。

ECMAScript 6 中可以使用 new.target 判断当前函数是否由 new 调用:

function Person(name) {
  if (typeof new.target !== "undefined") {
    this.name = name
  } else {
    throw new Error("You must use new with Person.")
  }
}

var person = new Person("Nicholas")
var notAPerson = Person("Michael")  // error
Arrow Function

Arrow Function 是指使用 => 符号定义的函数。与传统的 JavaScript 函数相比,Arrow Function 主要有以下几个不同点:

  • 没有 this, super, arguments, new.target 的绑定。Arrow Function 中 this, super, arguments, new.target 的值由距离最近的非 Arrow Function 定义
  • 不能被 new 调用。Arrow Function 没有构造方法因此不能作为构造器使用
  • 没有 prototype。Arrow Function 的 prototype 属性不存在(函数本身不能被 new 调用,prototype 没有必要)
  • 函数中的 this 的值不能被修改

基本语法:

let reflect = value => value

// equivalent to:
let reflect = function(value) {
  return value
}


let sum = (num1, num2) => num1 + num2

// equivalent to:
let sum = function(num1, num2) {
  return num1 + num2
}


let getName = () => "Nicholas"

// equivalent to:
let getName = function() {
  return "Nicholas"
}


let doNothing = () => {}

// equivalent to:
let doNothing = function() {}


let getTempItem = id => ({ id: id, name: "Temp" })

// equivalent to:
let getTempItem = function(id) {
  return {
    id: id,
    name: "Temp"
  }
}

参考资料

Understanding ECMAScript 6

相关文章

网友评论

      本文标题:理解 JavaScript 编程(ECMAScript 6)(一

      本文链接:https://www.haomeiwen.com/subject/ddujwhtx.html