【JS】JS语法

290 阅读16分钟

一、补充知识点

  1. JavaScript 本身不提供任何与I/O(输入/输出)相关的API,都要靠宿主环境(host)提供,所以 JavaScript只合适嵌入更大型的应用程序环境,去调用宿主环境提供的底层 API。目前,已经嵌入JavaScript的宿主环境有多种,最常见的环境就是浏览器,另外还有服务器环境,也就是 Node 项目。

①宿主环境是浏览器的话,它提供的额外 API 可以分成三大类。

  • 浏览器控制类:操作浏览器

  • DOM 类:操作网页的各种元素

  • Web 类:实现互联网的各种功能

②如果宿主环境是服务器,则会提供各种操作系统的 API

  • 比如文件操作 API
  • 网络通信 API等等
  • 这些你都可以在 Node 环境中找到。
  1. JavaScript 的核心语法部分相当精简,只包括两个部分
  • 基本的语法构造(比如操作符、控制结构、语句)
  • 标准库(就是一系列具有各种功能的对象比如Array、Date、Math等)。
  • 除此之外,各种宿主环境提供额外的 API(即只能在该环境使用的接口),以便 JavaScript 调用。
  1. 推荐安装 Chrome 浏览器,它的“开发者工具”(Developer Tools)里面的“控制台”(console),就是运行 JavaScript 代码的理想环境。
  2. JS区分大小写!
  3. 大部分空格回车没有实际意义(当不影响浏览器断句时)
  • var a = 1var a=1没有区别
  • 加回车大部分时候也不影响
  • 只有一个地方不能加回车,那就是return后面

二、表达式与语句

1、表达式(expression)

(1)表达式,指一个为了得到一个值的计算式

(2)函数的值也称为返回值

(3)例子

①1+2表达式的值为3

②add(1,2)表达式的值为函数的返回值

③console.log表达式的值为函数本身。因为后面没有加括号,也就是没有调用这个函数。

④console.log(3)表达式的值为函数的返回值Unidifined(源代码里规定了),3只是这个函数打出来的东西。

2、语句(statement)

  • 语句是为了完成某种任务而进行的操作
  • 语句一般会改变环境(比如声明、赋值)
  • 语句以分号或者空格结尾,一个分号或者空格就表示一个语句结束。
  • js代码不用写分号不用不用,只有立即执行函数前面需要
  • 多个语句可以写在一行内。
  • 如果语句间用逗号连接,表示一个语句。
  • 举例
var a=1+3

这条语句先用var命令,声明了变量a,然后将1 + 3这个表达式的值赋值给变量a。

3、区别

  • 表达式是为了得到值,一定会返回一个值。凡是 JavaScript 语言中预期为值的地方,都可以使用表达式。比如,赋值语句的等号右边,预期是一个值,因此可以放置各种表达式。
  • 语句主要为了进行某种操作,一般情况下不需要返回值

4、举例

(1)

var a=1+3

这条语句先用var命令,声明了变量a,然后将1 + 3这个表达式的值赋值给变量a。

三、变量

1、定义

变量是对“值”的具名引用。变量就是为“值”起名,然后引用这个名字,就等同于引用这个值。变量的名字就是变量名。

2、声明变量的两种方式

(1)var a;已过时,不准用

  • var是变量声明命令。它表示通知解释引擎,要创建一个变量a
  • 此时只声明了这个变量,但是还未给它赋值,所以该变量的值是undefinedundefined是一个特殊的值,表示“无定义”。
  • 如果变量赋值的时候,忘了写var命令,这条语句也是有效的。
var a = 1;
// 基本等同
a = 1;

但是,不写var的做法,不利于表达意图,而且容易不知不觉地创建全局变量,所以建议总是使用var命令声明变量。

  • 可以在同一条var命令中声明多个变量。比如var a b;

(2)let a=1

① 是目前最新的、更合理的方式

② 用于声明变量

③ 规则

  • 遵循块作用域,即使用范围不能超出{ }

  • 不能重复申明(不对啊,可以重复声明啊)
  • 可以赋值,也可以不赋值
  • 必须先声明再使用,否则报错
  • 全局声明的let变量,不会变成window的属性
  • for循环配合let有奇效

(3)变量声明后,即指定了值也指定了类型,但是值和类型还是可以随意再变化的

3、声明常量const a=1

① 声明常量

② 声明时就要赋值,赋值后不能改

③ 规则

  • 遵循块作用域,即使用范围不能超出{ }
  • 必须先声明再使用,否则报错
  • 全局声明的const常量,不会变成window的属性
  • for循环配合const有奇效

4、变量的赋值a = 1+3

  • 1+3这个表达式的值4赋值给我们刚才声明的变量a
  • 以后,引用变量名a就会得到数值4。

5、变量的声明和赋值可以合写成一句var a = 1+3

  • 表示先声明变量a,再将数值1“赋值”给变量a
  • 至此,因为变量a已被声明,所以可以直接用a='hello'再次给变量a赋值(会覆盖之前的值),无需再次声明

6、name和'name'的区别

  • name是变量,值可以变啊,任何类型都可以再变,比如'name','hello'
  • 'name'就是一个字符串,是一个常量啊!你怎么去把一个字符串变掉?

四、标识符(identifier)

1、定义

  • 标识符指的是用来识别各种值的合法名称。
  • 最常见的标识符就是变量名和函数名。

2、标识符命名规则

(1)第一个字符,可以是

  • 任意 Unicode 字母(包括英文字母和其他语言的字母)
  • 美元符号($)
  • 下划线(_)。
  • 中文

(2)第二个字符及后面的字符

  • 同上
  • 数字0-9

(3)JavaScript 有一些保留字,不能用作标识符

arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。

3、报错样式

Uncaught SyntaxError: Unexpected token xxx

未捕捉的语法错误:xxx不是我们所期待的字符串

五、注释

  1. 定义: 源码中被 JavaScript引擎忽略的部分就叫做注释
  2. 作用:对代码进行解释。
  3. 写法
  • 单行注释,用//起头
  • 多行注释,放在/* 和 */之间。
// 这是单行注释

/*
 这是
 多行
 注释
*/
  1. 不好的注释
  • 把代码翻译成中文!
  • 过时的注释!
  • 发泄不满的注释!
  1. 好的注释
  • 踩坑注解
  • 为什么代码会写得这么奇怪,遇到什么bug

六、区块(block)

  1. 定义:使用大括号,将多个相关的语句组合在一起,称为“区块”。
{
  let a=1
  let b=1
}
  1. 对于var命令来说,JavaScript 的区块不构成单独的作用域(scope)。
{
  var a = 1;
}
a // 1

上面代码在区块内部,使用var命令声明并赋值了变量a,然后在区块外部,变量a依然有效,区块对于var命令不构成单独的作用域,与不使用区块的情况没有任何区别。

  1. 在 JavaScript 语言中,单独使用区块并不常见,区块往往用来构成其他更复杂的语法结构,比如for、if、while、function等。
  2. 大括号{}在语句只有一句的时候可以省略,建议别这样。

七、条件语句

(一)if 结构

  1. 规则
  • 先判断一个表达式的布尔值,然后根据布尔值的真伪,执行不同的语句。
  • 所谓布尔值,指的是 JavaScript 的两个特殊值,true表示真,false表示伪。
  • 如果表达式的求值结果为true,就执行紧跟在后面的语句;如果结果为false,则跳过紧跟在后面的语句。
  1. 基本形式
if (表达式){
  语句
}
  1. 关于=
  • 赋值表达式(=):x=y 把y的值赋给x!一直是true!
  • 严格相等运算符(===):x===y x等于y 优先采用
  • 相等运算符(==): x===y x等于y

(二)if...else 结构

if代码块后面,还可以跟一个else代码块,表示不满足条件时,所要执行的代码。

  1. 规则:
  • 先判断一个表达式的布尔值,然后根据布尔值的真伪,执行不同的语句。
  • 如果表达式的求值结果为true,就执行紧跟在表达式后面的语句1;如果结果为false,则执行else里的语句2
  1. 基本形式
if (表达式){
  语句1
}
   else{
  语句2
}
  1. 语句1里面可以有嵌套的if...else 结构
var m = 1;
var n = 2;

if (m !== 1)
if (n === 2) console.log('hello');
else console.log('world');

上面代码不会有任何输出,else代码块不会得到执行,因为它跟着的是最近的那个if语句,相当于下面这样。

if (m !== 1) {
  if (n === 2) {
    console.log('hello');
  } else {
    console.log('world');
  }
}
  1. 语句2里面可以有嵌套的if...else 结构
// 对一个变量多次进行判断
if (表达式一) {
  语句一;
} else {
  if (表达式二) {
    语句二;
  } else {
    if (表达式三) {
      语句三;
    } else {
      语句四;
    }
  }
}


// 去掉一些大括号,等同于
// 最推荐写法

if (表达式一) {
  语句一;
} else  if (表达式二) {
    语句二;
  } else  if (表达式三) {
      语句三;
    } else {
      语句四;
    }
    
