JavaScript 简介
历史
Brendan Eich 10 days.
1995年诞生,为处理输入验证操作。(LiveScript–>JavaScript)
1997年,JavaScript 1.1 提交至欧洲计算机制造商协会(ECMA),并定义了 ECMAScript 新脚本语言标准。
……
定义
JavaScript 是一种轻量级的脚本语言。
JavaScript 是一种嵌入式(embedded)语言。本身提供的核心语法不算很多,只能用来做一些数学和逻辑运算。本身不提供任何与 I/O(输入/输出)相关的 API,都要靠宿主环境(host)提供,所以 JavaScript 只合适嵌入更大型的应用程序环境,去调用宿主环境提供的底层 API。
目前,已经嵌入 JavaScript 的宿主环境有多种,最常见的环境就是浏览器,另外还有服务器环境,也就是 Node 、Deno等等。
组成
核心 (ECMAScript)
已经不断更新多次,到现在常用 ES5/ES6,直到如今更高版本。
1997年7月 ECMAScript 1.0 1998年6月 ECMAScript 2.0 1999年12月 ECMAScript 3.0 2007年10月 ECMAScript 4.0 2009年12月 ECMAScript 5.0 2011年6月 ECMAscript 5.1 2015年6月 ECMAScript 6(2015)
文档对象模型 (DOM)
- Document Object Model——文档对象模型)是用来呈现以及与任意 HTML 或 XML文档交互的API。DOM 是载入到浏览器中的文档模型,以节点树的形式来表现文档,每个节点代表文档的构成部分(例如:页面元素、字符串或注释等等);我们可以通过接口可以轻松的对节点进行添加、删除等操作。
浏览器对象模型 (BOM)
- Browser Object Model: 提供与浏览器进行交互的方法和接口。其标准并没有确切的,会因不同浏览器而异。
HTML 中应用 JavaScript
在HTML中以 <script></script>
标签内嵌,也可通过src
来引入外部的一个.js
文件.
Hello World
function hello(){ |
放置位置
在这之前我们需要了解HTML页面加载渲染的过程:
- 浏览器通过HTTP协议请求服务器,获取下载HMTL文档并开始从上到下解析,构建 DOM;
- 在构建 DOM 的过程中,如果遇到外联的样式声明和脚本声明,则暂停文档解析,下载样式文件和脚本文件;
- 样式文件下载完成后,构建 CSSDOM;脚本文件下载完成后,解释并执行,然后继续解析文档构建 DOM
- 完成文档解析后,将 DOM 和 CSSDOM进行关联和映射,最后将视图渲染到浏览器窗口。
- 置于
<head></head>
标签中- 在加载页面前,需要先将所有的JS代码下载、解析、执行完,才会去编译页面内容。但会导致页面出现空白,因为代码解析执行是会有相应的时间,相当于处于等待状态。
- 置于
<body></body>
标签中- 在页面代码前同样会出现在head标签中的问题;在页面代码后完美解决这个问题,当然也可以在script标签中添加
async
和defer
关键字来指定其是异步加载的,可以先加载页面再去处理JS代码,但同时有async
标记的则不会保证其执行顺序。
- 在页面代码前同样会出现在head标签中的问题;在页面代码后完美解决这个问题,当然也可以在script标签中添加
defer && async
Explanation
defer
:用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。只适用于外联脚本,保证了彼此的先后顺序。async
:HTML5新增属性,用于异步下载脚本文件,下载完毕立即解释执行代码。不能确保彼此的先后顺序,会在load
事件之前执行。
Useage
- 将脚本引用放置在DOM下方,紧接在您的
</body>
元素上方。 - 除非您要创建供他人使用的库,否则不要通过侦听 DOMContentLoaded或load 事件来使代码复杂化。相反,请参阅上一点。
- 使用 defer属性标记引用外部文件的脚本。
- 如果您的代码不依赖于加载DOM并作为对文档中其他脚本的准备工作的一部分来运行,则可以将此脚本放置在页面顶部,并 在其上设置async属性。
noscript
有时候我们需要提醒用户的浏览器暂不支持script,当然现在主流浏览器基本上都已经支持并丰富了很多操作和功能,但是我们需要知道知识点。
<noscript> |
基本语法
涉及到的其他程序语言共同处不再陈述。
数据类型
- Number
- Boolean (true/false)
- String
- undefined (未定义)
- null (空)
- Object
- Object
- Array
- Function
- Symbol (ES6)
变量
var a; |
变量名
,即标识符。大小写敏感,不能数字开头即可
var
存在变量提升(所有的变量的声明语句,都会被提升到代码的头部)(JavaScript 引擎的工作方式是:先解析代码,获取所有被声明的变量,然后再一行一行地运行);而let
则不会,只是在其作用域起效;同时也不能在同一作用域重复声明。
function f() { |
常量
const name = 'tadm'; |
const
实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const
只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
Number
JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此.
上述换句话说:JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数);这样对于浮点数的计算是非常危险的,因为计算并不是非常准确。
64 位浮点数
- 第 1 位:符号位,
0
表示正数,1
表示负数 - 第 2 位到第 12 位(共 11 位):指数部分
- 第 13 位到第64位(共 52 位):小数部分(即有效数字)
符号位决定了一个数的正负,指数部分决定了数值的大小,小数部分决定了数值的精度。
指数部分一共有11个二进制位,因此大小范围就是0-2047。IEEE 754 规定,如果指数部分的值在0-2047之间(不含两个端点),那么有效数字的第一位默认总是1,不保存在64位浮点数之中。也就是说,有效数字这时总是
1.xx...xx
的形式,其中xx..xx
的部分保存在64位浮点数之中,最长可能为52位。因此,JavaScript 提供的有效数字最长为53个二进制位。(-1)^符号位 * 1.xx…xx * 2^指数部分
- 第 1 位:符号位,
绝对值小于2的53次方的整数,即
-2^53
到2^53
,JS都可以精确表示
表示范围:
-Infinity
,-0
,0
,2^-1023
(开区间) ~ 2^1024
,Infinity
进制:
- 十进制:没有前导0的数值。
- 八进制:有前缀
0o
或0O
的数值,或者有前导0、且只用到0-7的八个阿拉伯数字的数值。- 严格模式(”use strict”)无效,会使 JS 引擎抛出错误
- 十六进制:有前缀
0x
或0X
的数值。 - 二进制:有前缀
0b
或0B
的数值。
parseInt():
将字符串转为整数
parseInt('hello1234ddd')
NaN
parseInt('1234ddd')
1234如果不是字符串时,则会先将其转换为字符串,再转为整数
parseInt(123) // parseInt(String(123))
123也可进行进制转换(返回十进制):
parseInt('10',2) --> 2
,如果输入了不包含当前进制的则会返回NaN
parseFloat():
- 将一个字符串转为浮点数
- 若字符串符合科学计数法,则会进行相应的转换
特殊值:
+0(0)
-0
+Infinity (Infinity)(x/+0,Infinity/0,Infinity + Infinity ,Infinity * Infinity )
-Infinity (x/-0)
NaN (Not a Number)
- 字符串解析成数字出错
- 某些不符合正常的函数运算结果(0/0)
- 0 * Infinity,Infinity - Infinity ,Infinity 、 Infinity
- NaN === NaN —> false
- [NaN].indexOf(NaN) —> -1 (indexOf 严格相等)
Infinity
与NaN
比较,总是返回false
isNaN(): 判断一个值是否为
NaN
- NaN –> true
- !Number –> isNaN(Number(x))
- 空数组和只有一个数值成员的数组 –> false
isFinite(): 返回一个布尔值,表示某个值是否为正常的数值。
只有以下才为
false
:- +-Infinity
- NaN
- undefined
位运算符:
String
零个或多个排在一起的字符,放在单引号或双引号之中;默认写在一行,如果需要多行,则需要行末添加反斜杠
\
(此时输出仍在一行内)
其
length
属性返回字符串长度,且不能被更改
toString():
- 将非字符串转换为字符串(为数值时可传入进制数)
反斜杠
转义
\HHH
反斜杠后面紧跟三个八进制数(
000
到377
),代表一个字符。HHH
对应该字符的 Unicode 码点,比如\251
表示版权符号。显然,这种方法只能输出256种字符。\xHH
\x
后面紧跟两个十六进制数(00
到FF
),代表一个字符。HH
对应该字符的 Unicode 码点,比如\xA9
表示版权符号。这种方法也只能输出256种字符。\uXXXX
\u
后面紧跟四个十六进制数(0000
到FFFF
),代表一个字符。XXXX
对应该字符的 Unicode 码点,比如\u00A9
表示版权符号。
字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从
0
开始);但不能用于增删改操作。
字符集:
JavaScript 使用 Unicode 字符集。JavaScript 引擎内部,所有字符都用 Unicode 表示。当然还允许直接在程序中使用 Unicode 码点表示字符,即将字符写成
\uxxxx
的形式,其中xxxx
代表该字符的 Unicode 码点。
解析代码的时候,JavaScript 会自动识别一个字符是字面形式表示,还是 Unicode 形式表示。输出给用户的时候,所有字符都会转成字面形式。
字符内部存储:
- 每个字符在 JavaScript 内部都是以16位(即2个字节)的 UTF-16 格式储存
但是由于历史原因,初始设计时并未考虑到现在使用了如此多的字符!所以对于码点在U+10000
到U+10FFFF
之间的字符,JavaScript 总是认为它们是两个字符(length
属性为2)。所以处理的时候,必须把这一点考虑在内;也就是说,JavaScript 返回的字符串长度可能是不正确的。
Null && Undefined
null
表示空,即空对象指针undefined
表示未定义,一般是声明了变量但并未对其初始化,该变量则为undefined
;派生自null
typeof null // "object"
null == undefined // true
null === undefined // false
Array
基本知识
一组值(任何类型)按次序排列(!排序),同C,JAVA等中的数组一样
注意:本质上也是一种特殊的对象(typeof [1,2] –> “object”)
arr[1] —> arr[‘1’] —> 2
length
属性是可写的:
- 数组的数字键不需要连续,
length
属性的值总是比最大的那个整数键大1
。另外,这也表明数组是一种动态的数据结构,可以随时增减数组的成员。 - 人为设置一个小于当前成员个数的值时,该数组的成员会自动减少到
length
设置的值(设置为0时则相当于清空数组)。 - 人为设置一个大于当前成员个数的值时,读取新增的位置数据时为
undefined
如果数组的键名是添加超出范围的数值,该键名会自动转为字符串;同时通过方括号([-1])仍可以获得其值,但此时
length
还是为0
in:
- 返回Boolean,判断某个元素是否存在于数组中
for…in…:
遍历数组(但是会遍历他的所有键)
var a = [1, 2, 3];
a.foo = true;
for (var key in a) {
console.log(key);
}
// 0
// 1
// 2
// foo以后通常使用
forEach
、map
、for...of...
、reduce
、reduceRight
……
类型检测
判断一个变量是否为数组:
- x instanceof Array
- Array.isArray(x)
栈方法(LIFO)
- push(x, …) — 入栈并返回修改后数组的 length
- pop() — (最后一项)出栈并返回被移除的项
队列方法(FIFO)
- unshift(x, …) — 入队并返回 length
- shift() — (第一项)出队并返回被移除的项
重排序
- x.sort()
- x.reverse()
操作方法
- x.concat(y,…)
- x.slice(start, [end]) — 返回新数组且不会影响原数组
- x.splice()
- x.splice(start, delNumber) — 从 start 开始删除 delNumber 项
- x.splice(start, 0, insert1,….) — 从 start 开始插入 多项
- x.splice(start, delNumber, insert1,…) — 从 start 开始删除 delNumber 项并插入多项,类似于替换效果
查找项的位置
- x.indexOf(item, [start]) — 从前往后
- x.lastIndexOf(item, [start]) — 从后往前
从数组中查找第一个符合条件的,若找到返回其索引,否则返回 -1 且在查找时使用 ‘===’ 进行判断。
Object
基本知识
let obj = { |
属性
Object.keys(obj) // 查看对象的所有自身可枚举属性 |
定义属性
Object.defineProperty
let obj = { |
以上为 false 时,我们仍要操作时在非严格模式下会将其忽略,严格模式下抛出错误。而且,一旦(configurable)属性定义为不可配置,就不能再修改为可配置。
var pattern = { |
get(only) — readOnly set(only) — writeOnly
Object.defineProperties
let obj = {} |
读取属性
Object.getOwnPropertyDescriptor
返回指定对象上一个自有属性对应的属性描述符(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)。如果指定的属性存在于对象上,则返回其属性描述符对象(property descriptor),否则返回 undefined 。
var o, d; |
Object.getOwnPropertyDescriptors
获取一个对象的所有自身属性的描述符。
原型模式中例子理解。
Create Object
工厂模式
快速创建多个相似对象
function createPerson(name, age){ |
构造函数模式
function Person(name, age){ |
原型模式
function Person(){}; |
组合使用构造函数模式和原型模式
非常常用,推荐!
function Person(name, age){ |
动态原型模式
function Person(name, age){ |
该模式时不能使用字面量形式重写原型,同样会切断实例和原型的联系。
寄生构造函数模式
不推荐,了解即可
稳妥构造函数模式
function Person(name, age){ |
此时没有this 和 new 操作,只能通过 sayName() 来访问 name,为对象的数据成员提供了一个’稳妥’的环境。
而且同寄生构造函数模式一样,创建的对象与构造函数或者其原型没有关系。
Extends
组合继承(常用)
function SuperType(name){ |
缺点:无论什么情况下都会调用两次超类型的构造函数(一次是在创建子类原型时,第二次是子类型构造函数内),这样就会产生不必要的、多余的属性;而且子类型会包含超类型所有的属性。
原型式继承
function object(o){ |
同原型模式一样,引用类型值的属性会被实例所共享。
寄生式继承
function object(o){ |
寄生组合式继承(最理想)
构造函数继承属性,原型链的混成形式继承方法。
function object(o){ |
类似数组的对象
// arguments对象 |
如果我们想要将其变为真正的数组:
var arr = Array.prototype.slice.call(arrayLike); |
More
Function
Basic
JavaScript 语言中称函数为第一等公民(函数与其他数据类型地位平等)
// 声明定义 |
函数重复声明定义时会进行重载覆盖操作
声明定义方式与函数表达式方式区别:
- 声明定义可在声明前调用。因为此时函数名类似于变量名(变量提升),整个函数则会被提升到代码头部。然而函数表达式(赋值)方式则不能在声明前调用,会抛出错误。
- 但是采用
function
命令和var
赋值语句声明同一个函数,由于存在函数提升,最后会采用var
赋值语句的定义。
var myFunc = function () {}; |
函数作用域
全局作用域
函数作用域(外部不能访问读取修改)
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。
var
命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。优先读取所在作用域变量,若没有才会沿上一层查找。(作用域链)
function foo(x) {
if (x > 100) {
var tmp = x - 100;
}
}
// 等同于
function foo(x) {
var tmp;
if (x > 100) {
tmp = x - 100;
};
}
块级作用域(ES6)
闭包 (重点)
因为JS
存在”链式作用域”,所以子对象会层级的向上寻找所需变量,同时父对象所有的变量对其子对象是可见的(很好的理解全局作用域)。
能够读取其他函数内部变量的函数(定义在一个函数内部的函数)
用途:
可以读取函数内部的变量
让这些变量始终保持在内存中(JS 有内存回收机制),使得它诞生环境一直存在
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
立即调用的函数表达式(IIFE)
自调用匿名函数
(function(){ /* code */ }()); |
用途:
- 避免污染全局变量
- IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
eval (不常用,了解)
接受一个字符串作为参数,并将这个字符串当作语句执行
eval('var a = 1;'); |
typeof
typeof 1 // "number" |
instanceof
返回一个布尔值,表示对象是否为某个构造函数的实例(检查整个原型链);只能用于对象,不适用(七种)原始类型的值.
var v = new Vehicle(); |
基本结构体
if (a === 0) { |
RegExp
变量、作用域、内存
复制变量值
基本类型
- 会在变量对象上创建一个新值,再将其复制到为新变量分配的位置上。因此我们操作新变量时不会影响原来的变量。
引用类型
- 同基本类型原理一样,不过此值是一个指向变量值存储在堆(基本类型放于栈内存,Object等放于堆内存)中的地址的指针。此时他们两个就指向同一个内存地址,因此会互相影响。
传递参数
按值传递(将函数外的值传入函数内同变量(基本类型与引用类型)间的复制一样)
一般我们可以在函数内通过 arguments
对象来访问传递到函数的参数数组(arguments[0,1,…]),通过arguments.length
来获取参数个数。
类型检测
- typeof
- instanceof
执行环境及作用域
执行环境定义了变量或者函数有权访问的其他数据,决定了他们各自的行为。
代码在一个环境中执行时,会创建变量对象的一个作用域链。其用途是保证对执行环境有权访问的变量和函数的有序访问。
没有块级作用域
if(true){ |
使用 var 声明的变量会自动被添加到最接近的环境中。
- 函数内部 —- 函数的局部环境
- with 语句 — 函数环境
- 未使用 var 声明 — 该变量自动添加到全局环境
垃圾回收
标记清除
- 进入环境 — 不回收
- 离开环境 — 回收
引用计数(被引用的次数)
window.CollectGarBage()
解除引用
let a = 2 |
解除引用并不是自动回收该值所占用的内存,而是让该值脱离执行环境,以便垃圾回收器下次运行时将其回收。
引用类型
后续持续更新,敬请期待!