TypeScript语法学习--变量的声明

JavaScript里相对较新的变量声明方式是let和const.
let在很多方面与var是相似的,但是可以帮助大家避免在JavaScript里常见一些问题。 const是对let的一个增强,它能阻止对一个变量再次赋值。
TypeScript是JavaScript的超集,所以它本身就支持let和const。
var 声明
一直以来我们都是通过var关键字定义JavaScript变量。

var a = 10;//定义了一个名为a值为10的变量
也可以在函数内部定义变量:

function f() {
var message = "Hello, world!";

return message;
}
且我们也可以在其它函数内部访问相同的变量。

function f() {
var a = 10;
return function g() {
var b = a + 1;
return b;
}
}

var g = f();
g(); // returns 11;
作用域规则
栗子:
function f(shouldInitialize: boolean) {
if (shouldInitialize) {
var x = 10;
}

return x;
}

f(true); // returns ‘10‘
f(false); // returns ‘undefined‘
变量 x是定义在*if语句里面*,但是却可以在语句的外面访问它。
因为 var声明可以在包含它的函数,
模块,命名空间或全局作用域内部任何位置被访问,包含它的代码块对此没有什么影响。
有些人称此为* var作用域或函数作用域*。 函数参数也使用函数作用域。
这作用域规则可能会引发一些错误。 其中之一就是,多次声明同一个变量并不会报错:
for(var i=0;i<2;i++){
for(var i=9;i<courrent.length;i++){
//....里层的for循环会覆盖变量i,因为i值都引用相同的函数作用域内的变量
}
}

捕捉变量怪异之处
for (var i = 0; i < 5; i++) {
setTimeout(function () { console.log(i); }, 100 * i);
//setTimeout会在若干毫秒的延时后执行一个函数(等待其它代码执行完毕)。
}
//打印出来的结果是5 5 5 5 5
//大多数人期待的结果是 0 1 2 3 4 5
问题在于:传给setTimeout的每一个函数表达式实际上都引用了相同作用域里的同一个i。
setTimeout在若干毫秒后执行一个函数,并且是在for循环结束后。 for循环结束后,i的值为5。 所以当函数被调用的时候,它会打印出 5!

一个通常的解决方法是使用立即执行的函数表达式(IIFE)来捕获每次迭代时i的值:

for (var i = 0; i < 6; i++) {
// capture the current state of ‘i‘
// by invoking a function with its current value
(function(i) {
setTimeout(function() { console.log(i); }, 100 * i);
})(i);
}
这种奇怪的形式我们已经司空见惯了。 参数 i会覆盖for循环里的i,但是因为我们起了同样的名字,所以我们不用怎么改for循环体里的代码。
let 声明
var存在一些问题,这恰好说明了为什么用let语句来声明变量。 除名字不同, let与var的写法一致。
let hello = "Hello!";
主要的区别不在语法上,而是语义
当用let声明一个变量,它使用的是词法作用域或块作用域。 不同于使用 var声明的变量那样可以在包含它们的函数外访问,块作用域变量在包含它们的块或for循环之外是不能访问的。

function f(input: boolean) {
let a = 100;

if (input) {
// Still okay to reference ‘a‘
let b = a + 1;
return b;
}

// Error: ‘b‘ doesn‘t exist here
return b;
}
a的作用域是f函数体内,而b的作用域是if语句块里。

在catch语句里声明的变量也具有同样的作用域规则。

try {
throw "oh no!";
}
catch (e) {
console.log("Oh well.");
}

// Error: ‘e‘ doesn‘t exist here
console.log(e);
拥有块级作用域的变量的另一个特点是,它们不能在被声明之前读或写。 虽然这些变量始终“存在”于它们的作用域里,但在直到声明它的代码之前的区域都属于 暂时性死区。 它只是用来说明我们不能在 let语句之前访问它们
注意一点,我们仍然可以在一个拥有块作用域变量被声明前获取它。 只是我们不能在变量声明前去调用那个函数。 如果生成代码目标为ES2015,现代的运行时会抛出一个错误;然而,现今TypeScript是不会报错的。

function foo() {
// okay to capture ‘a‘
return a;
}

// 不能在‘a‘被声明前调用‘foo‘
// 运行时应该抛出错误
foo();

let a;

重定义及屏蔽
我们提过使用var声明时,它不在乎你声明多少次;你只会得到1个。

