JavaScript基础知识点
JavaScript 实现网站动态交互功能,提供网站动态交互性 一般应于html文档中
通常通过事件对网页进行交互
<script>
和</script>
之间的代码行包含了 JavaScript
const声明 声明作用域内的局部常量 常量的值不能通过赋值改变
var let声明 声明作用域内的局部变量 变量的值可以更改
var 可以多次声明相同名称的变量 let不需要
var 一般定义全局变量 , let一般用于局部变量
1 | const number = 42; |
1 | (function () { |
写一个简单 Hello World 程序,这里其他的都很简单,就两个点注意下,
**”use strict”**声明为严格模式,严格模式下不能调用为定义的变量,但该声明只对于局部作用域,即只在函数内生效
1 | x = 1; // 不报错 |
还有一个就是
1 | (function () { |
这个结构IIFE(立即调用函数表达式),在定义是就可以执行,也叫自执行匿名函数,这个可以有效避免全局污染
JavaScript是区分大小写的
通过var 和let定义的变量未赋值时为 undefined 数值类型环境中 undefined
值会被转换为 NaN
这里发现一个有意思的东西
当声明变量后,变量可以返回赋值,但进行变量提升后,变量重新被声明,覆盖前面的赋值(变量提升即先调用变量再定义函数,JavaScript对这种语法不会报错)
JavaScript对于函数的定义其实可以算有两种办法,一种就是常规的定义方法 声明为函数,另一种就是通过表达式进行赋值(函数表达式实际是定义一个匿名函数从而进行赋值)
这里就是对两种不同定义方法进行函数提升,对于函数声明可以进行提升,而表达式则会报错
遍历跟常见语句存在一定差别,for ... in ...
语句读取数组下标 , 遍历数组值需要使用 for ... of ...
语句
嵌套函数:函数内嵌套另一个函数 嵌套(内部)函数对其容器(外部)函数是私有的
闭包:闭包是可以拥有独立变量以及绑定了这些变量的环境的表达式(即封闭了表达式)
既然嵌套函数是一个闭包,就意味着一个嵌套函数可以“继承”容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域
简单来说内部嵌套的函数可以访问外部函数的参数和变量,外部函数不能访问内部函数的参数和变量,但可以访问到该嵌套函数
用个简单的代码来看更容易理解,这里要注意一个点
每一次对外部函数的调用实际上重新创建了一遍闭包。只有当返回的 inside
没有再被引用时,内存才会被释放
多层嵌套后,形成多个作用域,这里称之为作用域链,而作用域链中如果存在变量冲突,采取就近原则,即越近拥有越高的优先级
arguments对象
函数所有的实际参数会被存储到arguments对象,其类似一个数组
JavaScript存在连个特殊参数:默认参数和剩余参数
forEach()
方法提供了遍历数组元素的其他方法
但是遍历过程中遇到默认参数,元素不会被输出
稀疏数组中删除元素,元素删除,位置保留
在 JavaScript 中 objects 是一种引用类型。两个独立声明的对象永远也不会相等,即使他们有相同的属性,只有在比较一个对象和这个对象的引用时,才会返回 true
JavaScript 私有字段是以 #
(井号)开头的标识符号是这个字段名的必要部分,这也就意味着私有字段永远不会与公共属性发生命名冲突。为了在类中的任何地方引用一个私有字段,你必须在类体中声明它(你不能在类体外部创建私有字段)。除此之外,私有字段与普通属性几乎是等价的。
1 | class Color { |
JavaScript中继承有一个要注意的点,继承父类的析构函数时,需要先调用super()
来初始化 , this
这里大致相当于 this = new Color(r, g, b)
super()
之前也可以有代码,但你不能在 super()
之前访问 this
1 | class ColorWithAlpha extends Color { |
链式调用
1 | doSomething(function (result) { |
代码中同时调用多个方法时会出现判断语句过于冗杂的问题(一般称之为回调地狱)在JavaScript中提供了一个很好的办法Promise对象
1 | const promise = doSomething(); |
每个 Promise 都代表了链中的一个异步过程的完成
1 | function(参数).then(function1,function2) |
链式调用需要在链子的各个部分存在返回值,从而为下一个回调函数提供参数,若存在条件竞争状态, 下一个then
会被提前调用
嵌套是一种可以限制 catch
语句的作用域的控制结构写法,嵌套的 catch
只会捕获其作用域及以下的错误,而不会捕获链中更高层的错误
1 | doSomethingCritical() |
JavaScript为了最大程度的灵活性和效率,将类型化数组的实现拆分为缓冲和视图两部分,缓冲是一种表示了数据块的对象,它没有格式可言,也没有提供访问其内容的机制,视图提供了上下文——即数据类型、起始偏移量和元素数量
缓冲分为两种类型
ArrayBuffer
对象用来表示通用的原始二进制数据缓冲区,将 ArrayBuffer
传递给另一个执行上下文,它会被转移,原始的 ArrayBuffer
将不再可用
SharedArrayBuffer
对象用来表示一个通用的原始二进制数据缓冲区,类似于 ArrayBuffer
对象,但它可以用来在共享内存上创建视图。与可转移的 ArrayBuffer
不同,SharedArrayBuffer
不是可转移对象
缓冲无法直接读取,它只包含原始数据,需要通过视图来访问缓冲所表示的内存
缓冲支持以下操作:
- 分配:创建一个新的缓冲时,会分配一个新的内存块,并且初始化为
0
。- 复制:使用
slice()
方法,你可以高效地复制缓冲中的一部分数据,而不需要创建视图来手动复制每一个字节。- 转移:使用
transfer()
和transferToFixedLength()
方法,可以将内存块的所有权转移给一个新的缓冲对象。若你想要在不同的执行上下文间转移数据,又不想复制,这些方法就很有用。转移后,原始缓冲将不再可用。SharedArrayBuffer
不能被转移(因为缓冲已经被所有执行上下文共享)。- 调整大小:使用
resize()
方法,可以调整内存块的大小(只要不超过预设的maxByteLength
限制)。SharedArrayBuffer
只能增长,不能缩小。
视图主要也分为两种 类型化数组
和DataView
类型化数组
可以方便地转换二进制数据 DataView
更底层,可以精确控制数据的访问方式
buffer
视图所引用的底层缓冲。
byteOffset
视图相对于缓冲起始位置的偏移量(以字节为单位)。
byteLength
视图的长度(以字节为单位)
迭代器
在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能附带一个返回值,其返回值具有两个属性
value
迭代序列的下一个值
done
如果已经迭代到序列中的最后一个值,则它为 true
,如果 value
和 done
一起出现,则它就是迭代器的返回值
迭代器创建好后,迭代器对象可以通过重复调用 next()
显式地迭代,在产生终值后,对 next()
的额外调用应该继续返回 {done:true}
迭代器可以表示无限大小的序列,例如 0 和 Infinity
(全局属性 Infinity
是一个数值,表示无穷大)之间的整数范围
这里就用一个简单的例子来方便理解
1 | function makeRangeIterator(start = 0, end = Infinity, step = 1) { |
简单理解一下,其实迭代器就是一次遍历,每一次遍历都会对一个变量进行重新赋值,从而达到迭代的效果,同时确定输出值,但由于JavaScript的特性使得可以将方法赋值,所以称之为迭代器,
生成器
生成器其实是迭代器的一种强大替换,它允许你定义一个非连续执行的函数作为迭代算法
生成器函数使用 function*
语法(**function*
** 声明创建一个绑定到给定名称的新生成器函数。生成器函数可以退出,并在稍后重新进入,其上下文(变量绑定)会在重新进入时保存)编写
这样的话,我们优化一下前面那个函数定义
1 | function* makeRangeIterator(start = 0, end = Infinity, step = 1) { |
每调用一次,虽然都返回一个新的生成器,但每个生成器只能迭代一次,都是变量的值绑定,由上一次调用时改变的值确定
最初调用时,生成器函数不执行任何代码,而是返回一种称为生成器的特殊迭代器。通过调用 next()
方法消耗该生成器时,生成器函数将执行,遇到遇到 yield
关键字进行返回
对象实现可迭代必须具有一个键值为 Symbol.iterator
的属性(**Symbol.iterator
** 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of
循环使用)
然后我们利用for...of
循环去进行迭代
这个其实有一个在其他语言中不常见的语法,来展开迭代[...""]
,通过这种方法来展开数组
生成器的 return()
方法可返回给定的值并终结这个生成器
闭包
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合,闭包让开发者可以从内部函数访问外部函数的作用域
1 | function makeFunc() { |
内部函数 displayName()
在执行前,从外部函数返回,这里就可以很好的理解闭包的作用, 闭包是由函数以及声明该函数的词法环境组合而成的,该环境包含了这个闭包创建时作用域内的任何局部变量
displayName
的实例维持了一个对它的词法环境(变量 name
存在于其中)的引用,因此,当 myFunc
被调用时,变量 name
仍然可用,其值 Mozilla
就被传递到alert
中
1 | function showHelp(help) { |
数组 helpText
中定义了三个有用的提示信息,每一个都关联于对应的文档中的 input
的 ID,但无论焦点在哪个 input
上,显示的都是关于年龄的信息
原因是赋值给 onfocus
的是闭包。这些闭包是由他们的函数定义和在 setupHelp
作用域中捕获的环境所组成的。这三个闭包在循环中被创建,但他们共享了同一个词法作用域,在这个作用域中存在一个变量 item。这是因为变量 item
使用 var
进行声明,由于变量提升,所以具有函数作用域。当 onfocus
的回调执行时,item.help
的值被决定。由于循环在事件触发之前早已执行完毕,变量对象 item
(被三个闭包所共享)已经指向了 helpText
的最后一项
这里进行修改
1 | function showHelp(help) { |
这里就是利用JavaScript的特性,前面也有提到每调用一次都生成一个独立的环境,从而使得所有回调不再共享一个环境
利用匿名闭包也可以解决这一问题
1 | function showHelp(help) { |
原型链
每个对象,都有一个私有属性指向另一个名为原型(prototype)的对象,原型对象也有一个自己的原型,层层向上直到一个对象的原型为 null
。根据定义,null
没有原型,并作为这个原型链中的最后一个环节
用一个简单的例子来解释原型链
1 | const o = { |
1 | { a: 1, b: 2 } ---> { b: 3, c: 4 } ---> Object.prototype ---> null |
o具有属性 a 和 b
o.[[Prototype]] 具有属性 b 和 c
**o.[[Prototype]].[[Prototype]] **是 Object.prototype
**o.[[Prototype]].[[Prototype]].[[Prototype]] **是 null
当对象本身找不到相应属性时,就会去检查其原型,知道检索到null为止,为搜索到该属性,返回undefined
还有一个点就是,检索会优先访问本身的属性,当本身属性和原型的属性相冲突时,本身属性优先级高于原型属性, 从而实现属性遮蔽
1 | function Box(value) { |
一些隐式构造
默认情况下,Constructor.prototype
是一个普通对象——即 Object.getPrototypeOf(Constructor.prototype) === Object.prototype
。唯一的例外是 Object.prototype
本身,其 [[Prototype]]
是 null
——即 Object.getPrototypeOf(Object.prototype) === null
1 | function Constructor() {} |
原型链构造
语法构造
1 | const o = { a: 1 }; |
析构函数
1 | function Graph() { |
Object.create()
1 | const a = { a: 1 }; |
类
1 | class Polygon { |
Object.setPrototypeOf()
1 | const obj = { a: 1 }; |
proto
1 | const obj = {}; |