[JS] ECMAScript 6 - Variable : compare with c#
前言
范围包括:ECMAScript 新功能以及对象。
当前的主要目的就是,JS的学习 --> ECMAScript 6 入门
let 命令
js
- 因为let, i的范围限制在了循环中。
var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6
- 里外不一样作用域。
for (let i = 0; i < 3; i++) { // 设置循环变量的那部分是一个父作用域 let i = 'abc'; // 循环体内部是一个单独的子作用域 console.log(i); } // abc // abc // abc
- 不存在变量提升。【强制"声明在前"的正常思维】
- 不允许重复声明。
- Temporal Dead Zone【形成了封闭作用域】。
危险区:有些“死区”比较隐蔽,不太容易发现。
(1) x的值y还未声明;
function bar(x = y, y = 2) { // 编译器观察参数是:从前到后的顺序 return [x, y]; } bar(); // [2, 2]
(2) 比较变态的写法。
// 不报错 var x = x; // 报错 let x = x; // ReferenceError: x is not defined
(1) ES5的问题一:
var tmp = new Date(); function f() { console.log(tmp); // tmp本意是上面的,但是:内层变量可能会覆盖外层变量 if (false) { var tmp = 'hello world'; } } f(); // undefined
(2) ES5的问题二:
var s = 'hello';
for (var i = 0; i < s.length; i++) { // 原本变量i
只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
console.log(s[i]);
}
console.log(i); // 5
危险区:块级作用域与函数声明
原本的规定:"函数只能在顶层作用域和函数作用域之中声明";However,根据 ES5 的规定都是非法,但能运行。
// 情况一 if (true) { function f() {} } // 情况二 try { function f() {} } catch(e) { // ... }
(1) When ES5,这里是false,ES5还能执行,这本身就是个bug设计。
function f() { console.log('I am outside!'); } (function () {
if (false) { // 这里是false,还能执行,这本身就是个bug设计。 function f() { console.log('I am inside!'); } // 在if
内声明的函数f
会被提升到函数头部 } f();
} ());
(2) When ES6,三条规则只对 ES6 的浏览器实现有效
-
-
- 允许在块级作用域内声明函数。
- 函数声明类似于
var
,即会提升到全局作用域或函数作用域的头部。 - 同时,函数声明还会提升到所在的块级作用域的头部。
-
实际的运行代码逻辑:在浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var
声明的变量。
// 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。
-
- ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立
如果确实需要,也应该写成函数表达式,而不是函数声明语句。
// 函数声明语句 { let a = 'secret'; function f() { return a; } } // 函数表达式 { let a = 'secret'; let f = function () { return a; }; }
- 立即执行函数表达式(IIFE)失去了必要性
立即执行函数(IIFE,Immediately-invoked function expression):声明一个函数然后立即执行它。
Ref: https://www.cnblogs.com/maczyt/archive/2015/10/25/4908624.html
(function () {
alert("IIFE");
})();
//或者
(function () {
alert("IIFE");
}());
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。
# 因为本身就自带闭包的性质。
// IIFE 写法
(function () {
var tmp = ...;
...
}());
// 块级作用域写法
{
let tmp = ...;
...
}
c#
变量的作用域是可以访问该变量的代码区域。一般情况下,确定作用域有以下规则:
1. 只要类在某个作用域内,其字段也在该作用域内
2. 局部变量存在于声明该变量的块语句或方法结束的封闭花括号之前的作用域内。
3. 在for、while或类似语句中声明的局部变量存在与该循环体内
public static int Main() { int j=20;
for(int i=0; i<10; i++) // <---- 这个可以!
{
Console.writeLie(i);
}
for(int i=0; i<10; i++) { int j=30; //错误 Console.WriteLine(j+i); }
return 0; }
const 命令
js
const
实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。
- 对象添加一个属性可以。【看上去没有冻结,毕竟还是可以在其实地址之后有所改动!】
const foo = {}; // 为 foo 添加一个属性,可以成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only
- 更严格的:完全不能改变!【冻结】
Ref:Object.freeze(), Object.isFrozen()
const foo = Object.freeze({}); // 常规模式时,下面一行不起作用; // 严格模式时,该行会报错 foo.prop = 123;
下面是一个将对象彻底冻结的函数。
var constantize = (obj) => {
Object.freeze(obj); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } } ); };
c#
Ref: C#基础知识七之const和readonly关键字
静态常量:所谓静态常量就是在编译期间会对变量进行解析,再将常量的值替换成初始化的值。【宏,const修饰的常量】
动态常量:所谓动态常量就是编译期间会将变量标记只读常量,而不用常量的值代替,这样在声明时可以不初始化,可以延迟到构造函数初始化。【readonly】
对比:
-
- const修饰的常量在声明时必须初始化值;readonly修饰的常量可以不初始化值,且可以延迟到构造函数。
- cons修饰的常量在编译期间会被解析,并将常量的值替换成初始化的值;而readonly延迟到运行的时候。
- const修饰的常量注重的是效率;readonly修饰的常量注重灵活。
- const修饰的常量没有内存消耗;readonly因为需要保存常量,所以有内存消耗。
- const只能修饰基元类型、枚举类、或者字符串类型;readonly却没有这个限制。
const 前不能再用static,啰嗦。【算常量】
static const int temp = 100; // (PM:如果不编译的话,编辑器不会报错。编译后,再提示错误! )
readonly 可以,且在类中可以延迟到构造函数再初始化。【还是变量】
class Program { static readonly int A = B * 10; static readonly int B = 10; public static void Main(string[] args) { Console.WriteLine("A is {0},B is {1} ", A, B); Console.ReadLine(); } }
Ref: C# const, readonly, static readonly
下面的语句中static readonly和const能否互换了:
1. static readonly MyClass myins = new MyClass();
// 不能换成const。构造函数在编译期间无法确定
2. static readonly MyClass myins = null;
// 能换成const。我们也看到,Reference类型的常量(除了String)只能是Null。
3. static readonly A = B * 20; static readonly B = 10;
// 能换成const。我们能在编译期间非常明确的说,A等于200。
4. static readonly int [] constIntArray = new int[] {1, 2, 3};
// 不能换成const。构造函数在编译期间无法确定
5. void SomeFunction() { const int a = 10; ... }
// 不能换成readonly,readonly只能用来修饰类的field,不能修饰局部变量,也不能修饰property等其他类成员。
顶层对象的属性
js
顶层对象,ES5 之中,顶层对象的属性与全局变量是等价的。
-
- 在 浏览器环境 -
window
对象 【window
对象有实体含义,指的是浏览器的窗口对象;顶层对象是一个有实体含义的对象,也是不合适的】 - 在 Node -
global
对象
- 在 浏览器环境 -
这引来大问题
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。这样的设计带来了几个很大的问题,
-
- 首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);
- 其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);
- 最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。
var a = 1; // 它是顶层对象的属性 // 如果在 Node 的 REPL 环境,可以写成 global.a // 或者采用通用方法,写成 this.a window.a // 1
/**
* 使用了let,全局和window有了区别
*/ let b = 1; // 不是顶层对象的属性 window.b // undefined
global 对象
ES5 的顶层对象,本身也是一个问题,因为它在各种实现里面是不统一的。
-
- 浏览器里面,顶层对象是
window
,但 Node 和 Web Worker 没有window
。【顶层对象指代不一样】 - 浏览器和 Web Worker 里面,
self
也指向顶层对象,但是 Node 没有self
。 - Node 里面,顶层对象是
global
,但其他环境都不支持。【node: global; 浏览器: self】
- 浏览器里面,顶层对象是
Jeff: 既然这么乱,就有提供一个统一的方案:this
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this
变量,但是有局限性。
-
- 全局环境中,
this
会返回顶层对象。但是,Node 模块和 ES6 模块中,this
返回的是当前模块。【this ----> 当前模块】 - 函数里面的
this
,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this
会指向顶层对象。但是,严格模式下,这时this
会返回undefined
。【那就是建议在对象中的方法运行咯】 - 不管是严格模式,还是普通模式,
new Function('return this')()
,总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全政策),那么eval
、new Function
这些方法都可能无法使用。
- 全局环境中,
综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。临时的方案是:
c#
c#中有没有类似的全局变量呢?答案上否定的,在c#中不存在全局变量的概念。
但可以使用静态static关键词达到类似的效果。
Public static class MyMame {
Private static string name = ”yzh”;
Public static string Name {
Get{
Return name;
}
}
}
(完)