function f(x) {
var x;
var x;

if (true) {
var x;
}
}
在上面的例子里,所有x的声明实际上都引用一个相同的x,并且这是完全有效的代码。 这经常会成为bug的来源。 好的是, let声明就不会这么宽松了。

let x = 10;
let x = 20; // 错误,不能在1个作用域里多次声明`x`
并不是要求两个均是块级作用域的声明TypeScript才会给出一个错误的警告。

function f(x) {
let x = 100; // error: interferes with parameter declaration
}

function g() {
let x = 100;
var x = 100; // error: can‘t have both declarations of ‘x‘
}
并不是说块级作用域变量不能用函数作用域变量来声明。 而是块级作用域变量需要在明显不同的块里声明。

function f(condition, x) {
if (condition) {
let x = 100;
return x;
}

return x;
}

f(false, 0); // returns 0
f(true, 0); // returns 100
在一个嵌套作用域里引入一个新名字的行为称做屏蔽。 它是一把双刃剑,它可能会不小心地引入新问题,同时也可能会解决一些错误。 例如,假设我们现在用 let重写之前的sumMatrix函数。

function sumMatrix(matrix: number[][]) {
let sum = 0;
for (let i = 0; i < matrix.length; i++) {
var currentRow = matrix[i];
for (let i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
}

return sum;
}
这个版本的循环能得到正确的结果,因为内层循环的i可以屏蔽掉外层循环的i。

通常来讲应该避免使用屏蔽,因为我们需要写出清晰的代码。 同时也有些场景适合利用它,你需要好好打算一下。

块级作用域变量的获取

在我们最初谈及获取用var声明的变量时,我们简略地探究了一下在获取到了变量之后它的行为是怎样的。 直观地讲,每次进入一个作用域时,它创建了一个变量的 环境。 就算作用域内代码已经执行完毕,这个环境与其捕获的变量依然存在。

function theCityThatAlwaysSleeps() {
let getCity;

if (true) {
let city = "Seattle";
getCity = function() {
return city;
}
}

return getCity();
}
因为我们已经在city的环境里获取到了city,所以就算if语句执行结束后我们仍然可以访问它。

回想一下前面setTimeout的例子,我们最后需要使用立即执行的函数表达式来获取每次for循环迭代里的状态。 实际上,我们做的是为获取到的变量创建了一个新的变量环境。 这样做挺痛苦的,但是幸运的是,你不必在TypeScript里这样做了。

当let声明出现在循环体里时拥有完全不同的行为。 不仅是在循环里引入了一个新的变量环境,而是针对 每次迭代都会创建这样一个新作用域。 这就是我们在使用立即执行的函数表达式时做的事,所以在 setTimeout例子里我们仅使用let声明就可以了。

for (let i = 0; i < 6 ; i++) {
setTimeout(function() {console.log(i); }, 100 * i);
}
会输出与预料一致的结果:0 1 2 3 4 5

const 声明
const 声明是声明变量的另一种方式。

const numLivesForCat = 9;
它们与let声明相似,但是就像它的名字所表达的,它们被赋值后不能再改变。 换句话说,它们拥有与 let相同的作用域规则,但是不能对它们重新赋值。

这很好理解,它们引用的值是不可变的。

const numLivesForCat = 9;
const kitty = {
name: "Aurora",
numLives: numLivesForCat,
}

// Error
kitty = {
name: "Danielle",
numLives: numLivesForCat
};

// all "okay"
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;
除非你使用特殊的方法去避免,实际上const变量的内部状态是可修改的。 幸运的是,TypeScript允许你将对象的成员设置成只读的。 接口一章有详细说明。

let vs. const
现在我们有两种作用域相似的声明方式,我们自然会问到底应该使用哪个。 与大多数泛泛的问题一样,答案是:依情况而定。

使用最小特权原则,所有变量除了你计划去修改的都应该使用const。 基本原则就是如果一个变量不需要对它写入,那么其它使用这些代码的人也不能够写入它们,并且要思考为什么会需要对这些变量重新赋值。 使用 const也可以让我们更容易的推测数据的流动。

跟据你的自己判断,如果合适的话,与团队成员商议一下。

解构数组
最简单的解构莫过于数组的解构赋值了:

let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
这创建了2个命名变量 first 和 second。 相当于使用了索引,但更为方便:

first = input[0];
second = input[1];
解构作用于已声明的变量会更好:

// swap variables
[first, second] = [second, first];
作用于函数参数:

function f([first, second]: [number, number]) {
console.log(first);
console.log(second);
}
f(input);
你可以在数组里使用...语法创建剩余变量:

let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]
当然,由于是JavaScript, 你可以忽略你不关心的尾随元素:

let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1
或其它元素:

let [, second, , fourth] = [1, 2, 3, 4];
对象解构
你也可以解构对象:

let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, b } = o;
这通过 o.a and o.b 创建了 a 和 b 。 注意,如果你不需要 c 你可以忽略它。

就像数组解构,你可以用没有声明的赋值:

({ a, b } = { a: "baz", b: 101 });
注意,我们需要用括号将它括起来,因为Javascript通常会将以 { 起始的语句解析为一个块。

你可以在对象里使用...语法创建剩余变量:

let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;

属性重命名
你也可以给属性以不同的名字:

let { a: newName1, b: newName2 } = o;
这里的语法开始变得混乱。 你可以将 a: newName1 读做 "a 作为 newName1"。 方向是从左到右,好像你写成了以下样子:

let newName1 = o.a;
let newName2 = o.b;
令人困惑的是,这里的冒号不是指示类型的。 如果你想指定它的类型, 仍然需要在其后写上完整的模式。

let {a, b}: {a: string, b: number} = o;
默认值
默认值可以让你在属性为 undefined 时使用缺省值:

function keepWholeObject(wholeObject: { a: string, b?: number }) {
let { a, b = 1001 } = wholeObject;
}
现在,即使 b 为 undefined , keepWholeObject 函数的变量 wholeObject 的属性 a 和 b 都会有值。

函数声明
解构也能用于函数声明。 看以下简单的情况:

type C = { a: string, b?: number }
function f({ a, b }: C): void {
// ...
}
但是,通常情况下更多的是指定默认值,解构默认值有些棘手。 首先,你需要在默认值之前设置其格式。

function f({ a="", b=0 } = {}): void {
// ...
}
f();
上面的代码是一个类型推断的例子,将在本手册后文介绍。

其次,你需要知道在解构属性上给予一个默认或可选的属性用来替换主初始化列表。 要知道 C 的定义有一个 b 可选属性:

function f({ a, b = 0 } = { a: "" }): void {
// ...
}
f({ a: "yes" }); // ok, default b = 0
f(); // ok, default to {a: ""}, which then defaults b = 0
f({}); // error, ‘a‘ is required if you supply an argument
要小心使用解构。 从前面的例子可以看出,就算是最简单的解构表达式也是难以理解的。
尤其当存在深层嵌套解构的时候,就算这时没有堆叠在一起的重命名,默认值和类型注解,也是令人难以理解的。
解构表达式要尽量保持小而简单。 你自己也可以直接使用解构将会生成的赋值表达式。
展开
展开操作符正与解构相反。 它允许你将一个数组展开为另一个数组,或将一个对象展开为另一个对象。 例如:

let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];
这会令bothPlus的值为[0, 1, 2, 3, 4, 5]。 展开操作创建了 first和second的一份浅拷贝。 它们不会被展开操作所改变。

你还可以展开对象:

let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" };
search的值为{ food: "rich", price: "$$", ambiance: "noisy" }。 对象的展开比数组的展开要复杂的多。 像数组展开一样,它是从左至右进行处理,但结果仍为对象。 这就意味着出现在展开对象后面的属性会覆盖前面的属性。 因此,如果我们修改上面的例子,在结尾处进行展开的话:

let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { food: "rich", ...defaults };
那么,defaults里的food属性会重写food: "rich",在这里这并不是我们想要的结果。

对象展开还有其它一些意想不到的限制。 首先,它仅包含对象 自身的可枚举属性。 大体上是说当你展开一个对象实例时,你会丢失其方法:

class C {
p = 12;
m() {
}
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!
其次,TypeScript编译器不允许展开泛型函数上的类型参数。 这个特性会在TypeScript的未来版本中考虑实现。

原文地址:https://www.cnblogs.com/allyh/p/10434680.html

时间: 2024-08-02 01:48:01

TypeScript语法学习--变量的声明的相关文章

go语言基本语法:变量的声明

一.变量的使用 1.1 什么是变量 变量是为存储特定类型的值而提供给内存位置的名称.在go中声明变量有多种语法. 所以变量的本质就是一小块内存,用于存储数据,在程序运行过程中数值可以改变 1.2 声明变量 var名称类型是声明单个变量的语法. 以字母或下划线开头,由一个或多个字母.数字.下划线组成 声明一个变量 第一种,指定变量类型,声明后若不赋值,使用默认值 var name type name = value 第二种,根据值自行判定变量类型(类型推断Type inference) 如果一个变

java基本语法day01_09变量的声明与使用

概念: 内存中的一个存储区域. 该区域拥有自己的名称(变量名)和类型(数据类型). java是强类型语言,java中每个变量必须先声明后使用. 该区域的数据可以在同一类型范围内不断变化. 注意: 变量的作用域:在一对{}之间有效. 初始化值 定义变量的格式:数据类型 变量名 = 初始化值 变量是通过变量名来访问这块区域的. 变量按作用域(被声明的位置)进行分类: 1.成员变量:在方法的外部.类的内部定义的变量(也叫全局变量).在一个类的任意地方都可以被访问. 局部变量:在方法或语句块的内部定义的

Typescript 开发3. 变量的声明

变量使用前必须先声明 1. var [变量名] : [类型]; 声明变量的类型,但没有初始值,变量值会设置为 undefined var uname:string; 2. var [变量名] : [类型] = 值; var uname:string = "Runoob"; 3. var [变量名]; 声明变量没有设置类型和初始值,默认初始值为 undefined,变量可以是任意类型 4. var [变量名] = 值; 声明变量并初始值,但不设置类型类型,该变量可以是任意类型 原文地址:

TypeScript基础学习 —— 变量声明

var.let.const 一.var 1.声明  一直以来我们都是通过var关键字定义JavaScript变量. var a = 10;   我们可以在其他函数内部访问相同的变量 function f() { var a = 10; return function g() { var b = a + 1; return b; } } var g = f(); g(); // returns 11; g可以获取到f函数里定义的a变量. 每当g被调用时,它都可以访问到f里的a变量. 即使当g在f已

TypeScript语法学习--基本类型

查看官方文档手册:链接:https://www.tslang.cn/docs/home.html (一)Boolean 最基本的数据类型就是简单的true/false值 The most basic datatype is the simple true/false value, which JavaScript and TypeScript call a boolean value. ex: let isDone: boolean = false; var isDone:boolean; //

今天第一次接触到typescript,看了第一个知识点就是变量的声明,来回忆回忆,做做笔记

以前只用过JavaScript原生写网站特效,今天还是第一次听说typescript的,然后看了一下它的基本知识,感觉很像Java,真的太像了,但是又有不同点.很让我惊奇看到的第一个知识点就和以前不同,很新鲜. 变量的声明: 在typescript中我知道的变量的声明有两种(除了var),分别是let和const.以前我经常用的是var,let和var很相似,const和他们不一样,让我惊讶的是它居然能阻止对变量的再次赋值. 因为typescript是JavaScript的超集,所以JavaSc

javascript学习笔记---ECMAScript语法(变量)

变量声明关键字var: var i = 1: var t = "asd"; var test1 = "hi", test2 = "hello"; 声明变量不一定要初始化, var i;//ok 另一方面在使用变量前若未加关键字var,则此变量为全局变量(此特性需特别记住). 变量名字: 变量名需要遵守两条简单的规则: 第一个字符必须是字母.下划线(_)或美元符号($) 余下的字符可以是下划线.美元符号或任何字母或数字字符 命名变量规则: Came

PHP 学习笔记(一):基础教程:语法,变量,函数,数组,超全局

PHP简介 PHP 脚本在服务器上执行. 什么是 PHP 文件 PHP 文件能够包含文本.HTML.CSS 以及 PHP 代码 PHP 代码在服务器上执行,而结果以纯文本返回浏览器 PHP 文件的后缀是 ".php" PHP能够做什么 PHP 能够生成动态页面内容 PHP 能够创建.打开.读取.写入.删除以及关闭服务器上的文件 PHP 能够接收表单数据 PHP 能够发送并取回 cookies PHP 能够添加.删除.修改数据库中的数据 PHP 能够限制用户访问网站中的某些页面 PHP

JAVA学习(三):Java基础语法(变量、常量、数据类型、运算符与数据类型转换)

Java基础语法(变量.常量.数据类型.运算符与数据类型转换) 1.变量 Java中,用户可以通过指定数据类型和标识符来声明变量,其基本语法为: DataType identifier; 或 DataType identifier = value; 其中,DataType是变量类型,如int/string/char/double/boolean等:identifier是变量名称,即标识符:value就是声明变量的值. 注: a.标识符由数字0-9.大小写字母.下划线.美元符号.人民币符号以及所有