写在前面

SAST2020前端组授课——JavaScript基础学习教程系列

变量声明

命名规则

JS中的变量是弱类型可以保存所有类型的数据,即变量没有类型而值有类型。变量名以字母、$、_ 开始,后跟字母、数字、_。(区分大小写,避免使用关键字和保留字!)

驼峰式命名规则:第一个字母小写,之后每个单词首字母大写。

动态弱类型语言

别的编程语言会对变量的类型有严格的限制,之间的转换也有规定。你开始定义一个变量,是整型它就只能是整型,是字符串它就必须是字符串。而JS就不同了。变量声明的时候不用规定是什么类型的,用的时候它自己根据你赋的值判断。

参考资料:https://blog.csdn.net/xo3ylAF9kGs/article/details/102812991

image-20201021205355409

可以思考一下:动态VS静态,强VS弱,之间是什么关系?

类型检查确保一个表达式中的变量类型是合法的。在静态类型语言中,类型检查发生在编译阶段;动态类型语言,类型检查发生在运行阶段。

强类型语言有更强的类型检查机制,表达式计算中会做严格的类型检查;而弱类型语言允许各种变量类型间做一些运算。

下面都是合法的命名:

1
2
let name = 'sast';
let $_hello='sast.com'

JS语言关键字不能用来做变量名,比如 true、if、while、class 等。

1
let class = 'sast';

奇怪的JS

下面是JS这种动态弱类型语言的有趣之处:

1
2
3
4
5
6
7
8
9
10
console.log(0 == "0");
console.log(0 === "0");
console.log(0 == []);
console.log("0" == []);
var x;
console.log(x);
var y = null;
console.log(y);
console.log(x == y);
console.log(x === y);

运算符

一元运算符 ++ – !
算术运算符 * / % + -
关系运算符 >= <= > <
相等运算符 == != === !==
逻辑运算符 && ||
赋值运算符 =

三目运算符:表达式1?语句1:语句2

运算符 说明
== 强制类型转换比较
=== 不强制类型转换比较

声明方式

可以使用多种方式定义变量比如var、let等(后面作用域会再讨论变量)。

1
let name = 'sast';

以上代码是声明和赋值的结合

1
2
let name;
name = 'sast';

使用, 可以同时声明多个变量

1
2
let n = 2,f = 3;
console.log(f);

下面演示了变量可以更换不同类型的数据

1
2
3
4
5
let sast = 'sast.com';
console.log(typeof sast);

sast = 114514;
console.log(typeof sast);

ES6中,新增了常量的定义方法:

1
const name = 'SAST';

数据类型

类似C++,JavaScript有着类似的数据类型。

基本数据类型

Number(NaN)、String、Boolean

数值强制转换。

String:转义字符(当成一个字符长度)

转义字符 含义
\n 换行
\t 制表
\b 退格
\\ \
" "
\u03b1,\u03b2,\u03b3,\u03b4,\u03b5 α,β,γ,δ,ε

Undefined、Null

undefined:未定义

null:空对象

1
2
console.log(typeof null);
>> Object

引用数据类型

Object:对象——对象是包括属性与方法的数据类型。

1
2
3
4
5
6
7
8
9
var web = "sast.njupt.edu.cn";
console.log(typeof web);
//string
web = 114514;
console.log(typeof web);
//number
web = {};
console.log(typeof web);
//object

在W3School中,JavaScript对象有着这样一句定义:

在 JavaScript 中,对象是王。如果您理解了对象,就理解了 JavaScript。

Javascript是一个典型的面向对象变成语言(OOPL)

什么是面向对象OOP?

  • 由过程到对象
  • 由具体到抽象
  • 由高耦合到低耦合

四大基本特性:封装、抽象、继承、多态

  • 对象是属性和方法的集合即封装
  • 将复杂功能隐藏在内部,只开放给外部少量方法,更改对象内部的复杂逻辑不会对外部调用造成影响即抽象
  • 继承是通过代码复用减少冗余代码
  • 根据不同形态的对象产生不同结果即多态

在 JavaScript 中,几乎“所有事物”都是对象。

  • 布尔是对象(如果用 new 关键词定义)
  • 数字是对象(如果用 new 关键词定义)
  • 字符串是对象(如果用 new 关键词定义)
  • 日期永远都是对象
  • 算术永远都是对象
  • 正则表达式永远都是对象
  • 数组永远都是对象
  • 函数永远都是对象
  • 对象永远都是对象

