⚠️⚠️传送门⚠️⚠️
[寒冬期前端准备总结---JS篇]
寒冬期前端准备总结---浏览器篇
寒冬期前端准备总结---服务器和网络篇
寒冬期前端准备总结---CSS篇
寒冬期前端准备总结---框架篇
寒冬期前端准备总结---算法篇
- 原型/原型链/构造函数/实例/继承
- 所有对象都有隐式原型属性proto,所有构造函数都有自己的原型prototype,且隐式原型_proto指向创造这个对象的构造函数的原型,构造函数的原型中有构造器constructor,构造器指向构造函数本身。通过隐式原型可以向上查找属性和方向供对象使用。
上图原型链结构:p对象的原型链 -> Person.prototype -> Object.prototype -> null
- ES5、ES6的继承区别
ES5
原型链继承,通过原型链之间的指向进行委托,prototype属性进行关联继承
构造函数继承,通过在子类内部调用父类,使用apply()和call()方法在新创建的对象上获取父类的方法,先有自己的this对象,再扩充this的属性
ES6
class类实现继承,创建的子类使用extends直接继承父类,并在构造器中调用super(),子类自己的this对象必须先通过父类的构造函数完成塑造,如果不调用super方法,子类就得不到this对象,先拿到父类的this,再有自己的this
* ES5原型链继承 :子构造函数的原型指向父构造函数的实例。
function Person(name) {
this.name = name
this.skills = ['eat', 'sleep']
}
Person.prototype.say = () => { console.log('hi'); }
function Boss() {}
Boss.prototype = new Person()
let Han = new Boss()// 父级引用共享
* ES5构造函数继承:父构造函数把执行对象赋给子构造函数的实例对象后执行自身
// 可以向父级传递参数
function Person(name) {
this.name = name
this.skills = ['eat', 'sleep']
}
Person.prototype.say = ()=> {console.log('hi')}
function Boss(name) {
Person.call(this, name)
}
let Han = new Boss('Han')// 可以向父级传参,父级引用不共享
- 用new创建一个对象的过程
1 let obj = {} // obj = new Object()
2 obj.__proto__ = Object.prototype // 对象的隐式属性指向构造函数的原型prototype,
3 Object.call(obj) // 调用上层的构造函数,使用call将this指向对象实例
* 如果在一个引用类型的对象上找不到某一个属性,会往她的构造函数的prototype里面查找
console.log(obj.__proto__ === Object.prototype);
__proto__:隐式原型 prototype:显示原型
Object.prototype.__proto__ == null;
Object.__proto__ == Function.prototype;
Function.prototype.__proto__ == Object.prototype;
**__proto__是形成他们之间关联(原型链)的主要属性**
🌰**原型链分析数组对象的方法🌰**
1 let arr = [];// 为let arr = new Array();的语法糖
2 arr.push方法在arr对象里并不存在,查找隐式原型__proto__;其中arr.__proto__ = Array.prototype;
3 也就是说push方法存在于Array的显式原型prototype上
- 数据类型
基本数据类型存储在栈中,复杂类型变量栈中存储的是指向堆的引用地址
基本类型按值访问;复杂类型是按引用访问
赋值:基础类型是值的副本,复杂类型是引用地址
- 数据结构
* 数组:插入快、删除慢
* 队列:先进先出
* 栈:后进先出
* 链表:插入删除快(指针),查询慢
* 二叉树:插入删除查询快
- 类型判断
* typeof判断基本数据类型:number、string、boolean、object、undefined,对于array、function、null、object均返回object
* Object.prototype.toString.call方法用于判断某个对象属于哪种内置类型,常用于判断浏览器内置对象
* instanceof 判断对象是不是某类的实例 person instanceof Person,实现机制时判断对象的原型链中能不能找到类型的prototype
* 判断一个类为另一个类的子类:子类的原型的所有属性在父类上是否存在;duck-typing弱化对象的类型,强化了对象的功能
* 另外ES5的Array.isArray方法可以用来判断数组
// 兼容数组判断
if(!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
* Set数组去重不包括NaN和{} : NaN === NaN (false) || {} === {} (false)
- 判断一个对象是否为空
* 将对象stringify为字符串,判断“{}”
* for in判断对象有没有key;for in 遍历自身和原型上的所有可枚举属性
* **hasOwnProperty方法会屏蔽掉原型上的属性**(console.log(arr.hasOwnProperty('push')) // false)
* Object.getOwnPropertyNames判断属性数组长度
* Object.keys判断属性数组长度(ES6)
- 对象的键名
* 只能是字符串和Symbol类型,每一个Symbol类型的数据都是唯一的
* 其他类型的键名会被强制转化为字符串
* 对象通过toString方法转为字符串 // {a: 1} toString后为 [object object]
- 深拷贝JSON.stringify的存在问题
* 可以实现基本类型数据的深拷贝
1 忽略不能被文本化的属性,例如function
2 循环引用会丢失,被忽略返回空对象
3 Symbol和undefined等不能用JSON表示的属性会丢失,数组中不能被stringify的元素会被用null填充(JSON是一个文本格式,不会对特殊的语言做处理)
* 重写对象原型prototype上的toJSON方法实现自定义的JSON.stringify的返回结果
* JSON.stringify(value, replacer?, space?)
第二参数replacer:表示需要stringify的属性名称数组或一个过滤属性的函数,且嵌套的属性也会被过滤掉
第三参数space:用于格式化输出结果
- 箭头函数和普通函数的区别
* 函数体内的this对象,是定义箭头函数时所在的对象的this,不是调用时对象的this
* 不可以使用arguments
* 不可以使用yield命令,不能用于Generator函数
* 不可以使用new实例化,无this,无prototype
- 操作符==会进行隐式的类型转换,toString方法的重写
var a = {
i: 1,
toString() {
return a.i++;
}
}
if(a == 1 && a == 2 && a == 3) {
console.log(true);
}
- 连续赋值题
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
a;// {n: 2}
b;// {n: 1, x: {n: 2}}
* . 运算符的优先级高于 = 。先执行a.x,此时内存中的{n: 1}变成{n:1,x:undefined};此时b.x和a.x都为x:undefined
* 赋值操作从右往左。先执行a = {n: 2};此时a指向新的内存地址,a为{n: 2};然后将这个返回值赋值给a.x,a.x为之前的{n:1,x:undefined}中的x对象,所以{n:1,x:undefined}变成了{n:1,x: {n: 2}},此时b指向这个内存地址
- call、apply、bind
* 都是改变this指向的,第一个参数为null,表示this指向全局window/global
* call后面参数一个一个的传、apply后面的参数放在数组中传
* bind后面参数一个一个传,但是返回一个新的函数,调用后才会执行
**callee** arguments.callee:arguments对象的属性,用来指向当前执行函数
**caller** arguments.callee.caller:function对象的属性,返回function的引用
- a.b.c.d的性能优于a[‘b'][‘c'][‘d’]:[]需要考虑变量的情况,且dot在编译解析的时候比中括号速度快
- ES6转ES5的原理:babel工具先将ES6代码解析成抽象语法树,修改抽象语法树,再将修改后的抽象语法树转化成ES5的代码
- setTimeout的第三个参数会作为回调方法的第一个参数传入
- var存在变量提升(先使用后声明);let、const没有变量提升(先声明后使用) 但是有暂时死区,不在全局对象(window/global)上,只存在于一个script块级作用域上
- 类数组的push方法是根据类数组的length来决定从何处插入数据的,length不存在或者不能转为数字时,默认length为0
- 数组扁平化 arr.flat(Infinity)数组 | arr.toString()逗号分隔字符串
- 进程和线程
* JS是单线程执行的,线程是CPU调度的最小单位,进程是CPU资源分配的最小单位
* 一个进程可以由一个或多个线程组成,且一个进程的内存空间是共享的
* 浏览器内核为多线程,常驻线程包括:GUI渲染线程、JS引擎线程、事件触发线程、定时器线程和异步http请求线程
- 事件冒泡、事件捕获、事件委托
* 事件捕获:从window层逐步向深层的节点传播事件
* 事件冒泡:从深层节点逐步向外层传播事件
* 外层节点先事件捕获(true)再事件冒泡(false);到目标节点时根据事件绑定的先后顺便执行事件
* 阻止事件传播:e.stopPropagation
* addEventListener方法的第二个参数,false表示执行冒泡、true表示执行捕捉,默认为false
* e.target表示触发事件的目标元素;e.currentTarget表示绑定事件的元素
- js内存泄漏和垃圾回收机制
内存泄漏的情景
* 全部变量引起的泄漏
* 闭包引起的泄漏
* dom删除或清空时,事件未清空引起的泄漏
垃圾回收机制
* 计数法:变量被引用的次数计数,但是循环引用时无法被回收
* 标记清除:当变量进入执行环境时标记使用,结束执行环境时标记回收,从根节点标记被使用的变量,清除未被使用的变量
- 函数
// 函数表达式
var a = function() {}
// 函数声明
function b() {}
* 函数表达式不同于函数声明,函数表达式中的函数名只在该函数内部有效,并且函数名绑定为常量绑定,对常量赋值在严格模式下会报错
* 函数声明优先于变量声明,**函数声明会在执行之前被解析 存在覆写情况(完全提升声明前可以调用);变量声明是在执行过程中解析函数**
* 函数表达式包括匿名函数和具名函数
* 函数声明会被提升,函数表达式不会
函数表达式👇
var a = 10;
// 'use strict'
(function a() {
a =20; // 具名函数a,不能被赋值
console.log(a); // a为函数a,严格模式下报错(Uncaught TypeError: Assignment to constant variable.)
})()
var a = 10;
// 'use strict'
(function a(a) {
a =20;
console.log(a); // 20
})(a)
- 模块化
* AMD:异步加载模块,使用define导出模块,require([module])引入模块,提前执行且不能按需加载
* CMD:延迟执行、按需加载、依赖就近,跟AMD的语法相同
* CommonJS:同步加载模块,主要用于node环境中,运行时加载(只会在第一次加载时运行,之后都是缓存,加载的对象在脚本运行完成时才生成),是**值的拷贝**
* ES6:import、export 按需编译时加载,需要babel转码,是**对值的引用**,静态编译(webpack同步、SystemJS异步)动态引用没有缓存值,在代码编译阶段就会生成
- Node和浏览器的事件循环
微任务:Promise.then、await后面的脚本
宏任务:setTimeout、setInterval、script、Promise的构造函数、async的函数体
* Node端,微任务在事件循环的各个阶段之间执行,宏任务有六个阶段,如下图
* 浏览器端,微任务的任务队列都是在每个宏任务执行完成之后执行的
node11 以前:
* 执行完一个阶段的所有任务
* 执行完nextTick队列里面的内容
* 然后执行完微任务队列的内容
node11以后:和浏览器行为统一,每执行一个宏任务就执行完微任务队列
在node中process.nextTick()不属于事件循环的一部分,无论时间循环的当前阶段如何,都将在当前操作完成后处理nextTick
- 函数节流和函数防抖
函数防抖:事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时
函数节流:规定一个单位时间内,只能有一次触发事件的回调的函数被执行,如果单位时间内事件被多次触发,只能有一次事件生效
* 防抖应用场景:适合多次事件一次响应
按钮加函数防抖,防止表单的多次提交
输入框输入校验,用函数防抖有效减少校验的请求次数
判断页面是否滚到底部
🌰
var timer = null
element.input = function () {
clearTimeout(timer)
timer = setTimeout(function () {
// DO SOMTHING...
}, 1000)
}
* 节流的应用场景:适合大量事件按时间做平均分配触发
页面的刷新
DOM元素的拖拽
Canvas的画笔功能
- 高阶函数、函数柯里化
* 高阶函数:将函数作为返回值,形成高阶函数 add(1)(2)(3):
🌰
function add(a) {
function sum(b) { // 使用闭包
a = a + b;
return sum;
}
// 重写toString方法,用于返回变量a的值,console.log(a(1)(2)(3))
sum.toString = function() {
return a;
}
return sum;
}
* 柯里化:把多个参数的函数转为返回单一参数的新函数的形式
🌰
// 不限形参的个数 优化版柯里化函数
function add() {
var args = Array.prototype.slice.call(arguments);
var fn = function() {
var newArgs = args.concat(Array.prototype.slice.call(arguments));
return multi.apply(this, newArgs);
}
fn.toString = function() {
return args.reduce(function(a, b) { return a + b; })
}
return fn;
}
* 柯里化的实现方式:用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数
延迟计算
动态创建函数
参数复用
- JS数组方法总结
* includes:判断数组是否存在某个元素;返回true/false
* forEach:遍历数组,该方法不会改变原数组,且没有返回值
* map:遍历数组,不会改变原数组,返回新数组,函数没有显示返回值时,新数组各项为undefined
* find:根据检索条件,查找数组中第一个满足条件的元素,没有时返回undefined
* findIndex:同上,返回元素下标
* filter:根据条件,过滤返回获得的新数组,不改变原数组
* push:数组末尾添加一个元素,返回数组新长度
* pop:删除数组最后一个元素,并返回该元素
* shift:删除数组第一个元素,并返回该元素
* unshift:数组开头添加一个元素,返回数组新长度
* concat:数组拼接,不会改变原数组,返回拼接后的新数组
* reverse:数组元素倒置,**改变原数组**,且返回新数组
* sort:数组排序,返回排序后的数组,且改变原数组,不传递函数时,使用toString后比较大小(a,b)=> a - b 生序
* join:已分隔符将原数组元素连接生成字符串
* split:字符串分隔为数组,第二参数为分隔符
* every:判断数组所有项是否条件,返回true/false
* some:判断数组任一项是否条件,返回true/false
* indexOf/lastIndexOf:查找元素,第一参数为查找元素,第二参数为开始位置(正向查找/反向查找),返回下标
* slice(start, end):数组截取,返回区间元素数组,**不改变原数组**
* splice(start, howmany, item1, item2):数组截取,删除指定数目的项,添加新的项,返回删除的元素,**改变原数组**
* reduce:为数组中的每一个元素依次执行回调;第一参数:初始值/上一次回调的返回值;第二参数:当前元素;第三参数:当前索引;第四参数:调用reduce的数组 arr.reduce((total, num) => total + num);
- 字符串转数字~~、+、Math.floor、parseInt
* Math.floor 负数时,向下取整 但是只能转标准格式的字符串,效率高于parseInt
* ~~ 负数时,向上取整,只能转标准格式的字符串,非标准结果为0
* + 将字符串转数字,不能转为整数,只能转标准格式的字符串(+"0" === false)
* parseInt效率最低,但是可以转非标准格式的字符串, 第二参数为转化的进制,不设置时会根据string来判断数字的基数0x转化为16进制
- 设计模式
* 发布订阅(观察者)
当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
* 工厂
提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类
* 单例
保证一个类仅有一个实例,并提供一个访问它的全局访问点
应用:全局缓存,浏览器中的window对象等
* 代理
把对一个对象的访问, 交给另一个代理对象来操作
- 前端性能优化
- 减少HTTP请求:合并文件
- 减少DNS查询:DNS缓存
- 非必须组件的延迟加载
- 未来组件的预加载
- 减少DOM元素的数量,以及减少DOM操作
- 将资源放在不同域名下,增加域可以提供并行下载量
- 减少使用CSS表达式
- 脚本放在页面底部,防止脚本阻塞
- 压缩js和css
- 图片优化(图片适配、雪碧图等等)
- 使用CDN,
- 添加缓存相关的响应头
- 配置ETag
- 资源js、css压缩
- 资源协商缓存,
- 按需引用资源
- 图片优化
选择合适的图片格式
* BMP:没有压缩像素格式
* JPG:有损压缩,没有透明效果,可以在保存时,可以选择压缩比例控制图片文件大小(60-80最佳);
JPG的“基线”从上到下加载,“连续”从模糊到清晰
* PNG:无损压缩,有透明效果,png8颜色值256色
* GIF:多帧动图,有透明度
* webp:google开发的,有损压缩,体积降低更多
* 总结:照片使用JPG,图标使用PNG,动画使用GIF
降低体积
* 压缩图片大小,在线压缩工具
响应式加载
* css的@media决定加载背景图片的大小
* h5的img属性,srcset为不同宽度的图片设置图片资源地址;sizes根据视图宽度决定图片渲染的宽度
🌰
<img class="img" src="imgbg-320.jpg”
srcset="imgbg-320.jpg 320w, imgbg-360.jpg 360w, imgbg-480px.jpg 480w”
sizes="(max-width: 480px) 480px, 320px”>
* picture标签根据视图大小加载不同资源,暂时未被浏览器兼容
减少HTTP的网络请求
* css sprites(背景精灵图/雪碧图):将多个小的图标放在同一张大图中,在css里面加载一次,每次使用时借助background-position、width、height使用数字精确的定位特定的背景大图位置
* 使用css来绘制一下图形图标,例如三角形,代替http请求
* svg矢量图使用XML格式定义图形,不会在缩放时质量改变
* h5 的canvas标签绘制图片
* base64,将一些小图标(2KB左右)转化成base64嵌入到页面html或者css中
图片延迟加载和图片预加载
* 延迟加载,当页面滚动到一定位置时,在加载新图片
* 预加载,对与当前页面不同域的服务器进行DNS Prefetch预加载(link标签),将静态资源放在不同域名的服务器上实现预加载
字体图库iconfont
* 在网站上定制图标库,下载包后,可以引入css文件,在i标签中写入对应的类名
- set、map、WeakSet和WeakMap
* set用于数据重组,map用于数据存储
* set是成员唯一且无序的类数组结构,注意对象引用;WeakSet只能存储引用对象,不能存放值,且不能被遍历,对象可能会被垃圾回收掉;
* map以[key, value]的形式存储数据,构造函数的参数:任何具有Iterator接口、且每个成员都是一个双元素的数组的数据结构都可以;键名不限于字符串
let map = new Map([[‘a’, 1], [‘b’, 2]]);
map.set(‘c’,’3’);
* WeakMap只接受对象作为键名,且键名为弱引用,会被垃圾回收,不能遍历
- Proxy
* 在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写
* 类似于Object.defineProperty,优于Object.defineProperty,可拦截13种方法 https://es6.ruanyifeng.com/#docs/proxy
Object.defineProperty是对一个对象的一个属性劫持,需要遍历整个对象的属性,proxy是对整个对象的劫持
🌰
let targetObj = {a: 1};
let obj = new Proxy(targetObj, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return ‘hhh';
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
console.log(obj.a);// hhh