大家好,又见面了,我是全栈君。
一、作用域
1、 期骗词法
JavaScript 中有两个机制可以“欺骗”词法作用域:eval(..) 和 with。
前者可以对一段包 含一个或多个声明的“代码”字符串进行演算,并借此来修改已经存在的词法作用域(在 运行时)。
后者本质上是通过将一个对象的引用当作作用域来处理,将对象的属性当作作 用域中的标识符来处理,从而创建了一个新的词法作用域(同样是在运行时)。
1.1 eval()
function foo(str, a) {
eval( str ); // 欺骗!
console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3
复制代码
在严格模式的程序中,eval(..) 在运行时有其自己的词法作用域,意味着其 中的声明无法修改所在的作用域。
function foo(str) {
"use strict";
eval( str );
console.log( a ); // ReferenceError: a is not defined
}
foo( "var a = 2" );
注意:
// "use strict" 的目的是指定代码在严格条件下执行。
// 严格模式下你不能使用未声明的变量。
// 严格模式通过在脚本或函数的头部添加 "use strict";
复制代码
eval()用法
eval函数是用来解析json对象的;它的功能是把对应的字符串解析成JS代码并运行。
语法:
eval("("+jsonObj+")")
复制代码
举个栗子。
function name1(){
console.log('name1')
}
function name2(){
console.log('name2')
}
var m="name1";
eval(m+'()');//运行name1();
m='name2';
eval(m+'()');//运行name2();
复制代码
1.2 with()
JavaScript 中另一个难以掌握(并且现在也不推荐使用)的用来欺骗词法作用域的功能是 with 关键字。
with 通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象 本身。
var obj = {
a: 1,
b: 2,
c: 3
};
// 单调乏味的重复 "obj"
obj.a = 2;
obj.b = 3;
obj.c = 4;
// 简单的快捷方式
with (obj) {
a = 3;
b = 4;
c = 5;
}
复制代码
with()用法
with语句用于设置代码在特定对象中的作用域。换句话说with就是为了封装某个对象,减少某个对象的调用。
语法:
var str="hello";
with(str){
alert(toUpperCase());//输出"HELLO"
}
复制代码
接下来再举个栗子,大家来思考一下结果。
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = { a: 3 };
var o2 = { b: 3 };
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a );
// 2——不好,a 被泄漏到全局作用域上了!
复制代码
到这里大家有什么疑问的吗???
如有不懂请看下面的解释
回顾一下上面的问题,实际上 a = 2 赋值操作创建了一个全局的变量 a。这是怎么回事?
简单来讲,with 可以将一个没有或有多个属性的对象处理为一个完全隔离的词法作用域,因此这个对 象的属性也会被处理为定义在这个作用域中的词法标识符。
eval(..) 函数如果接受了含有一个或多个声明的代码,就会修改其所处的词法作用域,而 with 声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。
好了到这里大家明白了吧 嘿嘿!
2、块作用域
说到块作用域大家一定很困扰吧,在es6入门时,var与let带来的作用域让我们的头都大了对吧!!!下面的会很有趣 ^_^
2.1 let和var
ES6 引入了新的 let 关键字,提供了除 var 以外的另一种变量声明方式。 let 关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部)。
代码分析:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
复制代码
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
复制代码
以上两段代码区别在于var与let使用的作用域不同。
1.var 变量的作用域是全局,而let 是局部的块作用域即for循环内。
2.明确循环内部的i与console.log(i)中的i是在不同的作用域中,它们有各自单独的作用域。
3.全局变量唯一性,var 声明的变量i在循环中被不断覆盖最终只是唯一的10,因此在外部调用中无论调用a数组的哪一个,最终都是10。
4.局部使用 let 定义时只在该函数作用域内部有效。
举个栗子细磨一下:
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i);
// ReferenceError: i is not defined
复制代码
2.2 const
除了 let和var 以外,ES6 还引入了 const,同样可以用来创建块作用域变量,但其值是固定的 (常量)。换句话说:const声明一个只读的常量。一旦声明,常量的值就不能改变。
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
代码分析:
var foo = true;
if (foo) {
var a = 2;
const b = 3; // 包含在 if 中的块作用域常量
a = 3; // 正常 !
b = 4; // 错误 !
}
console.log( a ); // 3
console.log( b ); // ReferenceError!
复制代码
const foo;
// SyntaxError: Missing initializer in const declaration
上面代码表示,对于const来说,只声明不赋值,就会报错。
复制代码
const 常量为变量时结果。。。。
const foo = {
x:0,
y:1
}
foo.x = 2
console.log(foo.x)
复制代码
大家思考一下第三个结果是多少? 打印结果为0?还是2呢?
先总结一下const:
1.只在块级作用域起作用,和let关键字一样
2.不存在变量提升,但必须提前声明,和let一样
3.不可重复声明同一个变量
4.声明后要赋值,没有赋值会报错
5.思考一下const定义的常量是变量呢????
公布一下答案foo.x为2 是不是大家有点蒙了,不是说const定义的常量不能改变吗,而此时却改变且未报错!我解释一下
有一个概念:在赋值过程中,我们可以分为传值赋值和传址赋值。
这里我们用到了传址赋值,什么叫传址赋值?
传址:在赋值过程中,变量实际上存储的是数据的地址(对数据的引用),而不是原始数据或者数据的拷贝
举个栗子
var obj= {
"name": '张三'
}
var obj1 = obj
obj1.name='李四'
console.log(obj) //李四
console.log(obj1) // 李四
复制代码
上面的obj1,obj2都变为李四就是传址赋值
说到这我想到了一个问题关于const关键字,我们常常说const定义常量。其实在es6中,const代表一个值 的常量索引。换句话说,变量名字在内存中的指针不能够改变,但是指向这个变量的值可以改变。
二、闭包
1、闭包是什么?
闭包就是外层函数的内部函数(不过要注意它的特性)。
1.1特性:
1.它有自己的局部作用域(local scope);
2.它可以访问外部函数的作用域(outer scope),参数(parameters),而不是参数对象;
3.它也可以访问全局的(global scope)
4.参数和变量不会被垃圾回收机制回收(不当的使用闭包可能造成内存泄漏的原因)
2、闭包工作原理
1.闭包存储外部函数变量的引用,因此总是可以访问外部变量的更新值
2.在它的外部函数被执行并返回值后,闭包仍然可以执行(常驻内存)
3、闭包的好处
1.保存状态(使一个变量长期驻扎在内存中)
2.避免全局变量的污染
3.允许私有成员的存在
4、如何使用闭包
大家看一下下面的代码是闭包吗?
function foo() {
var a = 2;
function bar() {
console.log( a ); // 2
}
bar();
}
foo();
复制代码
上面的代码是闭包吗??函数bar()可以访问外部作用域的变量a 但不是闭包,现在大家是不是有点好奇了
下面我们再看一段代码:
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz();
// 2 —— 朋友,这就是闭包的效果。
// 函数 bar() 的词法作用域能够访问 foo() 的内部作用域。然后我们将 bar() 函数本身当作 一个值类型进行传递。在这个例子中,我们将 bar 所引用的函数对象本身当作返回值。
// 在 foo() 执行后,其返回值(也就是内部的 bar() 函数)赋值给变量 baz 并调用 baz(),实 际上只是通过不同的标识符引用调用了内部的函数 bar()。
// bar() 显然可以被正常执行。但是在这个例子中,它在自己定义的词法作用域以外的地方 执行。
复制代码
而闭包的“神奇”之处正是可以阻止foo()执行后被销毁的发生。事实上内部作用域依然存在,没有被回收。是 bar() 本身在使用这个内部作用域。
拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一 直存活,以供 bar() 在之后任何时间进行引用。
bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。
闭包比较关键的地方在于函数A执行完毕后,函数里的变量或参数并没有被回收而被其他函数B(常见的情况就是B在A内声明或定义)引用着。
第一个:foo执行完后,它没有返回函数,它的外面也没有其他函数引用着它的变量,它的变量被回收,所以不是闭包。
简单来讲, foo执行过程中,bar被执行,虽然它引用了 foo 中的 a,但在 foo 执行完之前, bar也已经执行完了,所以整个过程执行完以后,所有局部变量都沒有被当前存在的其它变量(对象)引用,已经被系统销毁了。
第二个:foo执行完后,它返回的函数(也就是bar)还引用着它的变量a,所以是闭包。
结语
当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时 就产生了闭包。闭包内容还有很多,下次再做分享。
今天先到这,有疑问可以留言我会一一解答。
以上如有不足,请大家多多指点!!
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/107679.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...