function fn(){
  if (表达式) {
    return表达式
}
  if (表达式) {
    return表达式
}
return表达式
}

  1. else代码块总是与离自己最近的前面的那个if语句配对。
  2. 缩进也可以很变态,如面试题常常下套
a=1
if(a===2)
  console.log('a')
  console.log( 'a等于2')
// a等于2 
 
  等同于 
  
a=1
if(a===2){
    console.log('a')
}
console.log( 'a等于2')

(三)switch 结构

多个if...else连在一起使用的时候,可以转为使用更方便的switch结构。

  1. 基本结构
switch (fruit) {
  case "banana":
    // ...
    break;
  case "apple":
    // ...
    break;
  default:
    // ...
}
  1. 规则:上面代码根据变量fruit的值,选择执行相应的case。如果所有case都不符合,则执行最后的default部分。
  2. 注意
  • 每个case代码块内部的break语句不能少,否则会接下去执行下一个case代码块,而不是跳出switch结构。
  • switch语句内部采用的是“严格相等运算符”———switch语句后面的表达式,与case语句后面的表示式比较运行结果时,采用的是严格相等运算符(===),而不是相等运算符(==),这意味着比较时不会发生类型转换。
var x = 1;

switch (x) {
  case true:
    console.log('x 发生类型转换');
    break;
  default:
    console.log('x 没有发生类型转换');
}
// x 没有发生类型转换
上面代码中,由于变量x没有发生类型转换,所以不会执行case true的情况。

(四)问号冒号表达式--最简单的if..else结构

  1. 基本形式
(表达式) ? 语句1 : 语句2
  1. 规则:如果“表达式”为true,则执行“语句1”,否则执行“语句2”。
  2. 等同于if..else结构中语句1和语句2都只有一句的情况。
if (表达式){
  语句1
}
   else{
  语句2
}
  1. 能用这个就不用if..else
  2. 例子

var even = (n % 2 === 0) ? true : false;

上面代码中,如果n可以被2整除,则even等于true,否则等于false。

function max(a,b){
    if(a>b)return a;
    else return b;
}
等同于
function max(a,b){
  return a>b ? a : b
}

