JavaScript随手笔记-基础篇-[语句语法]

2018-08-17  本文已影响0人  七代目火影Na

今天主要来介绍一些JavaScript中一些基本的语句语法,首先我就这些语句语法列出了一个表,让我们首先来看一下。

语句 语法 用途
var var name =[=expr][,...]; 声明并初始化一个或者多个变量;
let let name =[=expr][,...]; 声明并初始化一个或者多个变量;
const const name =[=expr][,...]; 声明并初始化一个或者多个常量;
class class name {functions} 声明并初始化一个类;
extends class name extends name{functions} 继承一个类;
function function name([param[],...]){statement}; 声明一个函数
return return [expression]; 在函数执行完成之后返回一个值;
if/else if(expression){statement}else{statement}; 用于条件判断选择执行某个statement;
if/ else if /else if(expression){statement}else if(expression){statement}else{statement}; if/else的强化版
while while(expression){statement}; 基本的循环结构;
do/while do{statement}while(expression); while循环的一种表现形式;
for for(init;test;incr){statement}; 一种简写的for循环;
for/in for(var in object){statement}; 遍历一个对象的属性;
for/of for(let var of object); 可遍历数组字符串对象;
label label:statement; 给statement指定一个标签,可以引用标签的只有break和continue语句;
break break [label]; 退出最内层循环或者退出switch语句,又或者退出label指定的语句;
continue continue [label]; 重新开始最内层的循环或者重新开始label指定的循环;
switch switch(expression){expression}; 重新开始最内层的循环或者重新开始label指定的循环;
case case expression; 在switch语句中标记一条语句;
default default; 在switch中标记默认的语句;
empty ; 什么都不做;
debugger debugger; 断点调试器;
throw throw new Error(expression); 抛出异常;
try/catch/finally try{statement}[catch([param[],...]){statement}][finally{statement}]; 捕获异常
use strict "use strict"; 对脚本和函数应用严格模式;
with with(object)statement; 扩展作用链;
... ...array; 将一个数组转为用逗号分隔的参数序列;

以上就是我总结的关于js的所有的语句语法(因为对于ES6有点不熟,所以一些ES6里面的语法可能不全,请各位能及时提出建议),关于以上的语法,我主要把它们分为几个部分:

1. 声明语句

在ES6版本之前,主要的声明语句还是var,在Js中我们可以利用var去声明一个或者多个变量并初始化。与let不同,var可以声明多个同名变量,var的作用域是[函数域或者全域],在使用var声明变量的时候有一个需要注意的一个点,就是声明提前的问题。变量声明语句会被“提前”到脚本或者函数体的顶部,具体请看下面的代码:

function(){
  console.log(he); //控制台输出的为undefined
  var he = 4;
}

在本例中,我们事先打印了he变量,然后再在下一步中声明了he变量,但是我们会发现函数在执行的时候没有报错,反而打印出了he的值为undefined,这就是声明提前的问题,he变量的声明被提前到函数体顶部,并初始化为undefined,这和下面这段代码是等价的。

