JavaScript(一)基本语法
本文最后更新于:6 个月前
6种基本数据类型、变量、语句、执行上下文、作用域与作用域链、变量/活动对象、this的指向分析、执行上下文的变化过程
JavaScript (一)基本语法
一、基本数据类型
1、6种基本类型
- number(数字),包括int、float、infinite、NaN,特殊NaN值,自己不等于自己,要通过isNaN()判断
- string(字符串),可以用单引号、可以用双引号
- boolean(布尔值),true和false
- null(空值),表示空对象指针,null转为数字时自动变成 0
- undefined(未定义的值),表示未初始化的变量,用let和var声明但未赋值时,就是undefined
- symbol(符号),是ES6新增的,符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险
以上参考自《JavaScript高级程序设计》,注意:object不归为基本数据类型
1 |
|
2、Object类型(不是基本类型)
每个Object都有如下属性和方法:
- **toLocaleString()**:返回对象的字符串表示,该字符串反映对象所在的本地化执行环境
- **toString()**:返回对象的字符串表示
- **valueOf()**:返回对象对应的字符串、数值或布尔值表示。通常与toString()的返回值相同
- constructor:用于创建当前对象的函数
- hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性
- isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型
- propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用for-in语句枚举
因为Object是所有对象的基类,所以任何对象都有以上这些属性和方法
3、String类型
在这里着重探究String的一些性质与方法
- 不限单、双引号,如果字符串内部有引号,要加转义字符
\
作为前缀 - 多行字符串,一种方式是用
\
换行,另一种是ES6新加的,反引号` - 字符串之间可以通过+拼接,也可以通过
反引号 hello ${name} 反引号
的方式插入字符串变量 - s[0]这种赋值方式不起作用,不会改变字符串内容
方法 | 说明 |
---|---|
toUpperCase() toLowerCase() |
返回全部大写、小写 |
substring(start,end) slice(start,end) substr(start,length) |
接受两个参数,表示截取的区间 [起点,终点),第二个参数可选,缺省默认为字符串结尾 返回一个新的字符串,截取后的字符串。substring将负数值视为0,slice和substr将负数视为从结尾开始倒数 |
indexOf(字符) lastIndexOf(字符) |
分别从字符串开头寻找字符、从字符串末尾开始寻找字符。它们都可以接受一个或者两个参数,第一个参数是寻找的字符,第二个参数是开始寻找的起点。若没找到,返回-1 |
startsWith(字符串) endsWith((字符串) includes((字符串) |
接受一个字符串参数,返回boolean值,表示是否以指定字符串开始、结尾、或包含指定字符串。接受第2个参数,表示开始搜索的位置 |
trim() trimLeft()、trimRight() |
返回去除头尾空格后的副本 |
repeat(数值) | 将字符串复制xx次,返回复制后的副本 |
padStart(数值,字符串) padEnd(数值,字符串) |
用另一个字符串填充当前字符串 (如果需要的话,会重复多次),以便产生的字符串达到给定长度。从当前字符串左侧开始填充。第二个参数可选,表示填充的字符(串),缺省默认为空字符 如果第一个参数比实际长度短,返回原字符串。 padEnd从字符串右侧开始填充 |
match() | 与RegExp对象的exec方法一样,接受一个正则表达式或者RegExp对象,返回一个Array数组 |
search() | 接受正则表达式,返回第一个匹配的下标。若没找到,返回-1 |
replace(字符串,字符串) | 第一个参数接受字符串、或者正则表达式,表示需要被替换的字符串,如果是字符串那么只替换第一个匹配项,如果要替换所有需要使用正则表达式且加上g标记 第二个参数接受字符串,表示替换后的字符串。第二个参数还可以接受一个函数用于处理(JavaScript replace() 方法 (w3school.com.cn)) 返回一个新的替换后的字符串,原来的字符串不变 |
split() | 第一个参数接受一个分割字符或者正则表达式,第二个参数可选,接受一个数字,限制返回数组的大小。返回值是一个分割后的字符串数组 |
charAt(下标) charCodeAt(下标) |
返回字符串对应下标位置的字符 返回字符串对应下标位置的字符的Unicode编码 |
需要注意:以上的这些方法并非String原始值类型本身所有,而是在调用这些方法的时候,JavaScript会在后台自动完成String包装类型的创建、方法调用、包装对象销毁的一系列操作。
1 |
|
1 |
|
1 |
|
补充:string与number之间的自动转换
1 |
|
4、number类型
十进制转二进制
1 |
|
1 |
|
1 |
|
二、变量
1、变量声明
var | let | const | |
---|---|---|---|
版本支持 | 所有ES版本 | ES6之后 | ES6之后 |
声明作用域 | 函数作用域 | 块作用域 | 块作用域 |
变量声明提升 | 会 | 不会 | 不会 |
同一作用域内重复声明 | 允许 | 不允许 | 不允许 |
必须声明的同时初始化 不能声明迭代变量(如:for循环的i) |
1 |
|
1 |
|
1 |
|
1 |
|
暂时性死区,在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
1 |
|
1 |
|
2、块级作用域
ES6之前,只有 全局作用域和 函数作用域,ES6随着let
和const
的新增,增加了 块级作用域这个概念。
函数作用域使得一些不合理的现象存在:
1 |
|
1 |
|
块级作用域必须有大括号,如果没有大括号,被认为不存在块级作用域
1 |
|
内层作用域可以定义外层作用域的同名变量
1 |
|
内层作用域变量不会覆盖外层作用域变量,同时也意味着内层作用域定义的变量在外层不可用
1 |
|
块级作用域内部,优先使用函数表达式
1 |
|
块级作用域的出现,使得ES5 广泛使用的 「匿名立即执行函数」不再必要了
1 |
|
3、原始值&引用值
原始值(primitive value)就是最简单的数据,引用值(reference value)则是由多个值构成的对象。
JavaScript有6种原始值(基本数据类型):Number、String、Boolean、Null、Undefined、Symbol。保存原始值的变量是按值访问的
JavaScript不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。保存引用值的变量是按引用访问的
4、判断变量类型
- typeof操作符,用来区分各种原始类型(string、number、boolean、undefined)
- instanceof操作符,用来区分各种引用类型
5、解构赋值
从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值
1 |
|
1 |
|
如果要使用不一样的变量名字,可以使用下面这种方式
1 |
|
解构赋值还可以定义默认值,这样可以避免undefined
1 |
|
注意写法
1 |
|
1 |
|
1 |
|
使用场景
如果函数接受一个对象作为参数,那么可以直接用解构赋值的方式把对象的属性写在参数里面
1 |
|
三、语句
JavaScript的语句包括:if语句、while语句、do-while语句、for语句、for-in语句、for-of语句、标签语句、break和continue语句、with语句、switch语句,其中大多数语句与C++中的语法和使用一致。下面详细介绍一下for-in语句、for-of语句和with语句
1、for-in语句
for-in语句是一种严格的迭代语句,用于枚举对象中的非符号键属性,语法如下:
1 |
|
使用for in
会遍历数组所有的可枚举属性,包括原型,如果不想遍历原型方法和属性的话,可以在循环内部判断一下,使用hasOwnProperty()
方法
实例:
1 |
|
- 注意:for-in遍历的数组的下标,而不是数组元素
2、for-of语句
for-of语句是一种严格的迭代语句,用于遍历可迭代对象的元素,不能遍历对象,因为对象没有迭代器对象。语法如下:
1 |
|
实例:
1 |
|
1 |
|
3、with语句
with语句的用途是将代码作用域设置为特定的对象,其语法是:
1 |
|
实例:
1 |
|
四、执行上下文
变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。每个上下文都有一个关联的变量对象(variableobject),而这个上下文中定义的所有变量和函数都存在于这个对象上。虽然无法通过代码访问变量对象,但后台处理数据会用到它
1、执行上下文
执行上下文可以理解为当前代码的运行环境。在 JavaScript 中,运行环境主要包含了全局环境和函数环境。
上下文中的代码在执行的时候,会创建变量对象的一个作用域链,它决定了各级上下文中的代码在访问变量和函数时的顺序。作用域链是按照”栈“LIFO的特性添加的,如下图是一个形象的作用域链表示
注意:当执行一个函数时(而非定义函数时),才创建对应的执行上下文,全局代码一上来就执行,所以一开始就有一个全局上下文。
2、变量对象+作用域链+this
当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。对于每个执行上下文,都有三个重要属性:
- 变量对象(Variable object,VO)
- 作用域链(Scope chain)
- this
执行上下文的处理分为两个阶段:1.调用函数,创建执行上下文,进入执行上下文,根据参数对象Arguments初始化AO(VO)对象;2.执行函数代码,根据代码修改AO对象的值
3、执行上下文栈
在JavaScript引擎中,维护着一个执行上下文栈,当新建了一个执行上下文时,就会将它加入到栈顶;该函数代码执行完之后,将它的执行上下文从执行上下文栈中退出。
下面用一个例子来演示执行上下文栈变化的过程,ECStack(Execute Context Stack)执行上下文栈
1 |
|
1 |
|
五、作用域&作用域链
1、变量作用域
作用域:指程序源代码中定义变量的区域(全局作用域、函数作用域、块级作用域)。 JavaScript 采用的是词法作用域,函数的作用域在函数定义的时候就决定了。
- 不同关键字声明变量的作用域
var | let | const | |
---|---|---|---|
声明作用域 | 函数作用域 | 块作用域 | 块作用域 |
变量声明提升 | 会 | 不会 | 不会 |
1 |
|
由于函数可以嵌套,所以内部函数可以使用外部函数的变量。如果同名变量,内部变量覆盖外部变量
命名空间,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突。为了解决这个问题,可以把所有全局变量和顶层函数绑定到一个全局变量中
1
2
3var MYAPP = {};
MYAPP.name = "app"
MYAPP.foo = function(){...}
2、作用域链
作用域链:当查找变量时,会先从当前执行上下文的变量对象中查找;如果没有,再从父级执行上下文的变量对象中查找;一直找到全局执行上下文的变量对象为止。这个由多个执行上下文的变量对象构成的链表就叫作用域链。
函数有一个内部属性**[[scope]]**,
- 函数创建时就保存了所有父变量对象。
- 函数执行时,加入当前执行上下文的变量对象
函数创建时,保存所有父变量对象到
[[scope]]
属性中1
2
3
4
5function foo() {
function bar() {
...
}
}此时各自的
[[scope]]
为:1
2
3
4
5
6
7
8foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];函数激活时(即实际调用函数时),进入函数的执行上下文,将活动对象添加到作用域链头部,此时的
[[scope]]
为:1
2
3
4
5
6
7
8
9
10foo.[[scope]] = [
fooContext.AO,
globalContext.VO
];
bar.[[scope]] = [
barContext.AO
fooContext.AO,
globalContext.VO
];
六、变量对象
变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
1、全局上下文的变量对象
全局上下文中的变量对象就是全局对象,可以通过this
引用,也可以通过Window
引用,是所有全局变量的宿主(全局变量作为全局对象的属性)
2、函数上下文的变量对象
在函数上下文中,我们用**活动对象(activation object, AO)**来表示变量对象。
活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,也就是活动对象上的各种属性才能被访问。
3、创建过程
活动对象是在进入函数上下文时刻被创建的。执行上下文的代码会被分成两个阶段进行处理:
- 分析(进入执行上下文)
- 执行(代码执行)
进入执行上下文,变量对象包括:函数的形参、函数声明、变量声明
1 |
|
执行foo(1)
,创建执行上下文,进入执行上下文,根据Arguments对象初始化,此时的AO为:
1 |
|
代码执行,顺序执行代码,然后根据代码修改变量对象的值,当代码执行完后,AO为:
1 |
|
七、this
this是执行上下文的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。
1、this指向问题
this 为调用函数的对象
1 |
|
在(对象)方法中,this为该方法所属的对象
1 |
|
在函数中,this表示全局对象
1 |
|
在类上下文中,this为该实例对象,类静态方法不属于类实例对象
1 |
|
在箭头函数中,this等于定义它的执行上下文的this
1 |
|
2、改变this指向
指定this的方式:call、apply、bind
1 |
|
在非严格模式下使用 call
和 apply
时,如果用作 this
的值不是对象,则会被尝试转换为对象。null
和 undefined
被转换为全局对象(所以使用 call
和 apply
时,如果只想传递后面的参数,可以设置第一个参数为null
)
bind只能绑定this一次
1 |
|
八、执行上下文变化过程推演
下面用一段代码来演示在一段函数执行时,执行上下文(栈)、作用域链Scope
、活动对象AO
的变化,同样用ECStack表示执行上下文栈
1 |
|
执行全局代码,创建全局上下文,并压入栈
1
ECStack = [globalContext]
进入全局执行上下文
1
2
3
4
5
6
7
8globalContext = {
VO:{
scope:undefined,
checkscope: reference to function checkscope(){} //函数声明
},
Scope: [globalContext.VO], //作用域链
this:globalContext.VO
}checkscope
函数被创建,保存作用域链到函数的内部属性[[scope]]
中1
checkscope.[[scope]] = [globalContext.VO]
checkscope
函数被创建的同时,代码执行,根据代码内容修改变量对象的值1
2
3
4
5
6
7
8globalContext = {
VO:{
scope:"global scope",
checkscope: reference to function checkscope(){} //函数声明
},
Scope: [globalContext.VO], //作用域链
this:globalContext.VO
}执行
checkscope
函数,创建它的执行上下文,加入栈1
ECStack = [checkscopeContext,globalContext]
checkscope
函数执行上下文初始化:- 复制
checkscope
函数的[[scope]]
属性创建作用域链 - 用arguments创建活动对象
- 初始化活动对象,即加入形参、函数声明、变量声明
- 将活动对象压入
checkscope
作用域顶端
1
2
3
4
5
6
7
8
9checkscopeContext = {
AO:{
arguments:{length=0},
f:reference to function f(){}, //函数声明
scope: undefined, //变量声明,然后在执行代码的过程中,被修改为实际值"local scope"
},
Scope:[AO,globalContext.VO],
this:undefined //猜测可能是函数执行的时候才获得
}- 复制
f
被创建,保存作用域链到函数的内部属性[[scope]]
1
f.[[scope]] = [checkscope.AO, globalContext.VO]
checkscope
函数执行完毕,它的执行上下文从栈中退出1
ECStack = [globalContext]
执行
f
函数,创建它的执行上下文,加入栈1
ECStack = [fContext, globalContext]
.f 函数执行上下文初始化,第一步、复制函数
[[scope]]
属性创建作用域链,第二步、用arguments创建活动对象,第三步、初始化活动对象,即加入形参、函数声明、变量声明(这里f函数里面没有变量也没有函数),第四步、将活动对象压入checkscope
作用域顶端1
2
3
4
5
6
7fContext = {
AO:{
arguments:{length=0},
},
Scope:[AO, checkscope.AO, globalContext.VO],
this:undefined
}查找
scope
变量,在当前AO
活动对象中没有找到,沿着作用域链向上查找,在checkscope.AO
中找到了scope
。返回对应的值f
函数执行完毕,它的执行上下文从栈中退出1
ECStack = [globalContext]
全部代码执行完毕,全局上下文从栈中退出
1
ECStack = []
参考链接
mqyqingfeng/Blog: JavaScript深入系列、JavaScript专题系列、ES6系列、React系列。 (github.com)