所有 JavaScript 值,除了原始数据类型的值,都是对象。

image-20201114170755249

变量提升

解析器会先解析代码,然后把声明的变量的声明提升到最前,这就叫做变量提升。

下面代码在解析过程中发现while不能做为变量名,没有到执行环节就出错了,这是一个很好的解析过程的体验。

1
2
3
var web = 'sast.njupt.edu.cn';
console.log(web);
let while = 'sast'; //Uncaught SyntaxError: Unexpected token 'while'

使用 var 声明代码会被提升到前面

1
2
3
4
5
6
7
8
9
console.log(a); //undefined
var a = 1;
console.log(a); //1

//以上代码解析器执行过程如下
var a;
console.log(a); //1
a = 1;
console.log(a); //1

下面是 if() 中定义的var也会发生变量提升,注释掉if 结果会不同

1
2
3
4
5
6
7
8
var web = "sast.njupt.edu.cn";
function isOk() {
if ("0" == 0) {
var web = "njupt.edu.cn";
}
console.log(web);
}
isOk();

变量提升:提升的是变量的定义;

函数提升:提升的是函数的定义;

提升指的是函数及变量的声明都将被提升到函数的最顶部,且函数优先级大于变量。

而且变量的赋值不会提升。

使用 var 定义的代码,声明会被提升到前面,赋值还在原位置

1
2
3
4
5
6
7
8
9
10
fn();
function fn() { console.log('S'); }
fn();
function fn() { console.log('A'); }
fn();
var fn = function () { console.log('S'); }
fn();
function fn() { console.log('T'); }
fn();
//输出:T T T S S

作用域

一个简单的实例:

1
2
3
4
5
6
console.log(sast);
var sast = "SAST!";
// undefined
console.log(sast);
let sast = "SAST!";
// Uncaught ReferenceError: Cannot access 'sast' before initialization

var

使用 var 声明的变量存在于最近的函数或全局作用域中,没有块级作用域的机制。

没有块作用域很容易污染全局。

如果声明变量的时候,没有声明,直接赋值,在某种程度上也会被解释器执行,但是不建议这么写:

1
2
3
4
5
function sast() {
web = "sast.njupt.edu.cn";
}
sast();
console.log(web); //sast.njupt.edu.cn

没有块作用作用域时var也会污染全局

1
2
3
4
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log(i);

使用let有块作用域时则不会

1
2
3
4
5
let i = 100;
for (let i = 0; i < 10; i++) {
console.log(i);
}
console.log(i);

下例中体验到 var 没有块作用域概念, do/while 定义的变量可以在块外部访问到

1
2
3
4
5
6
7
8
9
10
11
var num = 0;
function show() {
var step = 10;
do {
var res = 0;
console.log(num = step++);
res = num;
} while (step < 20);
console.log(`结果是${res}`);
}
show();

var 全局声明的变量也存在于 window对象中

1
2
var sast = "SAST!";
console.log(windows.sast); // SAST!

这同样会导致污染到windows对象!

以往没有块任用时使用立即执行函数模拟块作用域

1
2
3
4
5
(function() {
var $ = this.$ = {};
$.web = "sast.njupt.edu.cn";
}.bind(window)());
console.log($.web);

有了块作用域后实现就变得简单多了

1
2
3
4
5
{
let $ = (window.$ = {});
$.web = "sast.njupt.edu.cn";
}
console.log($.web);

let

var 声明的区别是 let/const 拥有块作用域,下面代码演示了块外部是无法访问到let声明的变量。

  • 建议将let在代码块前声明
  • 用逗号分隔定义多个

let存在块作用域特性,变量只在块域中有效

1
2
3
4
5
if (true) {
let web = 'sast.njupt.edu.cn',sast = 'SAST!';
console.log(web); //sast.njupt.edu.cn
}
console.log(web); //web is not defined

块内部是可以访问到上层作用域的变量.

每一层都是独立作用域,里层作用域可以声明外层作用域同名变量,但不会改变外层变量

1
2
3
4
5
6
7
8
9
function run() {
let web = "sast.njupt.edu.cn";
if (true) {
let web = "njupt.edu.cn";
console.log(web); // njupt.edu.cn
}
console.log(web); // sast.njupt.edu.cn
}
run();

共同点

var/let/const共同点是

  • 全局作用域中定义的变量,可以在函数中使用

  • 函数中声明的变量,只能在函数及其子函数中使用

  • 函数中声明的变量就像声明了私有领地,外部无法访问

