JavaScript 基础卷(一):基础语法
使用与输出#
使用 JavaScript#
- 内部使用:置于
<script></script>
中- 在
HTML
中,JavaScript 脚本需置于<script></script>
标签之间 - 这部分内容可以放在
<head>
中,也可以放在<body>
中 <head>
与<body>
中的 JavaScript 有什么区别呢- 简单来说,区别就是执行顺序不同:
<head>
中的 JavaScript 会 HTML 标签解析之前就执行,而<body>
中的是在加载后才执行的 - 举个例子,脚本中的某个函数涉及到 DOM 操作,那么前提当然是这个 DOM 节点已经生成了,若放在
<head>
中,那么就会在 HTML 标签解析之前执行,也就没有对应的 DOM 对象可以操作了
- 简单来说,区别就是执行顺序不同:
- 应该放在哪里呢
- 需调用才执行的脚本或事件触发执行的脚本放在 HTML 的
<head>
部分中,这样可以保证在任何调用之前被加载 - 些当页面被加载时才会执行的脚本应该放在 HTML 的
<body>
部分,放在<body>
部分的脚本通常被用来生成页面的内容 - 一般是把函数放入
<head>
部分中,或者放在页面底部,这样可以把它们安置到同一位置,不会干扰页面的内容
- 需调用才执行的脚本或事件触发执行的脚本放在 HTML 的
- 参考链接(defer 和 async 超出此卷的范围,之后另做讲解)
- 在
- 外部使用:置于外部 js 文件中,通过
<script src="xxx.js"></script>
引入- 通常这个引入 src 的地方也就是你原本打算在
<script>
中写入内容的地方
- 通常这个引入 src 的地方也就是你原本打算在
输出 JavaScript#
JavaScript 没有提供 任何打印或者显示的函数,我们可以选择以下几种辅助方案输出:
window.alert()
弹出警告框document.write()
写入 HTML 文档中innerHTML
写入 HTML 元素中console.log()
写入浏览器的控制台中
下面我们详解这几个方法
window.alert()
- 基本用法请阅读:MDN — window.alert
document.write()
基本用法请阅读:
document.write(exp1, exp2, exp3, ...)
可以接受一个或者多个参数,该方法会按照顺序写入由document.open()
打开的一个文档流中(多个参数貌似不会换行显示)这里的参数可以是 变量、函数体、自执行函数、数字、字符串、表达式 等等,参数也同样支持 HTML 标签,只是需要用字符串形式
例如:
<script type="text/javascript"> var a = 100; var b = function(){ console.log('200') }; //变量 document.write(a,'<br/>'); //函数体 document.write(b,'<br/>'); //自执行函数 document.write(function(){ return 300; }(),'<br/>'); //数字 document.write(400,'<br/>'); //字符串 document.write('500','<br/>'); //表达式 document.write((300*2),'<br/>'); //HTML 标签 document.write('<h1>700</h1>'); </script>
浏览器显示如下:
注意,因为 document.write () 写入文档流,所以 在已加载或者已关闭的文档上调用 document.write () 会自动调用 document.open (),这将覆盖该文档,比如:
<p>ABCDE</p> <button onclick="displayDate()">点击这里</button> <script type="text/javascript"> function displayDate(){ document.write('覆盖页面') } </script>
此时,不管你的页面上有什么内容,点击按钮,就会立刻覆盖整个页面
这种情况一般伴随着两个条件:1、在函数内部调用 document.write ();2、通过按钮响应调用函数
再次重复一遍!向一个已经加载,并且没有调用过 document.open () 的文档写入数据时,会自动完成调用 document.open () 的操作,一旦完成了数据写入,系统要求调用document.close()
以告诉浏览器页面已经加载完毕,写入的数据会被解析到文档结构模型里,生成对应的 DOM 结点而直接嵌入 HTML 中,不会调用 document.open () 这也就是为什么我们上上个例子中连续多个 document.write () 没有被覆盖
试一下:手动使用
document.close()
:<h2>AB</h2> <h2>CD</h2> <h2>EF</h2> <script type="text/javascript"> document.write('document.close()之前','<br />'); document.close(); document.write('document.close()之后') </script>
<h2>AB</h2> <h2>CD</h2> <h2>EF</h2> <script type="text/javascript"> window.onload = function(){ document.write('document.close()之前','<br />'); document.close(); document.write('document.close()之后') } </script>
这是因为,
window.onload
表达的是An event handler for the load event of a window.
换言之,在文档加载完毕的时候,document.write () 在页面加载后调用,但在 W3C 规范中没有定义时,会发生自动的 document.open () 调用,所以页面才会被清除
innerHTML
- innerHTML 有着双向功能:①获取 HTML 内容;②写入 HTML 内容。案例如下:
<div id="x"> <p>此为用 innerHTML 获取内容1<p> <p>此为用 innerHTML 获取内容2<p> </div> <button type="button" onclick="Inner()">点击</button> <script type="text/javascript"> function Inner(){ alert(document.getElementById('x').innerHTML); document.getElementById('x').innerHTML = '此为用 innerHTML 写入内容' } </script>
- innerHTML 有着双向功能:①获取 HTML 内容;②写入 HTML 内容。案例如下:
console.log()
- 与
alert()
不同的是,console.log()
并不会阻断线程,用法比较简单,在此不细说了,下面列举一些控制台常用方法 - console.log() :输出普通信息
- console.info() :输出提示信息
- console.error() :输出错误信息
- console.warn() :输出警示信息
- console.debug() :输出调试信息
- 与
语句与表达式#
语句#
语句(statement) 是为了 完成某种任务 而进行的 操作 。
- 你可以把一个语句理解成一个动作,一个行为,比如 if 语句、循环语句,如下的赋值语句:
var a = 1;
- 上述语句先用
var
命令声明了变量a
,再将1+3
的运算结果赋值给变量a
- 上述语句先用
- 一个程序 是由 一系列语句 组成的
- JS 语句以分号结尾,JS 是 弱类型 的语言,虽然分号有时可以省略,也请在省略时 注意是否会造成语法错误 ,但是一个分号就肯定表示着一个语句的结束,多个语句可以写在一行内。如:
var a = 1; var b = 2;
- 分号前面可以没有任何内容,JavaScript 引擎将其视为空语句。
;;; //表示三条空语句
- 语句不存在返回值的说法
- 一般 JavaScript 的语句分为以下几种:
- 声明语句:变量声明和函数声明
- 赋值语句:在语句中比较重要的一类
- 控制语句:能够 改变语句的执行顺序,包括条件语句和循环语句,还有比较特殊的标签语句
- 表达式语句:是 JS 中最简单的语句,此类语句只由表达式组成,没有其他语法元素
- 赋值、delete、函数调用 这三类既是表达式,又是语句,所以叫做表达式语句
- 注:JS 中某些需要语句的地方,若可以使用一个表达式来代替,那么此语句即是表达式语句。但反过来不可以,不能在一个需要表达式的地方放一个语句。比如,一个 if 语句不能作为一个函数的参数。
表达式#
表达式 是由 运算符 和 运算元(可选) 构成的,并产生 运算结果 的语法结构 。
- 以下在 ES5 中,被称为 基本表达式(Primary Expression):
- this、null、arguments 等 内置关键字
- 变量,即一个已声明的标识符
- 字面量,仅包括数字字面量、布尔值字面量、字符串字面量、正则字面量
- 分组表达式,即用来表示立刻进行计算的
- 上述表达式均为 原子表达式,即无法再分解的表达式
- 除了基本表达式外,还有 复杂表达式,这类表达式 需要其他表达式的参与,如下:
- 对象、数组初始化表达式:
- 其实他们也算字面量的一种,但不把它们算作基本表达式,是因为对象字面量、数组字面量所包含的成员也都是表达式
- 数组初始化表达式语法如下:初始化的结果是一个新创建的数组,数组的元素是逗号分隔的表达式的值。举例:
[expression,expression,expression] //可以有 0个或多个子表达式
[] //一个空数组;[]内留空即表示该数组没有任何元素 [1+2,3+4] //拥有两个元素的数组,第一个是3,第二个是7
- 对象初始化表达式语法如下:初始化的结果是一个新创建的对象,举例:
{ expression1: expression2, expression1: expression2, expression1: expression2, } //在ES5及其之前,expression1只能是字符串字面量,但ES6开始支持以下写法: { [expression1]: expression, [expression1]: expression, [expression1]: expression, } //expression1可以是任何返回值为字符串或Symbol类型的表达式
{ foo: bar(3,5) } //不过同时,它也是一个完全合法的语句,这个语句的组成部分有: // 1.一个代码块:一个由大括号包围的语句序列; // 2.一个标签:你可以在任何语句前面放置一个标签.这里的foo就是一个标签. // 3.一条语句:表达式语句bar(3, 5).
- 函数定义表达式(注意,需与函数声明语句区分开)
- 关于函数声明和函数表达式 可查看此两篇博文:
js 函数声明和函数表达式的区别 以及 js 函数声明和函数表达式
- 关于函数声明和函数表达式 可查看此两篇博文:
- 属性访问表达式:
- 属性访问表达式的两种语法如下:以及
expression.identifier //expression可以是任意的表达式,identifier是属性名(必须合法) //注:跟在对象后面的句点'.'不是运算符,而是属性访问表达式的语法结构的一部分
expression1[expression2] //其中,expression1与expression2都可以是任意表达式 //而expression2的值会被转化为字符串(除非它是一个Symbol类型)
- 属性访问表达式的两种语法如下:
- 调用表达式(分为 函数调用 和 方法调用):
- 函数调用的语法如下:
expression0([expression1[,expression2[,expression3]]]) //expression0是一个返回值为函数对象的表达式 //小括号内的是参数列表,其参数为0或多个,用逗号分隔 //小括号并非操作符,而是调用语法的一部分
- 方法调用的语法如下:
expression0([[expression1[,expression2[,expression3]]]) //其中,expression0 是一个返回值为函数对象的表达式 //小括号提供一个逗号分隔的参数列表
- 函数调用的语法如下:
- 对象创建表达式:
- 该表达式的语法如下:
new expression0([expression1[,expression2[,expression3]]]) //同调用表达式一样,//expression0是一个返回值为函数对象的表达式 //小括号并非操作符而是语法的一部分
- 该表达式的语法如下:
- 对象、数组初始化表达式:
- 若表达式中未使用运算符,则称为
单值表达式
,否则为复合表达式
- JavaScript 表达式必有返回值,单值表达式返回其值本身,复合表达式返回运算的结果值
- 总结出的表达式分类如下:
- 单值表达式(不使用运算符)
- 简单表达式(不能再分解)
- 复杂表达式(需要其他表达式的参与)
- 复合表达式(由运算符将多个单值表达式结合而成的表达式)
- 单值表达式(不使用运算符)
变量#
相关概念#
- 变量是对 “值” 的具名引用
- 变量的类型是没有限制的,所以你可以随时修改变量的类型,这也表明了 JavaScript 是一种动态类型语言
标识符#
- 标识符(identifier) 指的是用来识别各种值的合法名称,最常见的标识符就是变量名以及函数名,标识符对大小写敏感
- 命名规则如下:
- 以
字母 或者 $ 或者 _
开头,后面可包含数字
- 对大小写敏感
- 中文是合法标识符,如:
var 临时变量 = 1;
- 保留字不能作标识符
- 以
- 保留字如下:
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
变量声明#
- 变量的声明与赋值是两个过程,如果只是声明变量而没有赋值,则该变量的值是
undefined
,undefined 是一个特殊的值,表示未定义 - 重复地声明变量是无效的
- 初识变量提升
- 由于 JavaScript 引擎的工作方式是:先解析代码,获取所有被声明的变量再运行,所以,所有变量的声明都会被提到 作用域的顶端
- 看几个例子:
function test(){ console.log(a); var a = 1; } test(); //undefined //上面的代码等效于: function test () { var a; console.log(a); a = 123; } test(); //undefined
看一道经典的面试题:a = 1; var a; console.log(a); //1
console.log(v1); //undefined var v1 = 100; function foo() { console.log(v1); //undefined var v1 = 200; console.log(v1); //200 } foo(); console.log(v1); //100
- 实际上,js 引擎并不会把
var a = 1;
当做一个过程,而是看做var a;
和a = 1;
,分别对应着声明与赋值,分别对应着编译阶段和执行阶段,而声明会被提到作用域的顶端 - 如果理解了上面三个例子,那么已经成功认识了变量提升这个概念,彻底理解会在之后的作用域中详解
- 参考阅读:
- 几种变量的声明:var、let、const
- 先认识一个块级作用域:在 ES5 中,作用域有全局作用域和函数作用域,而在 ES6 中,增加了一个 块级作用域,块级作用域由
{ }
包括(所以 if 语句等等也会形成块级作用域),举个栗子://通过var定义的变量可以跨块级作用域访问到 { var a = 1; console.log(a); //1 } console.log(a); //1 //通过var定义的变量不可以跨函数作用域访问到 (function A(){ var b = 2; console.log(b); //2 })(); console.log(b); //报错:b is not defined //if语句和for语句形成的是块级作用域而非函数作用域,因为var定义的变量可以在外部访问 if(true){ var c = 3; } console.log(c); //3 for(var i = 0; i < 4; i++){ var d = 5; } console.log(i); //4 console.log(d); //5
- 承接之前的块级作用域,说说 var、let、const 的区别:
- var 定义的变量,没有块级作用域的概念,所以可以跨块级作用域访问,但不能跨函数作用域访问
- let 定义的变量,只能在块级作用域里访问,不能跨块访问,也不能跨函数访问。
- const 用来定义常量,使用时必须初始化 (即必须赋值),只能在块作用域里访问,而且不能修改
- 举例如下:
//块级作用域 { //const 不允许改变 var a = 1; let b = 2; const c = 3; c = 4; //报错:Assignment to constant variable //const 声明变量时必须初始化 var A; let B; const C; //报错:Missing initializer in const declaration //打印,观察 console.log(a); //1 console.log(b); //2 console.log(c); //3 console.log(A); //undefined console.log(B); //undefined } console.log(a); //1 console.log(b); //报错:b is not defined console.log(c): //报错:b is not defined //函数作用域 (function Fun(){ var i = 5; let j = 6; const k = 7; })(); //console.log(i); //报错:i is not defined //console.log(j); //报错:j is not defined //console.log(k); //报错:k is not defined
- 读到这里,已经初步认识了三者,接下来详细说明一下各自的用法
- let 声明变量
- 之前提到过,ES5 没有块级作用域,这导致很多场景只能通过闭包来解决不合理的冲突,ES6 出来之后,我们可以通过 let 即可实现
- 注意,let 声明的变量只在 let 命令所在的代码块内有效,如上个例子中的
console.log(b);
与console.log(j);
均会报错 “未定义”,所以,for 循环就很适合 let,另外,说到 for 循环顺便提提它的特点:设置循环变量的那部分是一个 父作用域,而循环体内部是一个单独的 子作用域for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc
- let 不存在变量提升,之前在 var 的变量提升总让人感觉逻辑怪怪的,现在在 let 中不存在变量提升了
- 暂时性死区:只要块级作用域内存在 let 命令,它所声明的变量就 “绑定”(binding)这个区域,不再受外部的影响,例如:上面代码中,var 先将 a 声明为全局变量,但是块级作用域内 let 又声明了一个局部变量 a,导致后者绑定这个块级作用域,所以在 let 声明变量前,对 a 赋值会报错
var a = 1; if(true){ a = 'abc'; let a; }
因为 ES6 已经明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了 封闭作用域,不可在声明之前就使用这些变量
也就是在用 let 和 const 声明变量之前,不可以使用该变量,这种语法称为 暂时性死区(temporal dead zone,简称 TDZ) - var 可以重复声明变量的,只不过无效而已,但是 let 是不允许重复声明同一变量的,例如:
var a = 1; var a; console.log(a); let b = 1; let b; console.log(b); //报错:Identifier 'b' has already been declared function fun(){ var a = 1; let a; //报错:Identifier 'a' has already been declared } //下面的不报错 function fun(x){ { let x; } } fun(); //不报错
- const 声明变量
- const 声明一个 只读的常量,一旦声明,常量的值就不能改变,这也同时意味着,一旦用 const 声明变量,就必须立刻赋值,不能留到之后再赋值,例如:
//不能改变值 const a = 1; a = 2; //报错:Assignment to constant variable. //必须立刻赋值 const b; b = 3; //报错:Missing initializer in const declaration
- const 的作用域与 let 相同:只在声明所在的块级作用域内有效,同样的,const 声明的常量也不会有提升现象,而且也同样存在暂时性死区,只能在声明的位置后面使用
- 参考阅读:
- 拓展:const 真的不能修改么?
- 先看个例子:
const person = { name: 'A', age: 15 } person.name = 'B'; console.log(person.name) //B
- 因为对象是引用类型的,person 保存的仅仅是 对象的指针,这也就意味着,const 似乎只在乎指针有没有发生改变,而修改对象的类型是不会改变对象指针的,所以是被允许的
- 也就是说,const 定义的引用类型只要指针不发生改变,其他的不论如何改变都是允许的,用下面的代码测试,果然报错:
const person = { name: 'A', age: 15 } person = { name: 'A', age: 16 } console.log(person.name); //报错:Assignment to constant variable.
- 先看个例子:
- const 声明一个 只读的常量,一旦声明,常量的值就不能改变,这也同时意味着,一旦用 const 声明变量,就必须立刻赋值,不能留到之后再赋值,例如:
- 先认识一个块级作用域:在 ES5 中,作用域有全局作用域和函数作用域,而在 ES6 中,增加了一个 块级作用域,块级作用域由
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: