序
JS中除了变量提升、函数提升外,作用域链延长也是JS提升机制(Hoisting)的一部分。
一.变量提升、函数提升
变量提升的意义:
JS拿到一段代码或一个函数的时候,会有两步主要操作即
解析与执行,在解析阶段,JS会做语法检查和预编译。函数代码有错误时,函数执行前会抛出SyntaxError
- 声明提升可以提高性能,解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
- 容错性更好,在发布之后很长时间内都没有为程序员提供编译器、调试器、语法检查器等工具。
防止变量提升:
1.在严格模式中,为未声明的标识符赋值将会抛引用错误,因此可以防止意外的全局变量属性的创造。 2.ES6针对这个进行了改变,加入了let/const 后,变量Hoisting就不存在了。
这里研究的目的是更全面的理解js的这些机制。
1.用var关键字声明和不用关键字声明
区别:
- 1.无var在解析过程中提升,提前引用会抛“is not defined”
- 2.有var声明的变量仅提升声明过程的前两步,提前引用会抛“undefined”
变量声明过程:
// var 声明的「创建、初始化和赋值」过程
if (true) {
// 只是【创建(就不再是is not defined)、初始化为undefined】两步提升了,但是并没有赋值过程,所以是下面的输出结果
console.log(x, y) // undefined,undefined
var x = 1
var y = 2
}
// case2
if (true) {
// 解析前引用必然是没有声明创建
console.log(x, y) // Uncaught ReferenceError: y is not defined
var x = 1
y = 2
}
代码块和作用域是两个不同阶段的概念, 描述代码时使用{}括起来的代码被称为代码块,js执行时作用域是js的一个概念,js执行时限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。
变量提升和作用域、作用域链的关系:
function test() {
// 不用var关键字声明a,则a会在test作用域中查找,这里查不到则会在全局中创建
a = 1;
var b; // 如果是这样写 this.b = '';就是test的属性,b只是test函数的一个变量
console.log(a); // 1
}
test();
console.log(a); // 使用全局中创建的变量
作用域的作用
- var声明会提升到作用域顶端,局部的变量不会变为全局的属性(
《javascript高级程序设计》时(page194)详细说明全局变量和全局属性的区别,全局属性是可以delete掉的); - 不用var声明会在当前
作用域链中按顺序解析a,如果在当前作用域链没有发现声明a,则会在全局中创建属性并赋值; 如果在当前作用域链中发现声明则会执行赋值。 - 作用域顶端是一个集合,这样就解决了顺序问题。
作用域链的作用
- 保证执行环境有序的访问到所有变量和函数。
2.函数提升
声明方式
//函数声明式
function bar () {}
//函数字面量式
var foo = function () {}
函数提升机制
// 代码顺序
console.log(bar);
function bar () {
console.log(1);
// 执行顺序是这样的
function bar () {
console.log(1);
}
console.log(bar);
// 函数提升是整个代码块提升到它所在的作用域的最开始执行
- 函数声明式,js的机制会将函数提升到作用域顶端
- 采用字面量式声明函数就和函数提升没有关系了,这里的函数仅是变量的值。
测试题
测试题1
var x = 1, y = 2; // 全局变量
// z也是全局变量
var z = function () {
var x = 2;
return {
x: x, // 值类型,值为2就不会变了
y: function (a, b) {
// x为 “var x = 2;” a.y(x, y);执行完后 x就变成3
x = a + b;
},
z: function () {
return x; // 这里的x也是 “var x = 2;”
}
}
};
// 最外层没有能形成作用域的代码块,到此为止xyz都是window的全局变量
// a的声明方式会变成全局属性,值为z函数执行后返回结果
a = z();
a.y(x, y); // 执行后x为3
console.log(a.z(), a.x, x); // 3,2,1
测试题2
foo(); //高版本: Uncaught TypeError: foo is not a function
var a = true; // a这个变量提升没问题
if(a){ // 有代码块逻辑,下面是先执行foo(); 再执行这个判断
function foo () { console.log(1); }
}else{
function foo () { console.log(2); }
}
// 所以foo执行前都没有声明foo
// 低版本:2 ,这个就没必要研究了,不合理的存在
这意味着无论作用域中的var声明出现在什么地方,都将在代码本身被执行前首先进行处理,可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升。
测试题3
for(var i=0;i<5;i++){
var a=1;
console.log(i);
}
console.log(a, i); //在for循环外也是可以访问到a和i的
块级作用域
示例
if(true) {var a = '666'} console.log(a); // 666
if(true) {let a = '666'} console.log(a); // Uncaught ReferenceError: a is not defined
- ES6之前JS是没有块级作用域的。这意味着在块语句中定义的变量,实际是在函数中创建的,而不是语句中。 函数可以模仿块级作用域。
- ES6通过新增命令let和const来实现。
- 可以使用
自执行函数来模拟块级作用域
二.延长作用域链
-
执行环境也叫做上下文 context ,在全局中也就是 window对象,在函数中执行环境就是函数体
-
执行环境的类型只有两种,全局和局部(函数,块级作用域)。但是有些语句可以在
作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。
两种延长作用域链的方法
- try...catch...
- with
1.try - catch 语句的
catch块会创建一个新的变量对象,包含的是被抛出的错误对象的声明。 编译器遇到catch语句块(catch可以看做一个处理异常错误的函数)会提升到作用域链的前端。
2.with 语句会将指定的
对象的属性添加到作用域链中。
作用说明
1.延长作用域链的意义是对作用域链概念的一种特殊说明。是提升机制完整阐述的重要部分。
2.延长作用域链在实际开发中也很有用处。
3.注意不要频繁的使用with, 会造成很多无用的变量释放,浏览器需要频繁的解析(语法检查和预编译)影响性能。
// 释放多个对象的属性需要以此嵌套
with(location){ with(screen){ console.log(href, availWidth)}}
4.虽然很多人不提倡使用with, 个人觉得大可不必排斥它,如果with能让你感觉到爽就可以使用。
vue很重要的render函数就是使用with实现的,为什么要搞成字符串呢? 因为严格模式下不允许使用with。
function generate (
ast,
options
) {
var state = new CodegenState(options);
var code = ast ? genElement(ast, state) : '_c("div")';
return {
render: ("with(this){return " + code + "}"),
staticRenderFns: state.staticRenderFns
}
}
测试题
测试题1
function buildUrl() {
var qs = "?debug=true";
var href = 'charlesyu01'
with (location){ // 你可以不用,但不能不知道; with只在声明中的代码块中有效
var url = href // 这会让你的代码变得简洁很多;
}
console.log(url) // url本提升了; url的值既不是undefined也不是charlesyu01,而是location.href值
}
buildUrl();
- 用with声明的代码块,上面的参数对象释放的属性
仅在with声明中有效。 - 和块级作用域防止变量提升不同,with是为了更简洁的访问外部数据。
- with每次只能释放一个对象的属性
测试题2
为何try...catch可以在catch中捕获到异常信息呢,js是怎么实现的呢
try..catch只是处理语句,异常是解析器和执行中通过throw抛出的一个exception,然后交由try... 语句处理
// js的常见异常类型
EvalError、RangeError、ReferenceError、SyntaxError、 TypeError、URIError
// 参考官网https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
// MZ基金会可以说是JS语言的官网,任何与js相关的定义和描述都可以在这里找到
// 而W3C是一个民间组织,没有约束性,因此只提供建议;
// chrome特有的一些API或实现方法 也可以去chrome官网查阅资料