TDZ

TDZ 又称暂时性死区,指变量在作用域内已经存在,但必须在let/const声明后才可以使用。

TDZ可以让程序保持先声明后使用的习惯,让程序更稳定。

  • 变量要先声明后使用
  • 建议使用let/const 而少使用var

使用let/const 声明的变量在声明前存在临时性死区(TDZ)使用会发生错误

const

使用 const 用来声明常量,这与其他语言差别不大,比如可以用来声明后台接口的URI地址。

  • 常量名建议全部大写
  • 只能声明一次常量
  • 声明时必须同时赋值
  • 不允许再次全新赋值
  • 可以修改引用类型变量的值
  • 拥有块、函数、全局作用域
1
2
3
4
5
6
try {
const URL = "sast.njupt.edu.cn";
URL = "http://www.baidu.com"; //产生错误
} catch (error) {
throw new Error(error);
}

改变常量的引用类型变量的值

1
2
3
4
5
6
const INFO = {
url: 'sast.njupt.edu.cn',
port: '2333'
};
INFO.port = '8080';
console.log(INFO);

在不同作用域中可以重名定义常量

1
2
3
4
5
6
7
const NAME = 'ZLH';
function show() {
const NAME = 'SAST';
return NAME;
}
console.log(show());
console.log(NAME);

重复定义

使用 var 可能造成不小心定义了同名变量.

使用let 可以避免上面的问题,因为let声明后的变量不允许在同一作用域中重新声明

1
2
let web = 'sast.njupt.edu.cn';
let web = 'SAST'; // Identifier 'web' has already been declared

不同作用域可以重新声明

1
2
3
4
let web = 'sast.njupt.edu.cn';
if (true) {
let web = 'SAST'; //Identifier 'web' has already been declared
}

let 全局声明的变量不存在于 window对象中,这与var声明不同

1
2
let web = "sast.njupt.edu.cn";
console.log(window.web); //undefined

Object.freeze

如果冻结变量后,变量也不可以修改了,使用严格模式会报出错误。

1
2
3
4
5
6
7
8
"use strict"
const INFO = {
url: 'sast.njupt.edu.cn',
port: '2333'
};
Object.freeze(INFO);
INFO.port = '443'; //Cannot assign to read only property
console.log(INFO);

传值与传址

基本数据类型指数值、字符串等简单数据类型,引用类型指对象数据类型。

基本类型复制是值的复制,互相不受影响。下例中将a变量的值赋值给b变量后,因为基本类型变量是独立的所以a的改变不会影响b变量的值。

1
2
3
4
let a = 100;
let b = a;
a = 200;
console.log(b);

对于引用类型来讲,变量保存的是引用对象的指针。变量间赋值时其实赋值是变量的指针,这样多个变量就引用的是同一个对象。

1
2
3
4
5
6
let a = {
web: "后盾人"
};
let b = a;
a.web = "hdcms";
console.log(b);

undefined

对声明但未赋值的变量返回类型为 undefined 表示值未定义。

1
2
let sast;
console.log(typeof sast);

对未声明的变量使用会报错,但判断类型将显示 undefined

image-20191003194105707

1
2
console.log(typeof sast);
console.log(sast);

我们发现未赋值与未定义的变量值都为 undefined ,建议声明变量设置初始值,这样就可以区分出变量状态了。

函数参数或无返回值是为undefined

1
2
3
4
5
function sast(web) {
console.log(web); //undefined
return web;
}
console.log(sast()); //undefined

null

null 用于定义一个空对象,即如果变量要用来保存引用类型,可以在初始化时将其设置为null

1
2
var sast = null;
console.log(typeof sast);

严格模式

严格模式可以让我们及早发现错误,使代码更安全规范,推荐在代码中一直保持严格模式运行。

主流框架都采用严格模式,严格模式也是未来JS标准,所以建议代码使用严格模式开发

基本差异

变量必须使用关键词声明,未声明的变量不允许赋值

1
2
"use strict";
url = 'sast.njupt.edu.com'; //url is not defined

强制声明防止污染全局

1
2
3
4
5
6
"use strict";
function run() {
web = "sast.njupt.edu.com";
}
run();
console.log(web); //sast.njupt.edu.com

关键词不允许做变量使用

1
2
"use strict";
var public = 'sast.njupt.edu.com';

变量参数不允许重复定义

1
2
3
"use strict";
//不允许参数重名
function sast(name, name) {}