function(){
  var he;
  console.log(he);
  he = 4;
}
  1. ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的[代码块]内有效。
{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1

上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。

  1. let相比较var,其中的特点之一就是let不存在变量提升,使用let声明的变量,必须要在声明之后才能使用,而不像var一样在声明之前会被提升初始化。
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

上面代码中,变量foo用var命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量bar用let命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。

  1. 使用let声明的变量存在暂时性死区,意思就是说let声明变量的时候会绑定一个区域使这个区域中的变量不在受外界影响。
var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
注:let的存在使typeof不再是一个百分之百安全的操作。

  1. 使用let在同一个块级作用域中不允许声明两个相同的变量。
// 报错
function func() {
  let a = 10;
  var a = 1;
}

// 报错
function func() {
  let a = 10;
  let a = 1;
}

const声明一个只读的常量。一旦声明,常量的值就不能改变;const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值;const的作用域与let命令相同:只在声明所在的块级作用域内有效;const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。const声明的常量,也与let一样不可重复声明。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

const foo;
// SyntaxError: Missing initializer in const declaration

if (true) {
  const MAX = 5;
}

MAX // Uncaught ReferenceError: MAX is not defined

if (true) {
  console.log(MAX); // ReferenceError
  const MAX = 5;
}

var message = "Hello!";
let age = 25;

// 以下两行都会报错
const message = "Goodbye!";
const age = 30;

2. 类声明语句

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。ES5代码都可用 ES6 的class改写。

  1. 但是class和传统的ES5语法不同的一点就是通过class声明的方法都是不可以枚举的,而在ES5中的构造函数中是可以枚举的。
class Point {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

var Point = function (x, y) {
  // ...
};

Point.prototype.toString = function() {
  // ...
};

Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
  1. 类的属性名可以采用表达式
let methodName = 'getArea';

class Square {
  constructor(length) {
    // ...
  }

  [methodName]() {
    // ...
  }
}
  1. 相对于ES5的构造函数,还有不存在变量提升、不能像使用函数一样使用类、class表达式const class = new Class{};、可使用静态属性等特点,在此就不一一列举了。
  2. 静态方法,在其中涉及到的一个概念就是静态方法,静态方法只能通过类的实列去调用而不能通过对象去调用,这个方面js和java是一样的。
    tips:其中新增了一个new.target属性可以用来判断这个对象是不是通过构造函数调用的,如果不是的话就会返回一个undefined。

我们在进入extends实现继承之前我们先回想一下在es5中有的几种继承方式:

注:这里的原型链获取都是采用的.prototype,这里是构造对象的原型链获取方式,如果是非构造对象,则采用Object.getPrototypeOf(o)或者o.__proto__获取。
  1. 默认模式,也就是通过原型链继承
    child.prototype = new parent();
    tips:继承了父类的模板和原型,但是子类在初始化的时候是用的父类的传参方法,在实际开发中必须要手动纠正,即在末尾加上一句child.prototype.constructor = child把其子类的原型链的构造方法改为自己的构造方法。
  2. 借用构造函数和apply、call;
    child: parent.apply(this,arguments);
    tips:只继承了父类的模板而没有继承他的原型链。
  3. 借用和设置原型;
    child: parent.apply(this,arguments);
    child.prototype = new parent();
    tips:子类拥有超类的两份属性,程序较大的时候会影响性能。
  4. 共享原型;
    child.prototype = parent.prototype
    tips:这里共享了两个对象的原型链实现了继承,但是缺点就是其中一个实列对于原型链的修改都会影响到另一个实列。
  5. 利用空对象作为中介;可用于非构造函数对象的继承
function extend(Child, Parent) {
    var F = function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Child.uber = Parent.prototype;
  }

tips:上述方法的一个改良,依旧没有类的概念。

  1. 拷贝继承; 浅拷贝,只适用于基本类型的拷贝继承
function extend(Child, Parent) {
    var p = Parent.prototype;
    var c = Child.prototype;
    for (var i in p) {
      c[i] = p[i];
    }
    c.uber = p;
  }

tips:其实这种方法就是对原型链的一个深拷贝,实现了对实列的继承,缺点就是如果原型链中的属性设置了不能枚举,那么其中的属性就不会被拷贝。

  1. Object.create;
function extend(child,parent){
     child.prototype = Object.create(parent);
     child.prototype.constructor = child;
}

tips:Object.create()方法是ECMAScript 5中新增的方法,这个方法用于创建一个新对象。被创建的对象继承另一个对象的原型,在创建新对象时可以指定一些属性。
语法: Object.create(proto[,propertiesObject])
这个方法其实就是 上述的第五种方法。

  1. extends;
class child extends parent{
...
}

最后一种方法也就是我们今天的重点,es6中新出现的语法extends就是最后一种实现继承的方式,这个语法的特点和含义对于学习过java等面向对象语言的同学来说应该不难理解,两者的方式都是一样的。

tips:判断父子类的函数

cp instanceof point;
Object.getPrototypeOf(ColorPoint) === Point;

3. 函数语句

关键字function用于在js中定义一个函数,函数定义在js中有以下几种写法:

//最平常的写法
function func(args){
  ...
}

//赋值写法
var func = function(args){
  ...
}

//回调函数写法
function func(callback){
  ...
  callback();
}

/**函数体内声明写法
 * 这种写法在ES5中是错误的写法,但是在ES6中是支持这种写法的,因此这种写法在大部分的浏览器中是不会报错的
 */
function func(){
  ...
  function get(){}
}

//立即调用写法
function(){
  ...
}();

4. 循环语句

以上的三个循环我相信大家对它的用法应该都很了解,在这里我就不详细介绍了,这三个语句在java,js中用法大同小异,大家不熟悉可以去回顾一下。

for/in循环也用来for关键字,但是用法却和它有些不同,for/in循环主要是用于枚举对象的属性成员

// for/in可以枚举出对象中属性成员
var object = {
  a: 1,
  b: 2
  ...
}
for(item in object){
  console.log(item);
}

// for/in可以枚举出数据的下标(不是其中的对象)
var arr = [1,2,...];
for(index in arr){
  console.log(arr[index]);
}

for/of是ES6中的一个新的语法,可以把它看作一个遍历器,其中遍历对象的方式和for/in是一样的,而遍历数组的方式和其中的forEach是相同的。

let arr = [1,2...];
for(item off arr){
  console.log(item);
}

5. 判断语句

这个语句也是在各大编程语句中很常见的一种语句if/elseif/else if/else还有switch,在这里我就不详细讲述它的用法,我就简要地说几个要注意的点。

在现实的开发中我们经常会遇到嵌套的if else,其中就会有一下的情况

if(a === b)
  ...
  if(b === c)
    ...
else
  ...
//因为缩进的误导我们会以为第二个if是被套在里面,但是在js中,其中if和else的配对中没有花括号就近地配对,所以会解析为以下的内容:
if(a === b){
  ...
  if(b === c){
    ...
  }
  else{
    ...
  }
}
//所以我们在实际的开发中,还是要多写花括号,以防止发生这样的错误

虽然我们的switch已经可以方便地实现很多的功能,但是在某些情况下我们也会发现它的不足之处,所以在这里有一中可以用对象的方法去代替switch的方法:

//原switch的写法
switch(type){
  case type1:
    func1;
    break;
  case type2:
    func2;
    break;
  ....
}
//对象写法
var switcher = {
  type1: func1,
  type2: func2,
  ...
};
switcher["typeX"]();
// 利用对象的方式,在很多的场合可以很清晰地选择要执行的方式,代码量也比switch小

6. 跳转语句

对于跳转语句,熟悉循环语句的同学都会对跳转语句有所了解或者说熟悉跳转语句,以上的continue break throw case default return这几个跳转语句大家再熟悉不过了,这几个跳转语句也经常出现在java中,它们的用法是一样的,在这里我也不做过多的陈述了,着重要提的一点就是break语句和continue语句,他们可以和标记语句label一起使用,来实现对特定语句的跳转,具体在下一节会做详细地陈述。

7. 标签语句

js中的标记语句lable是js特有的一种语法,因为标签语句只能与break continue联合使用,所以一般来说标签语句主要放在循环体处,用于循环跳转操作,以下是代码示列:

var temp=0;  
start:  
for(var i=0; i<5; i++) {  
    for(var m=0; m<5; m++) {  
        if(m==1) {  
            break start;  
        }  
        temp++;  
    }  
}  
alert(temp);

上面的代码,通过标签语句可以轻松地break掉外层循环,如果不使用标签语句的话,我们break只能跳出内层的循环,要实现外层循环得多添加几个判断语句,所以有时候标签语句带给我们的便利还是很多的。

8. 异常语句

try{
  ...
}
catch(exception){
  ...
}
finally{
  ...
}

参数说明:
tryStatements:必选项。可能发生错误的语句序列。
exception:必选项。任何变量名,用于引用错误发生时的错误对象。
catchStatements:可选项。错误处理语句,用于处理tryStatements中发生的错误。
编码时通常将可能发生错误的语句写入try块的花括号中,并在其后的catch块中处理错误。错误信息包含在一个错误对象(Error对象)里,通过exception的引用可以访问该对象。根据错误对象中的错误信息以确定如果处理。

9. 其它语句

with将绑定的对象添加至作用域链头部,然后执行语句,执行之后又恢复作用域链到原始状态,如下所示:

with(document.forms[0]){
  // 直接访问表单元素
  name.value = 0;
  address,value = 0;
  email.value = 0;
}

使用with语句还可以为当前对象的属性赋值或者添加属性,比如下面的列子:
with(o) x = 1;

扩展运算符( spread )是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

/**
 * 函数调用
 */
console.log([1,2,3,4,...]);
console.log([1,2,...[3,4],...]);
// 打印出来的结果 1,2,3,4,...

function(array,...items){
  array.push(...items);
}

/**
 * 替代数组的apply方法
 */
// ES5 的写法
function f(x, y, z) {
  ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6 的写法
function f(x, y, z) {
  ...
}
var args = [0, 1, 2];
f(...args);

/**
 * 拼接数组
 */
var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];
[...arr1, ...arr2, ...arr3];

/**
 * 解构赋值
 */
const [first, ...rest] = [1, 2, 3, 4, 5];
// first: 1; rest: [2,3,4,5];

/**
 * 函数返回值
 * 有时候需要返回多个值的时候可以用spread
 */
var dateFields = readDateFields(database);
var d = new Date(...dateFields);

/**
 * 字符串转数组
 */
console.log(...["array"]);
// 输出 ['a','r','r','a','y'];

/**
 * 实现了 Iterator 接口的对象,可以用其转换为数组
 */

使用'use strict'可以消除js中语法的很多不合理不严谨的地方,下面是一些关于严格模式与非严格模式的区别:

10. 空语句

如果在语句中只有一个;,则不会发生任何的执行。

上一篇下一篇

猜你喜欢

热点阅读