(五)&&短路逻辑(用最新的?

  1. A && B && C && D取第一个假(布尔值判断真假)或D,并不会取布尔值true或false,布尔值在这只是用来判断真假

  2. 如果第一个运算子A的布尔值为true,则返回第二个运算子B的值(注意是值,不是布尔值);如果第一个运算子A的布尔值为false,则直接返回第一个运算子B的值,且不再对第二个运算子求值。

  3. A的布尔值为false,就停下,返回A的值。A的布尔值为true,就继续往后,再看B的布尔值,为false就停下,返回B的值,为true就继续往后。知道有个运算子的布尔值为false或者到最后一个运算子了,都返回他的值!

  4. false就停下返回值,true就继续。

  5. 例子

var a=1
if(a===1){
  console.log(a)
}
等同于
a===1 && console.log('f1存在')

console && console.log && console.log('hi')
//防御性编程
//自我保护,没有console就返回undefined,而不会报错
//IE没有console
//如果console不存在,就是undefined,那么布尔值就是false,那么在console就停下了,返回undefined。这样就不会报错了
//如果console存在,console .log不存在,布尔值为undefined,就在此停下,返回undefined,不会报错了
//如果console存在,console .log存在,就可以执行最后一句了

if(console){
    if(console.log){
        console.log('Hi')
    }
}
如果前俩都不存在,布尔值都false,就不执行这个不存在的函数了呗。直接跳过,不会报错影响整个程序

console?.log?('hi')
//最新语法,可选链语法
// ?表示我不知道有没有,有就继续,没就停下

fn && fn()

fn存在就调用fn,不存在就不调用

1&&2 数字1的布尔值为真,所以取最后一个值2

0&&2 数字0的布尔值为假,所以取第一个假值0

't' && '' // ""
't' && 'f' // "f"
't' && (1 + 2) // 3
'' && 'f' // ""
'' && '' // ""

var x = 1;
(1 - 1) && ( x += 1) // 0
x // 1

(六)||短路逻辑(用最新的n=0)

1、 规则:A||B||C||D取第一个真值或D,并不会取true|false

2、 true就停下返回值,false就继续。注意可以变成false的有六个值,会出现bug

a||b
//等同于
if(!a){b}else{a}
//如果a不存在,就执行b,否则还是a

3、 例子:判断变量a是否存在

a = a||100
//如果a存在就是a,如果a不存在就令a为100
//如果变量a存在,变量a就为第一个真值,再把变量a赋值给变量a,a还是a自己
//a不存在,那么第一个a不是真值,就执行最后一句a=100。给a一个保底值。
等同于
if(a){
    a=a
    } else{
      a=100 // 保底值
      }
      
//如果a不存在就令a为100,如果a存在就令a还是为a
等同于
if(!a){
    a=100 // 保底值
    } else{
      a=a
      }

4、有问题!如果a本来就为false的那六个值,不就会被重新赋值为保底值了吗

let b = 0
b = b||100
b //100

5、新语法应运而生

function f1(n){  //定义一个函数,参数为n
    n = n||0    //如果参数的布尔值为false(比如不存在),就让参数为0,就让参数还是等于参数
    return n+1  //返回参数+1
}

我给的参数为空字符串的话,我这个参数就会被整成0了啊

function f1(n=0){  //如果n是null或者undefined就给它保底值0
    return n+1  
}

所以a只有不存在的时候才给保底值,空字符串或者其他的false情况还是自己的值

七、循环语句

(一)while

  1. 基本结构
while (表达式) {循环体语句}
  1. 规则:
  • 判断表达式的真假
  • 当表达式为真,执行语句
  • 执行完再判断表达式的真假
  • 当表达式为假,执行后面的语句
  1. 例子

var i = 0;  //初始化表达式

while (i < 100) {   //判断表达式
  console.log('i 当前为:' + i);  //循环体语句
  i = i + 1;    //递增表达式
}

上面的代码将循环100次,直到i等于100为止。

while(a !== 1){
    console.log(a)
    a=a+0.1
}

会进入死循环。因为浮点数a永远不会等于1

  1. 初始化、判断、增长有一个不写都会进入死循环。循环体不写就是没有意义。

(二)for循环

  1. 基本结构
for (初始化表达式; 判断表达式; 递增表达式) {
  循环体语句
}
  1. 规则
  • 先执行初始化表达式
  • 再判断表达式的真假
  • 如果为真,就执行循环体语句,然后执行递增表达式
  • 如果为假,就直接退出循环
  1. 例子

var x = 3;
for (var i = 0; i < x; i++) {
  console.log(i);
}
// 0
// 1
// 2
// 最后循环执行完i=3

上面代码中,初始化表达式是var i = 0,即初始化一个变量i;判断表达式是i < x,即只要i小于x,就会执行循环;递增表达式是i++,即每次循环结束后,i增大1。

for(var i=0;i<3;i++){
    setTimeout(()=>{
    console.log(i)
    },0)
}
//3个3
/*
i=0满足i<3,所以执行循环体打出i,但要过一会。
以此类推
i=1满足i<3,所以执行循环体打出i,但要过一会。
i=2满足i<3,所以执行循环体打出i,但要过一会。
i=3不满足i<3,结束循环。
for循环不结束,就不算过一会!
现在循环结束了,过一会了,终于要打出i了,而且还要打出三次,因为此时i=3了。所以打出了3个3

*/
  1. 所有for循环,都可以改写成while循环。

(三)do...while循环

  1. 基本结构
do{
  语句
}
while (条件);
  1. 规则:与while循环类似,唯一的区别就是先运行一次循环体,然后判断循环条件。
  2. 不管条件是否为真,do...while循环至少运行一次,这是这种结构最大的特点。
  3. while语句后面的分号注意不要省略。
  4. 例子。
var x = 3;
var i = 0;

do {
  console.log(i);
  i++;
} while(i < x);
// 0 1 2

(四)break 语句和 continue 语句

  1. break语句和continue语句都具有跳转作用,可以让代码不按既有的顺序执行。
  2. break语句用于退出所有循环,跳出代码块或循环
  3. continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。
  4. 如果存在多重循环,不带参数的break语句和continue语句都只针对最内层循环。
  5. 例子
for (var i = 0; i < 5; i++) {
  console.log(i);
  if (i === 3)
    break;
}
// 0
// 1
// 2
// 3
var i = 0;

while (i < 100){
  i++;
  if (i % 2 === 0) continue;
  console.log('i 当前为:' + i);
}
//上面代码只有在i为奇数时,才会输出i的值。如果i为偶数,则直接进入下一轮循环。

(五)标签label

  1. 基本结构:标识符:语句
  2. 举例
foo:{
    console.log(1);
    break foo;
    console.log('本行不会输出');
}
//foo是一个标签,表示一个代码块
//1
{
    foo:1;
}
//这是一个代码块,代码块里有一个标签foo,标签foo的内容是1

八、参考资料

  1. JavaScript 教程
  2. 进阶:《你不知道的JavaScript》上卷