一、数据类型(引用类型,基本类型)
根据储存条件区分为引用类型:Object, Array, RegExp, Date, Function, Math
基本类型: string number undefined, null, boolean, bigint, symbol
基本类型储存在栈中,引用类型储存在堆中
typeof 可以检测基本数据类型 对于引用类型只能区分出Object和function
instanceof可以检测引用类型,原理是基于原型链的查询
二、闭包,原型链,作用域链
闭包是指有权访问另外一个函数作用域中的变量的函数,当前环境中存在指向父级作用域的引用
过度使用闭包有可能会造成内存的泄露,浏览器的垃圾回收原理(闭包相互引用,不会被回收)
三、js的继承 class
call 原型链 组合继承
四、ES NEXT
let const ; 箭头函数, class, promise; 解构赋值; …操作符;set map对象;asnyc await; generator; export import;
五、js中的this指向问题
1.全局上下文默认指向window
2.直接调用函数,this相当于全局上下文
3.obj.fn的方式调用,this指向这个对象
4.DOM事件绑定,this默认指向绑定事件的元素
5.new 构造函数,this指向实例对象
6.箭头函数,this会指向当前最近的非箭头函数的this;
let obj = {
a: function() {
let do = () => {
console.log(this);
}
do();
}
}
obj.a(); // 找到最近的非箭头函数a,a现在绑定着obj, 因此箭头函数中的this是obj
new 一个对象做了哪些事情?
1、创建一个空对象
2、让空对象的_proto_(IE没有该属性)成员指向了构造函数的prototype成员对象
3、使用call调用构造器函数,属性和方法被添加到 this 引用的对象中
4、如果构造函数中没有返回其它对象,那么返回 this,即创建的这个的新对象,否则,返回构造函数中返回的对象
function MyFunction(firstName, lastName){
this.firstName = firstName
this.lastName = lastName
}
var a = new MyFunction('jony', 'sino')
new MyFunction {
let obj = {}
obj.__proto__ = MyFunction.prototype
var result = MyFunction.call(obj, 'jony', 'sino')
return typeof result === 'Object' ? result : obj
}
六、深浅拷贝
代码示例:
// 这是正常赋值的情况
let arr = [1, 2, 3];
let newArr = arr;
newArr[0] = 100;
console.log(arr);//[100, 2, 3]
这里只是复制了引用,所以arr会跟着改变
// 浅拷贝
let arr = [1, 2, 3];
let newArr = arr.slice();
newArr[0] = 100;
console.log(arr);//[1, 2, 3]
这是浅拷贝,这时arr和newArr已经不是引用的同一块空间了
浅拷贝的几种方式:
Object.assign
concat数组
slice
...展开运算符
但是这种情况下会有新的问题
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr);//[ 1, 2, { val: 1000 } ]
这里改变newArr的值的时候arr也会跟着变化
这个时候就需要深拷贝了
深拷贝可以用遍历去实现
const deepClone = (target) => {
if (typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? []: {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop]);
}
}
return cloneTarget;
} else {
return target;
}
}
同时也可以这样:JSON.parse(JSON.stringify());
七、虚拟dom Virtual DOM 和 diff算法
虚拟DOM指的是用JS模拟的DOM结构,将DOM变化的对比放在JS层来做。换而言 之,vdom就是JS对象。
虚拟DOM并不一定会比真实的dom操作要快,要分场景,小规模的变动的时候有优势
虚拟DOM更加优秀的地方在于:
1、它打开了函数式的UI编程的大门,即UI = f(data)这种构建UI的方式。
2、可以将JS对象渲染到浏览器DOM以外的环境中,也就是支持了跨平台开发,比如ReactNative。
diff算法,就是用来找出两段文本之间的差异的一种算法,这个并不是前端原创的算法,在linux的diff命令中有所体现,并且大家常用的git diff也是运用的diff算法。
diff算法的实现就是递归对比差异
八、ssr 和 seo 优化
ssr就是服务端渲染,当访问站点时服务器直接把页面的代码返回,浏览器拿到之后直接渲染就行。
实现原理:
核心步骤:
同构,所谓同构,通俗的讲,就是一套React代码在服务器上运行一遍,到达浏览器又运行一遍。服务端渲染完成页面结构,浏览器端渲染完成事件绑定;
数据的注水和脱水
注水:即把服务端的store数据注入到window全局环境中
脱水:就是把window上绑定的数据给到客户端的store
csr劣势:
由于页面显示过程要进行JS文件拉取和React代码执行,首屏加载时间会比较慢。
对于SEO(Search Engine Optimazition,即搜索引擎优化),完全无能为力,因为搜索引擎爬虫只认识html结构的内容,而不能识别JS代码内容
seo优化:
1.网站结构布局优化
合理的控制网站的结构,结构层次越少越便于抓取,一般目录结构不超过三级
2.控制首页链接数量(如果首页链接太少,没有“桥”,“蜘蛛”不能继续往下爬到内页,直接影响网站收录数量。但是首页链接也不能太多,一旦太多,没有实质性的链接,很容易影响用户体验,也会降低网站首页的权重,收录效果也不好)
3.导航优化(导航尽量使用文字方便抓取,图片的话也是需要加title和alt属性),网站应该加上面包屑导航
4.网站的结构布局(头部:logo主导航,用户信息; 底部:版权信息和友情链接)
5.利用布局,把重要内容HTML代码放在最前
6.控制页面的大小,减少http请求,提高网站的加载速度
7.突出重要内容—合理的设计title、description和keywords
8.语义化书写HTML代码,符合W3C标准
9.a标签加title属性 img加alt属性
前端页面性能优化
1.减少http请求
2.css Sprites
3.文件合并
4.base64 svg
5.cdn
6.http缓存,浏览器缓存
7.css头部,js尾部
8.减少回流操作
9.减少dom操作
十、如何进行垃圾回收
垃圾回收机制就是找出那些不再继续使用的值,然后释放其占用的内存,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的。
引用计数法:就是看一个对象是否有指向它的引用,但它却存在一个致命的问题:循环引用(相互调用;dom元素绑定事件)
标记清除法:将“不再使用的对象”定义为“无法达到的对象”。简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
十一、EventLoop 宏任务,微任务
为什么需要引入微任务?
这个问题其实就是如何解决回调处理的问题,
1.使用同步回调,直到异步任务进行完,再进行后面的任务;
2.使用异步回调,将回调函数放在宏任务的队尾;
3.使用异步回调,将回调函数放在当前宏任务的最后面
明显第三种才是我们需要的处理方式。
十二、promise Generator函数 asnyc await
自己实现一个promise?
简化版
class MyPromise {
constructor(fn){
if(typeof fn !== 'function') {
return
}
this.state = 'pending'
this.value = null
this.reason = null
this.onResolveCallBack = []
this.onRejectCallBack = []
// 添加resovle时执行的函数
let resolve = (val) => {
const run = () => {
if (this.state !== 'pending') return
this.state = 'fulfilled'
// 依次执行成功队列中的函数,并清空队列
const runFulfilled = (value) => {
this.onResolveCallBack.forEach(cb => (value))
}
}
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run, 0)
}
// 添加reject时执行的函数
let reject = (err) => {
if (this.state !== 'pending') return
// 依次执行失败队列中的函数,并清空队列
const run = () => {
this.state = 'pending'
this.reason = err
this.onRejectCallBack.forEach(cb => cb(err))
}
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run, 0)
}
try {
fn(resolve, reject)
} catch(err) {
reject(err)
}
}
then(onFulfilled, onRejected){
const { value, state, reason } = this
// 返回一个新的Promise对象
return new MyPromise((onFulfilledNext, onRejectedNext) => {
// 封装一个成功状态的执行函数
let fulfilled = value => {
try {
if(typeof onFulfilled !== 'function') {
onFulfilledNext(value)
} else {
let res = onFulfilled(value);
if (res instanceof MyPromise) {
res.then(onFulfilledNext, onRejectedNext)
} else {
onFulfilledNext(res)
}
}
} catch (err) {
onRejectedNext(err)
}
}
let rejected = error => {
try {
if(typeof onRejected !== 'function'){
onRejectedNext(error)
}else{
let res = onRejected(error)
if(res instanceof MyPromise){
res.then(onFulfilledNext, onRejectedNext)
}else{
onRejectedNext(res)
}
}
} catch (err) {
onRejectedNext(err)
}
}
switch (state) {
// 当状态为pending时,将then方法回调函数加入执行队列等待执行
case 'pending':
this.onResolveCallBack.push(fulfilled)
this.onRejectCallBack.push(rejected)
break
// 当状态已经改变时,立即执行对应的回调函数
case 'fulfilled':
fulfilled(value)
break
case 'rejected':
rejected(reason)
break
}
})
}
}
升级版
class MyPromise {
constructor(fn){
if(typeof fn !== 'function') {
return
}
this.state = 'pending'
this.value = null
this.reason = null
this.onResolveCallBack = []
this.onRejectCallBack = []
// 添加resovle时执行的函数
let resolve = (val) => {
const run = () => {
if (this.state !== 'pending') return
this.state = 'fulfilled'
// 依次执行成功队列中的函数,并清空队列
const runFulfilled = (value) => {
this.onResolveCallBack.forEach(cb => (value))
}
// 依次执行失败队列中的函数,并清空队列
const runRejected = (error) => {
this.onRejectCallBack.forEach(cb => (error))
}
/* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
*/
if (val instanceof MyPromise) {
val.then(value => {
this.value = value
runFulfilled(value)
}, err => {
this.reason = err
runRejected(err)
})
} else {
this.value = val
runFulfilled(val)
}
}
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run, 0)
}
// 添加reject时执行的函数
let reject = (err) => {
if (this.state !== 'pending') return
// 依次执行失败队列中的函数,并清空队列
const run = () => {
this.state = 'pending'
this.reason = err
this.onRejectCallBack.forEach(cb => cb(err))
}
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run, 0)
}
try {
fn(resolve, reject)
} catch(err) {
reject(err)
}
}
then(onFulfilled, onRejected){
const { value, state, reason } = this
// 返回一个新的Promise对象
return new MyPromise((onFulfilledNext, onRejectedNext) => {
// 封装一个成功状态的执行函数
let fulfilled = value => {
try {
if(typeof onFulfilled !== 'function') {
onFulfilledNext(value)
} else {
let res = onFulfilled(value);
if (res instanceof MyPromise) {
res.then(onFulfilledNext, onRejectedNext)
} else {
onFulfilledNext(res)
}
}
} catch (err) {
onRejectedNext(err)
}
}
let rejected = error => {
try {
if(typeof onRejected !== 'function'){
onRejectedNext(error)
}else{
let res = onRejected(error)
if(res instanceof MyPromise){
res.then(onFulfilledNext, onRejectedNext)
}else{
onRejectedNext(res)
}
}
} catch (err) {
onRejectedNext(err)
}
}
switch (state) {
// 当状态为pending时,将then方法回调函数加入执行队列等待执行
case 'pending':
this.onResolveCallBack.push(fulfilled)
this.onRejectCallBack.push(rejected)
break
// 当状态已经改变时,立即执行对应的回调函数
case 'fulfilled':
fulfilled(value)
break
case 'rejected':
rejected(reason)
break
}
})
}
}
Generator实现的原理?
asnyc await 其实是Generator函数的语法糖
await 必须要在 asnyc 后面的原因是async刚好返回一个Promise对象,可以异步执行阻塞
十三、js设计模式
工厂模式、
构造函数模式、
原型模式、
单例模式
发布订阅模式和观察者模式
示意图:
代码:
观察者模式:
class Observer{
constructor(name){
this.name = name
}
update(){
console.log(`my name is${this.name}`)
}
}
class Subject{
constructor(){
this.observerList = []
}
add(observer){
this.observerList.push(observer)
}
remove(observer){
this.observerList = this.observerList.filter(ob => ob !== observer)
}
notify(){ // 通知
let observerList = this.observerList;
for(let i = 0;i < observerList.length;i++){
observerList[i].update();
}
}
}
发布订阅者模式:
class PubSub{
constructor(){
this.subscribers = {}
}
subscribe(type, fn){
if(!this.subscribers[type]){
this.subscribers[type] = []
}
this.subscribers[type].push(fn)
}
publish(){
let arg = arguments
let key = [].shift.call(arg)
let fns = this.subscribers[key]
if(!fns || fns.length <= 0) return false
for(let i = 0, len = fns.length; i < len; i++ ){
fns[i].apply(this, arg)
}
}
unSubscribe(key){
delete this.subscribers[key];
}
}
工厂模式
function factory(name, age){
let obj = new Object
obj.name = name
obj.age = age
obj.say = function(){
alert(obj.name)
}
return obj
}
var obj1 = factory("cc", 20);
构造函数模式
function MyObj(name, age){
this.name = name
this.age = age
this.say = function(){
alert(this.name)
}
}
var obj2 = new MyObj('cc', 20)
原型模式
function MyProtoType(){
}
MyProtoType.prototype.name = 'cc'
MyProtoType.prototype.age = 20
MyProtoType.prototype.say = function(){
alert(this.name)
}
var obj3 = new MyProtoType();
obj3.say() // cc
十四、webpack相关
缺点:配置太过复杂
十五、项目相关,遇到的难点
编译打包,长列表的优化,列表数据的优化,强缓存等
十六、call apply bind 和 this的指向问题
call apply bind 作用都是用来改变this的指向的
用法不同:
fn.call(this, arg1, arg2, arg3)
fn.apply(this, [arg1, arg2, arg3])
fn.bind(this, arg1, arg2)() //bind 是创建一个新的函数,我们必须要手动去调用
同时在es6之前this的指向问题也是一个坑点,下面我们来看下面几种情况:
例一:
var name = "windowsName";
function a() {
var name = "Cherry";
console.log(this.name); // windowsName
console.log("inner:" + this); // inner: Window
}
a();
console.log("outer:" + this) // outer: Window
例二:
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // Cherry
}
}
a.fn();
例三:
var name = "windowsName";
var a = {
name : null,
// name: "Cherry",
fn : function () {
console.log(this.name); // windowsName
}
}
var f = a.fn;
f();
例四:
var name = "windowsName";
function fn() {
var name = 'Cherry';
innerFunction();
function innerFunction() {
console.log(this.name); // windowsName
}
}
fn()
> 在 JavaScript 中, 函数是对象。 JavaScript 函数有它的属性和方法。call() 和 apply()
> 是预定义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象本身 在 JavaScript 严格模式(strict
> mode)下, 在调用函数时第一个参数会成为 this 的值, 即使该参数不是一个对象。 在 JavaScript
> 非严格模式(non-strict mode)下, 如果第一个参数的值是 null 或 undefined, 它将使用全局对象替代。
这里的 innerFunction() 调用是属于函数调用,没有挂载在任何对象上面,在非严格模式下this就是指向window的
例五:
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
},100 );
}
};
a.func2() // this.func1 is not a function
**匿名函数的 this 永远指向 window**
最后总结起来就是:this 永远指向最后调用它的那个对象
十七、v8的垃圾回收(新生代,老生代)
在v8引擎中能使用的系统内存是有限的,在64位的操作系统中最大可以分配到1.4G内存,在32位的操作系统中最大只能分配到0.7G内存;
我们知道在js中有堆、栈2种内存;对于栈内存而言,当esp指针下移,也就是上下文切换后,栈顶的空间会自动回收,(栈内存是先进后出);而相当于堆内存就比较复杂了。
在js中所有的对象类型的数据都是分配在堆内存中的,在我们构造一个对象进行赋值操作的时候相应的内存就分配到了堆上,你可以不停的创建对象,只到达到内存的上限,那么为什么要设置一个上限值呢?这是由2个因素决定的,一个是js单线程的执行机制,一个是js的垃圾回收机制的限制。
js是单线程执行的,一旦进入到垃圾回收,其他的各种运行逻辑都需要暂停,另外垃圾回收机制的执行也是非常耗时的,如果不设置内存上限的话,当需要回收的时候我们的js代码执行会一直没有响应造成应用的卡顿。
好了,下面就来介绍v8中的垃圾回收机制:
v8中把堆内存分为了2部分处理,新生代内存和老生代内存;
新生代内存就是临时分配的内存,存活周期短;
#新生代内存
老生代内存是常驻内存,存活的时间长;
根据不同的内存,采用了不同的回收策略,新生代内存的默认内存大小在64位和32位系统下分别是32M和16M,同时将内存一分为二,
From部分表示正在使用的内存,To内存表示目前闲置的内存,当进行垃圾回收的时候,先将from里面的对象先检查一遍,如果是存活对象就复制到to里面(在to内存中是按照顺序从头开始排列的),否则就回收;当from里面的所有存活对象都复制到to中之后,将to和from两者的角色对调,from现在为闲置,to现在为正在使用。
那么为什么不直接将非存活对象直接回收呢,如果直接将非存活对象回收的话会出现这种情况:
内存会出现零散的空间(内存碎片),由于堆内存是连续分配的,这样就会导致一些大的对象无法进行空间分配;这种新生代的垃圾回收机制叫做Scavenge算法
Scavenge算法主要解决的就是内存碎片的问题,经过处理回收之后,to空间就变成了这样:
不过Scavenge算法的缺点也非常明显,就是内存只能使用一半;但是只存放生命周期短的对象,这种对象一半比较少,因此时间性能非常优秀。
老生代内存
在新生代中的变量经历过多次回收之后依然存在,那么就会被放入到老生代内存中,这种现象叫做晋升
下面几种情况会产生晋升:
1.已经经历过一次scavenge回收
2.to(闲置)空间的内存占用超过25%
现在进入到老生代的垃圾回收机制中,老生代的内存空间一般都是比较大的,所以肯定不能再用scavenge算法来实现了,浪费了一半的空间。
那么就是使用我们熟知的标记-清除法,首先分为标记阶段和清除阶段,首先会遍历堆中所有的对象,给他们做上标记,然后对代码中使用的变量和被强引用的变量取消标记,然后剩下的就是需要回收的变量了,在随后的清除阶段对其进行空间的回收;
这样也会产生内存碎片,老生代处理这个问题的方式比较简单粗暴,在清除阶段之后把所有的存活对象往一端移动;
由于是移动对象,执行速度不会很快,事实上这也是整个过程最耗时的地方
增量标记
由于老生代的垃圾回收会比较耗时,在垃圾回收的过程中会不可避免的阻塞到业务逻辑的执行,为了解决这个问题,v8采取了增量标记的方案,即将任务分为很多个小的部分完成,每做完一个任务就先暂停一下,让js应用逻辑执行一会,然后再执行下面的部分,如此循环,直到标记阶段完成才进入内存碎片的整理上面来。经过增量标记之后,垃圾回收过程对JS应用的阻塞时间减少到原来了1 / 6, 可以看到,这是一个非常成功的改进。
这里说一个点:是使用栈内存效率高还是堆内存效率高呢?
答案肯定是栈内存,而堆内存首先要在堆内存新分配存储区域,之后又要把指针存储到栈内存中