深入理解ES6(至第三章-函数)
栏目: JavaScript · 发布时间: 6年前
内容简介:JS引擎在扫描代码发现变量声明时,要么将它们提升至作用域顶部(第一种情况:第二种情况:
-
let
-
const
- 默认使用,在某种程度上实现代码不可变,减少错误发生的几率
- 如果常量是对象,则对象中的值可以修改
- 不能重复声明,声明不会提升
二、临时死区:
JS引擎在扫描代码发现变量声明时,要么将它们提升至作用域顶部( var
声明),要么将声明放到 TDZ
中( let
和 const
声明)。访问 TDZ
中的变量会触发运行时错误。只有执行过变量声明语句后,变量才会从 TDZ
中移出,然后可以正常访问。
第一种情况:
if(condition) { console.log(typeof value); //引用错误! let value = "blue"; //TDZ } 复制代码
第二种情况:
console.log(typeof value); //'undefined'-->只有变量在TDZ中才会报错 if(condition) { let value = "blue"; } 复制代码
三、循坏中块作用域绑定
-
立即调用(IIFE)
-
let
和const
之所以可以在运用在 for-in 和 for-of 循环中,是因为每次迭代会 创建一个新的绑定 (const在 for 循环中会报错)。
四、全局块作用域绑定
var
可能会在无意中覆盖一个已有的全局属性 let
或 const
,会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。换句话说,使用 let
或 const
不能覆盖全局变量,而只能遮蔽它。如果不行为全局对象创建属性,使用 let
和 const
要安全得多。
注:如果希望子安全局对象下定义变量,仍然可以使用var。这种情况常见于在浏览器中跨frame或跨window访问代码。
第二章 字符串和正则表达式
一、UTF-8码位
名词解释:
- 码位: 每一个字符的“全球唯一的标识符,从0开始的数值”
- 字符编码: 表示某个字符的数值或码位即为该字符的字符编码。
-
基本多文种平面(BMP,Basic Multilingual Plane)
在UTF-16中,前2^16个码位均以16位的编码单元表示,这个范围被称作 基本多文种平面
二、codePointAt()、 String.fromCodePoint() 和 normalize()
- 两个方法对应于 charCodeAt() 和 fromCharCode()
- normalize(): 规范的统一,适用于比较排序,国际化。
三、正则表达式 u 和 y 修饰符、正则表达式的复制、flag属性
- u: 编码单元 ---> 字符模式
这个方法尽管有效,但是当统计长字符串中的码位数量时,运动效率很低。因此,你也可以使用字符串迭代器解决效率低的问题,总体而言,只要有可能就尝试着减小码位计算的开销。
检测u修饰符支持:
function hasRegExpU() { try { var pattern = new RegExp('.', 'u'); return true; } catch (ex) { return false; } } 复制代码
- y: 第一次匹配不到就终止匹配
当执行操作时, y修饰符会把上次匹配后面一个字符的索引保存到 lastIndexOf
中;如果该操作匹配的结果为空,则 lastIndexOf
会被重置为0。g修饰符的行为类似。
1. 只有调用exec()和test()这些正则表达式对象的方法时才会涉及lastIndex属性; 2. 调用字符串的方法,例如match(),则不会触发粘滞行为。 复制代码
- 正则表达式的复制
var re1 = /ab/i; re2 = new RegExp(re1); //没有修饰符复制 re3 = new RegExp(re1, "g"); //有修饰符(ES6) 复制代码
- flag属性 --- 获取正则表达式的修饰符
es5方法获取正则表达式的修饰符:
function getFlags(re) { var text = re.toString(); return text.substring(text.lastIndexOf('/' + 1, text.length); } 复制代码
模板字面量
多行字符串
基本的字符串格式化(字符串占位符)
HTML转义
- 标签模板
function passthru(literals, ...substitutions) { //返回一个字符串 let result = ""; //根据substitutions的数量来确定循环的执行次数 for(let i=0; i<substitutions.length; i++){ result += literals; result += substitutions[i] } //合并最后一个literal result += literals[literals.length - 1]; return result; } let count = 10; price = 0.25; message = passthru`${count} items cost $${(count * price).toFixed(2)}`; console.log(message) 复制代码
- String.raw
String.raw`assda\\naadasd` //代码模拟(略) 复制代码
第三章 函数
一、默认参数值
ES5默认参数值
下面函数存在什么问题 ???
function makeRequest(url, timeout, callback) { timeout = timeout || 2000; callback = callback || function() {}; } 复制代码
假如 timeout
传入值0,这个值是合法的,但是也会被视为一个假值,并最终将 timeout
赋值为2000。在这种情况下,更安全的选择是通过 typeof
检查参数类型,如下:
function makeRequest(url, timeout, callback) { timeout = (typeof timeout !== 'undefined') ? timeout :2000; callback = (typeof callback !== 'undefined') ? callback : function() {}; } 复制代码
ES5默认参数值
function makeRequest(url, timeout = 2000, callback) { //函数的其余部分 } //特别注意:此时 null 是一个合法值,所以不会使用 timeout 默认值,即 timeout = null makeRequest('/foo', null, function(body){ doSomething(body); }) 复制代码
二、默认参数值对 arguments
的影响**
- ES5:
非严格模式:参数变化,arguments对象随之改变;
严格模式:无论参数如何变化,arguments对象不再随之改变;
- ES6
非严格模式/严格模式:无论参数如何变化,arguments对象不再随之改变;
注:在引用参数默认值的时候,只允许引用前面参数的值,即先定义的参数不能访问后定义的参数。这可以用默认参数的临时死区来解释。如下:
function add(first = second, second) { return first + second; } console.log(add(1, 1)); //2 console.log(add(undefined, 1)) //抛出错误 //解释原理: //add(1, 1) let first = 1; let second =1; //add(undefined, 1) let first = second; let second = 1; //处于临时死区 复制代码
三、不定参数的使用限制
- 每个函数最多只能声明一个不定参数,而且移动要放在所有参数的末尾。
-
不定参数不能用于对象字面量
setter
之中( 因为对象字面量setter的参数有且只有一个,而在不定参数的定义中,参数的数量可以无限多 )
无论是否使用不定参数,arguments对象总是包含所有传入函数的参数。
四、展开运算符
let value = [25, 50, 75, 100]; //es5 console.log(Math.max.apply(Math, values); //100 //es6 console.log(Math.max(...values)); //100 复制代码
五、name 属性
两个有关函数名称的特例:
bind(1) Function
var doSomething = function() { //空函数 } console.log(doSomething.bind().name); //'bound doSomething' console.log((new Function()).name); //'anonymous' 复制代码
切记:函数name属性的值不一定引用同名变量,它只是协助调试用的额外信息,所以不能使用name属性的值来获取对于函数的引用。
六、明确函数的多重用途
JS函数有两个不同的内部方法: [[call]] 和 [[Construct]] 。
-
当通过new关键字调用函数是,执行的是 [[Construct]]
函数,它负责创建一个通常被称为实例的新对象,然后再执行函数体,将
this
绑定到实例上(具有 [[Construct]] 方法的函数被统称为 构造函数 ,箭头函数没有 [[Construct]] 方法 ); -
如果不通过
new
关键字调用函数,则执行 [[call]] 函数,从而直接执行代码中的函数体;
七、元属性(Metaproperty)new.target
为了解决判断函数是否通过new关键字调用的问题, new.target
横空出世 (instance of ---> new.target)
在函数外使用 new.target
是一个语法错误。
八、块级函数
- ES5严格模式下,代码块中声明函数会报错;
- ES6严格模式下, 可以在定义该函数的代码块中访问和调用它 (块级函数提升,let变量不提升);
- ES6非严格模式下,函数不再提升至代码块的顶部,而是提升至外围函数或全局作用域的顶部。
九、箭头函数
箭头函数与传统的JS函数不同之处主要有一下一个方面:
-
没有
this
、super
、arguments
和new.target
绑定; -
不能通过
new
关键字调用; - 没有原型;
-
不可以改变
this
的绑定; -
不支持
arguments
对象 - 不支持重复的命名参数
创建一个空函数:
let doNothing = () => {}; 复制代码
返回一个对象字面量
let getTempItem = id => ({ id: id, name: "Temp"}); 复制代码
创建立即执行的函数
let person = ((name) => { return { getName: function() { return name; } } })("xszi") console.log(person.getName()); //xszi 复制代码
箭头函数没有 this
绑定
let PageHandler = { id: '123456', init: function() { document.addEventListener("click", function(event){ this.doSomething(event.type); //抛出错误 }, false) }, doSomething: function(type) { console.log("handling " + type + "for" + this.id) } } 复制代码
使用 bind()
方法将函数的 this
绑定到 PageHandler
,修正报错:
let PageHandler = { id: '123456', init: function() { document.addEventListener("click", (function(event){ this.doSomething(event.type); //抛出错误 }).bind(this), false) }, doSomething: function(type) { console.log("handling " + type + "for" + this.id) } } 复制代码
使用 箭头函数 修正:
let PageHandler = { id: '123456', init: function() { document.addEventListener("click", event => this.doSomething(event.type), false); }, doSomething: function(type) { console.log("handling " + type + "for" + this.id) } } 复制代码
-
箭头函数没有
prototype
属性,它的设计初衷是 即用即弃 , 不能用来定义新的类型。 -
箭头函数的中
this
取决于该函数外部非箭头函数的this
值,不能通过call(), apply()
或bind()
方法来改变this
的值。
箭头函数没有 arguments
绑定
始终访问外围函数的 arguments
对象
十、尾调用优化
- ES5中,循环调用情况下,每一个未完成的栈帧都会保存在内存中,当调用栈变的过大时会造成程序问题。
-
ES6中尾调用优化,需要满足一下三个条件:
- 尾调用不访问当前栈帧的变量(也就是说函数不是一个闭包);
- 在函数内部,尾调用是最后一条语句;
- 尾调用的结果作为函数值返回;
如何利用尾调用优化
function factorial(n) { if ( n<=1 ) { return 1; } }else{ //引擎无法自动优化,必须在返回后执行乘法操作 return n * factorial(n-1); //随调用栈尺寸的增大,存在栈溢出的风险 } 复制代码
function factorial(n, p = 1) { if ( n<=1 ) { return 1 * p; } }else{ let result = n * p; //引擎可自动优化 return factorial(n-1, result); //不创建新的栈帧,而是消除并重用当前栈帧 } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 深入理解 JavaScript 函数
- 【4】JavaScript 基础深入——函数、回调函数、IIFE、理解this
- 深入理解 Java 函数式编程,第 5 部分: 深入解析 Monad
- 深入学习javascript函数式编程
- [译] 深入理解 JavaScript 回调函数
- 重读《深入理解ES6》—— 函数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript忍者秘籍
John Resig、Bear Bibeault / 徐涛 / 人民邮电出版社 / 2015-10 / 69.00
JavaScript语言非常重要,相关的技术图书也很多,但没有任何一本书对JavaScript语言的重要部分(函数、闭包和原型)进行深入、全面的介绍,也没有任何一本书讲述跨浏览器代码的编写。本书是jQuery库创始人编写的一本深入剖析JavaScript语言的书。 本书共分四个部分,从准入训练、见习训练、忍者训练和火影训练四个层次讲述了逐步成为JavaScript高手的全过程。全书从高级We......一起来看看 《JavaScript忍者秘籍》 这本书的介绍吧!