JavaScript
# 1. 同步和异步
同步:只有当前调用的方法执行完成并返回结果后,才能执行后续代码
异步:不用等待调用方法完成,而是直接执行后续代码
# 2. 数据类型
- 简单数据类型:Number、String、Boolean、null、undefined、Symbol、bigInt
- 复杂数据类型(引用数据类型):Object
# 存储方式
- 简单数据类型存储在栈中,大小固定,占据空间小
- 复杂数据类型存储在堆中,并在栈中存储了对应的引用地址,指向其在堆中的地址,大小不确定,一般占据空间大。
栈和堆的区别:
栈比堆内存小,但运行速度快; 堆是无序存储,根据引用获取
# 判断数据类型
typeof [] === typeof {} === typeof null === 'object'
typeof undefined === 'undefined'
typeof 123 === typeof NaN === 'number'
typeof '' === 'string'
判断是否为Array:Array.isArray(arr)
;
判断是否为null:val === null
null === null 结果为 true
undefined === undefined 结果为 true
NaN === NaN 结果为 false,要使用 Number.isNaN(num)
来判断是否为 NaN
null == undefined
结果为true,null只有与undefined、null 做 == 返回值会为true
Boolean([])、Boolean({}) 值为 true
# 3. 正则表达式 (RegExp) (opens new window)
创建正则表达式的两种方法:
var reg = new RegExp('\b+abc');
var reg = /\b+abc/;
\d 匹配数字, \s 匹配空格,+ 表示至少一个,*表示任意个(包括0),[] 表示范围,^ 表示以什么开头,$ 表示以什么结尾,? 表示 0 或 1 个
# 4. 数组方法
增
push: 在尾部增加一个或多个数据,返回增加后的长度,原数组改变 unshift: 在头部增加一个或多个数据,返回增加后的长度,原数组改变删
pop: 删除尾部第一个数据,返回删除的数据,原数组改变 shift: 删除头部第一个数据,返回删除的数据,原数组改变查
indexOf: 接收2个参数(value, start),从左到右进行查询,返回value的索引值,如果不存在则返回-1;start为可选值,表示开始查询的位置,默认从0开始。 lastIndexOf: 与indexOf相反,从右开始查询截取
slice: 接收2个参数(start,end),start必选,表示从哪里开始截取;end表示截取到哪个位置为止(不包含end),可省略,省略表示到最后一位。start和end均可为负数,表示从最后一位开始数。返回被截取的数组,原数组不变。
splice: 接收多个参数(start,num,data1,data2...),所有参数可选。不传入参数时,不进行任何操作;只传入start,表示从start位置(包含start)开始删除到数组结尾;传入start和num,从start开始删除num个值;传入多个,从start开始删除num个值,并将后续的参数插入到start位置。返回截取的数组,原数组改变遍历(均不会改变原数组)
forEach: 接收的参数为回调函数,回调默认接收3个参数: val(数组数据), index(索引值), self(遍历的数组)。 没有返回值,不会改变原数组
map: 接收参数同forEach。会将遍历所产生的返回值组成新数组并返回
filter: 接收参数同forEach。回调函数的返回值需要接收布尔值,会将返回值为true的数据组成新数组返回
every: 接收参数同forEach。回调函数的返回值需要接收布尔值,当返回值为true,会继续遍历;当返回值为false,则会结束遍历。只要有一个返回值为false,整个函数的返回值就为false;只有全部返回值为true,最终返回值才会为true
some: 接收参数同forEach。回调函数的返回值需要接收布尔值。与every相反,当返回值为true,结束遍历;当返回值为false会继续遍历。只要有一个返回值为true即最终返回true,否则返回false
reduce: 接收2个参数callback和initial(初始值,可省略),callback默认接收4个参数(pre,val,index,self)。当不传入initial时,遍历从第二项开始,此时pre的值为数组第一位,往后继续遍历时pre的值取决于callback的返回值;若传入initial,遍历从第一项开始,此时pre的值为initial。函数返回最后一次遍历时回调的返回值其他
sort: 排序。会改变原数组
concat: 连接多个数组,不改变原数组,返回连接后的数组
join: 接收一个参数(str),可选,默认为‘,’,将数组转换成字符串并以传入的参数作为间隔
reverse: 翻转数组,会改变原数组
toString: 类似于不传参的join函数,发生隐式类型转换时会自动调用该函数
# 5. 字符串方法
JavaScript 中字符串一旦创建就不会再改变,所有涉及改变字符串的操作都是在创建的副本上操作,所以也不会改变原字符串
- 操作方法
- 增:+、concat()
- 删:slice(start, end)、substr(start, end)、substring(start, end)
- 改:
去除空格:trim()
、trimLeft()
、trimRight()
重复:repeat(n)
, n表示字符串重复次数
填充:padStart(len, str)
、padEnd(len, str)
, 如果字符串长度小于len, 则用str在对应位置填充至len长度
大小写转换:toUpperCase()
、toLowerCase()
- 查
返回指定位置的字符:charAt(i)
返回指定字符串的位置:indexOf(str)、lastIndexOf(str)
判断是否以某字符串开头:startsWith(str)
判断是否包含某字符串:includes(str)
转换方法
split(str):将字符串按指定字符(串)分割成数组的每一项模板匹配方法
- match():接收一个正则表达式字符串或 RegExp 对象,返回数组
- search():接收参数同上,返回匹配的索引,找不到则返回 -1
- replace():接收2个参数,第一个为匹配的内容,第二个为替换的元素,返回替换后的字符串
# 6. 浅拷贝和深拷贝
只有引用类型才存在浅拷贝和深拷贝的区别。浅拷贝只复制对象的引用地址,即新老对象指向的是同一块内存;深拷贝是在堆中创建一个新的对象,使这个对象的值等于拷贝的对象的值。
- 浅拷贝
- Object.assign()
- Array.prototype.slice()、Array.prototype.concat()
- 使用扩展运算符实现的复制
- 深拷贝
- JSON.stringify()
缺点:会忽略 undefined、Symbol() 和 函数 - 手写循环递归
function deepClone(obj, hash = new WeakMap()) {
if (obj == null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
# 7. 闭包
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域
let demo = function(n){
let num = n;
return function(){
num++;
console.log(num);
}
}
let change = demo(1); // 返回的这个内部函数即为闭包
change() // 2
change() // 3
闭包的作用主要有2个:
- 延长生命周期
- 私有化变量
运用:柯里化函数,即是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术
let fn = function(a, b, c){
return a + b + c;
}
console.log(1, 2, 3) // 6
let curr = function(fn){
return function(a){
return function(b){
return function(c){
return fn(a, b, c);
}
}
}
}
console.log(curr(fn)(1)(2)(3)) // 6
缺点:使用不当会造成内存泄漏,当一个闭包不再使用时应将其赋值为 null
# 8. EventLoop
由于 JS 是单线程的,即 JS 每一个时刻只能执行一个任务,如果前一个任务需要执行很久,后一个任务就需要等待很长时间才能够执行。而这些耗时长的任务往往是诸如网络请求等 I/O 操作,这就会导致 CPU 会有很长一段时间是空闲的。
为了解决诸如此类的阻塞问题,JS 将任务分为同步和异步两种。
1)所有同步任务都由主线程执行,形成一个执行栈;
2)而异步任务会交由 Web Api 处理,而后进行到任务队列中;
3)当所有同步任务执行完毕,执行栈为空后,就会读取任务队列的队首事件进入执行栈,由主线程执行;
4)主线程会不断重复上述过程,也就是所谓的 事件循环(Event Loop)
异步任务可以进一步分为 宏任务 和 微任务,微任务会存储到任务队列中的微任务队列,当执行栈为空后,会先判断微任务队列是否为空,不为空则先执行完微任务队列中的事件,再执行宏任务。每执行完一个宏任务,也会先判断微任务队列是否为空,不为空先执行完微任务,再执行下一个宏任务。
常见宏任务有:1. script(整体代码); 2. setTimeout/setInterval; 3. setImmediate、I/O(node.js); 4. UI事件; 5. postMessage、messageChannel
常见微任务有:1. new Promise().then()(回调函数); 2. process.nextTick(node.js); 3. Proxy; 4. MutationObserve
# 9. 作用域链
JS 作用域可分为:
- 全局作用域:在程序的任何位置都可访问到
- 函数作用域:或称为局部作用域,只在函数内部可访问到
- 块级作用域:用 const 或 let 定义在 {} 内的变量,只在该 {} 内可访问
JS 遵循的是词法作用域,或者可叫做静态作用域,即变量在定义时,其作用域就已经确定下来了
在 JS 使用一个变量时,会先从当前作用域下去寻找,如果没有找到,会接着从它的上级作用域寻找,以此类推直到找到该变量或已经到了全局作用域。如果到最后都没找到该变量,就会在全局作用域隐式声明该变量或直接报错
# 10. this 指向问题 !!
绑定规则:
- 默认绑定:全局定义函数时,函数内部的 this 会默认绑定到全局对象 window,严格模式下为 undefined
- 隐式绑定:对象内部定义函数时,this 会指向该对象
- new 绑定:通过 new 创建对象,this 指向该实例对象
- 显示绑定:使用 call、bind、apply 函数改变 this 的指向
Tips:this 永远指向最后调用它的对象
箭头函数
- 没有自己的 this,而是继承上一层作用域的 this 指向,也无法使用 call、bind、apply 改变 this 指向
var name = 'parent'
var obj = {
name: 'son',
showName1: function(){
console.log('普通函数: ' + this.name)
},
showName2: () => {
console.log('箭头函数: ' + this.name)
}
}
obj.showName1() // 普通函数: son
obj.showName2() // 箭头函数: parent
obj.showName2.call(obj) // 该变this指向无效,输出->箭头函数:parent
- 没有 arguments
- 不能作为构造函数,和 new 一起用会抛出错误
var demo = () => console.log('demo');
new demo() // 报错,demo is not a constructor
# 11. arguments
概念: arguments 是用于接收函数所有参数的一个局部变量,它是一个类数组(即具有length 属性且可通过索引获取值,但没有数组的内置方法)
转成数组的方法:
- 使用
Array.from()
方法
let arr = Array.from(arguments)
- 使用扩展运算符
let arr = [...arguments]
- 使用 slice() + call/apply 实现
let arr = Array.prototype.call(arguments)
- 使用 concat() + call/apply 实现
let arr = Array.prototype.apply([], arguments)
# 11. call、apply、bind
call, apply, bind 都可用于改变 this 的指向,但这三者之间也有区别。
call 和 apply 一样接收都接收两个参数,第一个参数是 this 的指向,第二个参数是函数接收的参数,但 call 接收的参数列表,而 apply 接收的是数组形式。call 和 apply 改变 this 后会立即执行原函数,即只能临时改变 this 的指向一次;
bind 同样接收参数与 call 相同,但 bind 不会立即执行原函数,而是会返回一个改变 this 后的新函数。并且 bind 接收参数列表是可分成多次传入。
手写 bind 函数
Function.prototype._bind1 = function(target, ...arg1){
let self = this
return function(...arg2){
self.apply(target, arg1.concat(arg2))
}
}
// 使用箭头函数实现
Function.prototype._bind2 = function(target, ...arg1){
return (...arg2) => {
this.apply(target, arg1.concat(arg2))
}
}
# 12. 原型链
function check(left, right){
// 先判断是否为基础类型,如果是,直接返回 false
if(typeof left !== 'object' || left === null) return false;
var sign = right.portotype;
while(left._proto_ !== null)
{
if(left._proto_ === sign) return true;
else left = left._proto_;
}
return false;
}
# 13. new 操作符
function mynew(func, ...args)
{
const obj = {};
obj._proto_ = func.prototype;
let res = func.apply(obj, args);
return res instanceof Object? res: obj;
}
# 14. 变量提升和函数提升 (opens new window)
js 引擎在正式在正式执行前,会先将变量、函数的声明提升到其作用域的最顶端,函数的优先权是最高的,它永远被提升至作用域最顶部,然后才是函数表达式和变量按顺序执行
var foo = 3;
function hoistFunction() {
console.log(foo); // function foo() {}
foo = 5;
console.log(foo); // 5,由于所在作用域内存在foo,所以不会访问父作用域的foo
function foo() {} // 函数声明,会被提升至其所在作用域的顶部
var foo = function(){} // 函数表达式
console.log(foo) // function foo() {}
}
hoistFunction();
console.log(foo); // 3
# 15. ajax
function myajax(option)
{
// 创建 xmr 对象
const xmr = new XMLHttpRequest()
// 初始化数据
option = option || {}
const type = (option.type || 'GET').toUpperCase()
const param = option.data
// 发送请求
if(type === 'GET'){
xmr.open('GET', param === null? option.url: option.url + '?' + param, true)
xmr.send(null)
}else{
xmr.open('POST', option.url, true)
xmr.send(param)
}
// 接收请求
xmr.onreadystatechange = function(){
console.log(xmr.readyState)
if(xmr.readyState === 4){
var status = xmr.status
if(status >= 200 && status < 300){
console.log('获取数据成功')
console.log(JSON.parse(xmr.responseText))
}else{
console.log('连接失败')
}
}
}
}
myajax({
url: 'http://127.0.0.1:3007/article/all',
type: 'Get',
data: null
})
# 16. map 和 object 的区别
- map的key值可以是任意数据类型,而bject的key值只能为整数、字符串、 symbol
- map的元素顺序遵循插入的顺序,而object则没有这个特性
- map支持迭代,可以用 for..of.. 遍历,而 object 不行
# 17. JS 遍历方式的不同
for..in..
for..in.. 遍历的是索引,当用此遍历数组时,会将其原型上的可遍历属性也遍历出来,一般用来遍历对象
for..of..
遍历的是属性值,只能用于有迭代器对象的集合,如数组、字符串、map、set等,不能遍历对象
forEach
不能用break打断循环,没有返回值,return也无法生效
# 18. JS 为什么是单线程
JS 作为浏览器脚本语言,其主要用途是与用户互动和操作dom,如果 JS 是多线程,当两个线程同时对同一个 dom 元素进行操作,必然会造成冲突。如果要解决这种冲突,则JS必然会变得复杂,效率也会降低,给用户的体验也会下降
# 19. JS 错误捕捉
- try catch finally
只能捕捉到运行时的错误,无法捕获语法错误 - window.onerror 运行时错误、语法错误都能捕捉到
/*
message:错误信息(字符串)。
source:发生错误的脚本URL(字符串)
lineno:发生错误的行号(数字)
colno:发生错误的列号(数字)
error:Error对象(对象)
若该函数返回true,则阻止执行默认事件处理函数。
*/
window.onerror = function(message, source, lineno, colno, error) {
// onerror_statements
}
/*
ErrorEvent类型的event包含有关事件和错误的所有信息。
*/
window.addEventListener('error', function(event) {
// onerror_statements
})
# 20. Symbol类型 (opens new window)
概念
独一无二的值,即使传入相同的参数创建出来也不相同
用途
- 避免对象属性被修改或覆盖
- 模拟私有属性或方法
# 21. 事件模型
原始事件模型(DOM 0级)
- 事件绑定方式
let btn = document.getElementById('btn')
btn.onclick = fn
- 解绑方式
btn.onclick = null
- 特点
- 绑定速度快
- 只支持冒泡,不支持捕获哦
- 每个事件只能绑定一个监听函数,绑定多个时,后一个会覆盖前一个
标准事件模型(DOM 1级)
也是谷歌使用的事件模型,主要分为三个阶段:
1)事件捕获阶段:事件从 document
一直传到目标节点,期间经过的元素节点如果有监听函数,则触发
2)事件处理阶段:事件到达目标节点,触发回调函数
3)事件冒泡阶段:事件从目标节点冒泡到 document
, 期间检测经过的元素是否有监听函数,如果有则触发
- 绑定方法
addEventListener(EventType, callback, useCapture)
// 例子
btn.addEventListener('click', fn)
- 解绑方法
removeEventListener(EventType, callback, useCapture)
// 例子
btn.removeEventListener('click', fn)
- 特点
- 事件类型不用 + on
- 可通过设置
useCapture
表明是否在捕获阶段进行事件处理,默认为 false,即在冒泡时进行处理 - 同一个事件类型可添加多个回调函数
IE 事件模型
IE 事件模型只有事件处理和冒泡两个阶段
- 绑定
attachEvent(eventType, callback)
- 解绑
detachEvent(eventType, callback)
- 实例
let btn = document.getElementById('btn')
btn.attach('onclick', fn)
btn.detach('onclick', fn)
阻止冒泡和默认行为
- 冒泡
event.stopPropagation() // 不考虑兼容性
event.cancelBubble = false // IE9 以下版本
- 默认行为 点击a标签会跳转到对应页面、点击输入框获取焦点等这些属于默认行为
event.preventDefault() // 不考虑兼容性
window.event.returnValue = false // IE 低版本