系列文章目录
目录
一、基础部分
1. 数据类型
分类:
string,boolean,number,null,undefined,object,symbol,bigint
- Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
- BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
分为基本(原始)数据类型和引用数据类型。
基本数据类型,存的是值,存储在栈当中,占据空间小、大小固定,属于被频繁使用数据。
引用数据类型,存的是地址,存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
判断类型方式:
- typeof:判断基本数据类型,null会被判断为object
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof []); // object
console.log(typeof function(){}); // function
console.log(typeof {}); // object
console.log(typeof undefined); // undefined
console.log(typeof null); // object
typeof NaN; // "number"
- instanceof:判断引用数据类型,内部运行机制是判断在其原型链中能否找到该类型的原型,不能判断基本数据类型
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
- constructor:有两个作用,一是判断数据的类型,二是对象实例通过
constrcutor
对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor
就不能用来判断数据类型了
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
- Object.prototype.toString.call():使用Object的原型方法toString来判断数据类型
同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。
isNaN 和 Number.isNaN 函数的区别
- 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。
- 函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。
2. ES6新增
- 1. let const
- 2. 模板字符串
- 3. 解构赋值
- 4. 展开运算符
- 5. 箭头函数
- 6. 类和继承
1. let const var
1. var声明的变量会挂载在window上,而let和const的不会
2. var声明的变量存在变量提升,let和const不存在,必须先定义后使用
3. let和const声明形成块级作用域
块级作用域由{ }包含,解决了ES5中,外层变量可能被内层变量覆盖,用来计数的循环变量
泄漏为全局变量。
如果块级作用域内存在const或let命令,他所声明的变量就绑定了这个区域,不再受外部影
响------->暂时性死区
4. 同一作用域下let和const不能声明同名变量(不能重复声明),而var可以
5. 使用let/const声明的变量在当前作用域存在暂时性死区
6. const一旦声明必须赋值,不能使用null占位,声明之后不可以再修改,如果声明的是引用数据类型,可以修改其属性(指针不可以改变,指向地方的内容可以改变),let,var可以不设置初始值
7. let的指针指向可以改变, const不可以
2.箭头函数
箭头函数与普通函数的区别:
- 更简洁
- 箭头函数不会自己创建this,他会在自己作用域的上一层继承this(会继承上一层普通函数的this),箭头函数的this在定义时就已经确定了,箭头函数继承来的this指向永远不会改变
- 箭头函数不能作为构造函数使用
- 箭头函数没有自己的arguments
arguments 是一个对应于传递给函数的参数的类数组对象,arguments对象是所有(非箭头)
函数中都可用的局部变量。arguments对象是一个伪数组. 除了length和索引外,不能用任何
数组的方法
- 箭头函数没有prototype
- 箭头函数不能用作Generator函数,不能使用yeild关键字
3. new操作符的实现原理
new的执行过程
- 创建了一个空对象
- 将构造函数的的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
- 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)
- 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。上面的第二三步箭头函数都无法执行
4.数组相关
数组的遍历方法:
- forEach:没有返回值 对数组数据操作的话会改变原数组
- map:不改变原数组 有返回值 可以链式调用
- filter:过滤数组,返回符合条件的元素的数组
- for...of:无法遍历不可迭代对象,
- 在遍历对象时,for...of:遍历的是值,for...in:遍历的是key
- for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链
- every和some:参数是函数,some只要函数有一个返回true就为true,every都为true,才为true
- find和findIndex:find返回第一个符合条件的值,findIndex返回第一个符合条件的索引(找到之后就停止遍历了)
- reduce和reduceRight:reduce对数组正序操作,reduceRight对数组逆序操作
reduce()
arr.reduce( function (prev, cur, index, arr) {}, init )
arr:表示原数组
prev:表示上一次调用回掉时的返回值,或者初始值init
cur:表示正在处理的数组元素
index:若提供了init值,则索引为0,否则为1,表示当前正在处理的元素索引
init:初始值
- 求和
let sum = arr.recude(function (prev,cur) {
return prev + cur
}, 0)
- 求最大值
- 计算数组中元素出现的次数
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
let nameNum = names.reduce((pre,cur)=>{
if(cur in pre){
pre[cur]++
}else{
pre[cur] = 1
}
return pre
},{})
console.log(nameNum); //{Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
- 数组去重
let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((pre,cur)=>{
if(!pre.includes(cur)){
return pre.concat(cur)
}else{
return pre
}
},[])
console.log(newArr);// [1, 2, 3, 4]
- 将多维数组转化为一维
二维转一维
let arr = [[0, 1], [2, 3], [4, 5]]
let newArr = arr.reduce((pre,cur)=>{
return pre.concat(cur)
},[])
console.log(newArr); // [0, 1, 2, 3, 4, 5]
递归let arr = [[0, 1], [2, 3], [4,[5,6,7]]]
const newArr = function(arr){
return arr.reduce((pre,cur)=>{
pre.concat( Array.isArray(cur) ? newArr(cur) : cur )
},[])
}
console.log(newArr(arr)); //[0, 1, 2, 3, 4, 5, 6, 7]
数组去重:
- 双层循环:外层循环元素,内层循环时比较值,相同就跳过,不同就push到数组当中
- 利用splice直接操作原数组:相同就删除
- forEach+indexof
- ES6的set:return Array.from(new Set(array)) / return [ ...new Set(array) ]
- includes:看是否包含,不包含push到数组当中
- Map数据结构:创建一个空的Map,遍历需要去重的数组,把数组元素当作key存入,因为Map不会存在相同的key,所以遍历后得到的就是去重后的数组
5. Map和Set
在js当中对象是一组键值对,但是键必须是字符串,就有很大的限制,所以Map出现了,key不仅可以是字符串,也可以是其他类型的值,包括对象。
Map有着极快的查找速度,Set与Map类似,但是只存key,不存value,且key不重复,重复的元素会被自动过滤。
Map的操作方法:
- size:返回Map的成员总数
- set( key, value ):设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。
- get( key ):读取key对应的value,读取不到的话,就返回undefined
- has( key ):返回布尔值,表示某个key是否在Map当中
- delete( key ):删除某个key,返回是否删除成功
- clear():清除所有成员,没有返回值
Map的遍历器:
- keys():返回键名
- values():返回键值
- entries():返回所有成员
- forEach():遍历Map的所有成员
WeakMap:
也是键值对的存储,键是弱引用的。其键必须是对象,原始数据类型不能作为key值,而值可以是任意的。操作方法与Map基本一致,clear被弃用,可以通过创建一个空的WeakMap并替换原对象来实现清除。
WeakMap的设计目的在于,有时想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存。而WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
6. AJAX
ajax指的是通过js异步通信,从服务器获取XML文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个页面。是指一种创建交互式网页应用的网页开发技术。它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。
步骤:
- 创建一个 XMLHttpRequest 对象。
- 在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。
- 在发起请求前,可以为这个对象添加一些信息和监听函数。
- 当对象的属性和监听函数设置完成后,最后调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体。
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", url, true);
// 设置状态监听函数
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 当请求成功时
if (this.status === 200) {
handle(this.response);
} else {
console.error(this.statusText);
}
};
// 设置请求失败时的监听函数
xhr.onerror = function() {
console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);
使用Promise封装:
// promise 封装实现:
function getJSON(url) {
// 创建一个 promise 对象
let promise = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
// 新建一个 http 请求
xhr.open("GET", url, true);
// 设置状态的监听函数
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 当请求成功或失败时,改变 promise 的状态
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
// 设置错误监听函数
xhr.onerror = function() {
reject(new Error(this.statusText));
};
// 设置响应的数据类型
xhr.responseType = "json";
// 设置请求头信息
xhr.setRequestHeader("Accept", "application/json");
// 发送 http 请求
xhr.send(null);
});
return promise;
}
7.ajax、axios、fetch的区别
- ajax:它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。
- 本身是针对MVC编程,不符合前端MVVM的浪潮
- fetch:es6出现,Fetch是基于promise设计的,fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。
- 优点: 基于标准 Promise 实现,支持 async/await
- 缺点: fetch只对网络请求报错,对400,500都当做成功的请求;fetch默认不会带cookie
- axios:是一种基于Promise封装的HTTP客户端
- 浏览器端发起XMLHttpRequests请求
- node端发起http请求
- 支持Promise API
- 监听请求和返回
- 对请求和返回进行转化
- 取消请求
- 自动转换json数据
- 客户端支持抵御XSRF攻击
8. 常见的DOM操作:
-
创建:
- document.createElement('span')
- 父节点.appendChild(子节点) // 把子节点塞到父节点当中
-
删除:
- 父节点.removeChild(子节点)
-
交换:
- 父节点.insertBefore(子节点1,子节点2) // 把子节点1置于子节点2之前
9. 原型和原型链:
ES5 中新增了一个 Object.getPrototypeOf() 方法,可以通过这个方法来获取对象的原型。
当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。
多个对象之间通过_ _proto_ _连接起来,就是原型链
原型链的终点是Object.prototype._ _proto_ _,而Object.prototype._ _proto_ _ === null,所以原型链的终点就是null。
使用hasOwnProperty() 可以用来判断属性是否属于原型链属性。
10. 作用域链:
(1)执行上下文:
- 全局:不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文。
- 函数:当一个函数被调用时,就会为该函数创建一个新的执行上下文,函数的上下文可以有任意多个。
- eval:执行在eval函数中的代码会有属于他自己的执行上下文,不过eval函数不常使用
创建执行上下文:分为创建阶段和执行阶段
- 创建阶段:
- this绑定
- 创建词法环境组件
- 创建变量环境组件
- 执行阶段:完成对变量的分配,执行完代码
(2)作用域:
- 全局作用域
- 函数作用域:函数内部可以访问外层的,外层的访问不到内部的
- 块级作用域:let,const可以声明块级作用域
(3)作用域链:
在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这一层层的关系就是作用域链。
作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。
11. 闭包:间接的访问到函数内部的变量
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,函数A将函数B返回出去,在外部通过函数B访问到函数 A 中的变量。
特点:
- 作用域空间不销毁
- 优点:因为不销毁,变量不会销毁,增加了变量的生命周期
- 因为不销毁,会一直占用内存,多了以后就会导致内存溢出
- 可以利用闭包,在一个函数外部,访问函数内部的变量
- 保护私有变量
- 优点:可以把一些变量放在函数里面,不会污染全局
- 缺点:要利用闭包函数才能访问,不是很方便
12. this指向
直接调用函数的话,this指向全局window
函数作为某个对象的属性来调用时,this指向调用他的对象
new之后,this被永远绑定在了实例对象上,不会被任何方式改变
箭头函数的this是继承来的,继承自包裹箭头函数的第一个普通函数的this,一旦被绑定,就不会被任何方式改变
改变this指向
- call: 函数名.call(this要绑定的对象,像函数传递的参数(依次传入即可))
- apply:函数名.apply(this要绑定的对象,[像函数传递的参数](数组))
- bind:通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。
若对一个函数多次bind,函数的this指向由第一次bind决定
13. 异步编程
1. 异步的解决方法:
- 回调函数,回调地狱
- 事件发布订阅
- Promise:
- 三种状态Pending,Resolved,Rejected。只有异步操作的结果可以修改Promise的状态,状态一旦改变,就不会再改变
- Promise的实例有两个过程:
- pending -> fulfilled : Resolved(已完成)
- pending -> rejected:Rejected(已拒绝)
- 解决了回调地狱,
- 缺点:
- 不能取消Promise,一旦新建它就会立即执行,无法中途取消
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
- Generator:
- es6提出,在函数名之前加*,函数内部使用yield语句,next()执行
- 结合co模块可以实现自动执行
- async/await:
- Generator语法糖,async返回一个Promise
- 内置了co模块
2. Promise方法:
Promise有五个常用的方法:then()、catch()、all()、race()、finally
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved
时调用,第二个回调函数是Promise对象的状态变为rejected
时调用。then
方法返回的是一个新的Promise实例(不是原来那个Promise实例)- catch相当于
then
方法的第二个参数,指向reject
的回调函数。不过catch
方法还有一个作用,就是在执行resolve
回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch
方法中。 -
all
方法可以完成并行任务, 它接收一个数组,数组的每一项都是一个promise
对象。当数组中所有的promise
的状态都达到resolved
的时候,all
方法的状态就会变成resolved
,如果有一个状态变成了rejected
,那么all
方法的状态就会变成rejected
。(所有promise返回成功才成功,否则为失败)失败的时候返回最先被Reject失败状态的值 -
race类似于all,它接收一个数组,数组的每一项都是一个
promise
对象,第一个promise返回成功则为成功,反之为失败。(只看第一个最先返回promise对象的值) -
finally
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。不接受任何参数。
14. 面向对象
1. 对象的创建方式
- 工厂模式:用函数将创建对象的过程封装起来,通过调用函数来创建对象,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系
- 构造函数模式:构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建立起了联系,因此可以通过原型来识别对象的类型
- 原型模式:使用原型对象来添加公用属性和方法,从而实现代码的复用
2. 对象的继承方式
(1)原型链
(2)构造函数
(3)组合继承
(4)寄生式继承
(5)寄生式组合继承
15. 垃圾回收
Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。
1. 方式:
- 标记清除:当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放
- 引用计数:引用计数就是跟踪记录每个值被引用的次数。当这个引用次数变为0时,说明这个变量已经没有价值,因此,在在机回收期下次再运行时,这个变量所占有的内存空间就会被释放出来。这种方法会引起循环引用的问题,这种情况下,就要手动释放变量占用的内存。
2.减少垃圾回收
- 对数组进行优化: 在清空一个数组时,最简单的方法就是给其赋值为[ ],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的。
- 对
object
进行优化: 对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收。 - 对函数进行优化: 在循环中的函数表达式,如果可以复用,尽量放在函数的外面。
3.内存泄漏:
- 意外的全局变量: 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
- 被遗忘的计时器或回调函数: 设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
- 脱离 DOM 的引用: 获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
- 闭包: 不合理的使用闭包,从而导致某些变量一直被留在内存当中。