目录
HTML
标签语意化
HTML5新特性
SEO
input元素的类型
iframe的特点
CSS
Flex
BFC
重排重绘
CSS优先级
CSS3新特性
清除浮动的方法
盒模型的理解
响应式布局
移动适配方案
三栏布局
圣杯布局和双飞翼布局
JS
JS为何是单线程
JS数据类型
js判断数据类型
js中的length属性
判断空对象
判断空数组
ES6新特性
ES6 Module 和 CommonJS 的区别
箭头函数和普通函数区别
new一个对象做了什么事情
构造函数与普通函数的区别
防抖和节流
深拷贝浅拷贝
数组去重
原型链
call、bind、apply的含义和区别
Class
Class和构造函数的区别
js继承的几种方式及其优缺点
闭包
浏览器的缓存机制
任务队列(宏/微任务)
事件委托
请求
请求的方式有哪些,应用场景是什么
什么是promise,解决了什么问题
谈谈对async/await的理解
跨越问题如何解决
输入网站url地址后发生了什么
ajax工作流程及原理
axios 是什么、怎么使用
后端接口没写出来,前端如何进行开发
post请求和get请求的区别
Session,Token,Cookie在身份认证方面的区别
常见的状态码
Vue2
Vue是什么
MVVM
观察者模式和发布订阅者模式
SPA(单页应用)的理解
响应式原理
Vue2生命周期
data为什么是函数的存在
Vue组件通信的方式
keep-alive原理及缓存策略
computed原理
watch原理
computed和methods、watch的区别
vue-router原理
Vue3
Vue2和Vue3响应式原理的区别
Vue3生命周期
选项式API和组合式API
VueX和Pinia的区别、优劣
Vite好在哪为什么
TS
对ts的理解
ts的数据类型
never和void的区别
枚举的理解
泛型
项目
token超时处理
登录页面安全问题
发布通知功能怎么实现
项目刚上线出现bug
用什么进行版本管理
Echarts在Vue里怎么使用
Echarts里的配置项不够用怎么办
团队中有的人用vue2有的人用vue3怎么办
项目优化
PC端兼容问题
img下的留白
如果图片加a标签在IE9-中会有边框
移动端问题
移动端页面滚动滞涩感
修改移动端的点击高亮效果
滚动穿透问题
在ios和andriod中,audio元素和video元素在无法自动播放
iOS 系统中文输入法输入英文时,字母之间可能会出现一个六分之一空格
IOS移动端click事件300ms的延迟响应
阻止旋转屏幕时自动调整字体大小
图片模糊问题
移动端某些情况下input的placeholder会出现文本位置偏上的现象
h5底部输入框被键盘遮挡问题
移动端如何做真机测试
H5和app的区别
简单来说:就是用正确的标签做正确的事。比如:
头部:header
导航:nav
主体内容:main
标题:h1 ~ h6
段落:p
侧边栏:aside
页脚:footer
这样,整篇HTML结构非常清晰,好看。
HTML语义化有什么好处呢?
网页加载慢导致CSS文件还未加载时(没有CSS),页面仍然清晰、可读、好看。
提升用户体验,例如title、alt可用于解释名词或解释图片信息。
有利于SEO:和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息。
方便其他设备(如屏幕阅读器、盲人阅读器、移动设备)更好的解析页面。
使代码更具可读性,便于团队开发和维护。
更加语义化的元素。 article、footer、header、nav、section
本地化储存。 localStorage 和 sessionStorage
拖曳以及释放的api。 Drag and drop(draggable="true";当拖拽一个项目到 HTML 元素中时,浏览器默认不会有任何响应。想要让一个元素变成可释放区域,该元素必须设置 ondragover和 ondrop 事件。)
媒体播放。 video 和 audio
增强表单控件类型。date、time、number、email、color、search
全双工通信协议。 websocket
跨域资源共享(CORS) Access-Control-Allow-Origin
补充:Web Storage 的概念和 cookie 相似,区别是它是为了更大容量存储设计的。Cookie 的大小是受限的,并且每次你请求一个新的页面的时候 Cookie 都会被发送过去,这样无形中浪费了带宽,另外 cookie 还需要指定作用域,不可以跨域调用。
1.localStorage: 用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。
2.sessionStorage: 用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此 sessionStorage 不是一种持久化的本地存储,仅仅是会话级别的存储。
Web Storage 拥有 setItem,getItem,removeItem,clear 等方法,不像 cookie 需要前端开发者自己封装 setCookie,getCookie(安装依赖js-cookie、Cookies.set()、Cookies.get()、Cookies.remove)
SEO(Search Engine Optimization),即搜索引擎优化。SEO的存在就是为了提升网站在搜索引擎中的权重,增加对搜索引擎的友好度,使得用户在访问网站时能排在前面。
突出重要内容---合理的设计title
、description
和keywords
语义化书写HTML代码,符合W3C标准
图片img
标签添加alt
和title
属性
使用h1标签自带权重
① button ② checkbox ③ file ④ hidden ⑤ image ⑥ password ⑦ radio ⑧ reset ⑨ submit ⑩ Text ⑪ Date
优点:
iframe能够原封不动的把嵌入的网页展现出来。
如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。
缺点:
iframe会阻塞主页面的onload事件;
iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。,会产生很多页面,不容易管理。
iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化(SEO)。
很多的移动设备无法完全显示框架,设备兼容性差。
iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。
哪些属性作用在父元素上?
justify-content:space-around space-between space-evently center flex-end
align-items:flex-star flex-end
flex-direction :row row-reverse column column-reverse
flex-wrap :nowrap(如果子孩子,的宽度超过父盒子,会进行总动的伸缩) wrap(不去管)
哪些属性作用在子元素上?
order (项目排列的顺序 越小越靠前)
align-self(单个属性的操作 flex-end center flex-star)
flex-grow(如果剩余有宽度是否进行扩张方法,默认是0,)
flex-shrink (空间不足的时候进行缩小默认是1 进行缩小)
定义:
块级格式化上下文。BFC
是一个完全独立的空间(布局环境),让空间里的子元素不会影响到外面的布局。用于对块级元素排版。默认情况下只有根元素(body)一个块级上下文。
布局规则:
内部的盒子会在垂直方向,一个个地放置;
盒子垂直方向的距离由 margin
决定,属于同一个 BFC
的两个相邻盒子的上下 margin
会发生重叠;
每一个元素的左边,与包含块的左边相接触(对于从右往左的布局,则相反),即使存在浮动也是如此;
BFC
的区域不会与 float
重叠;(应用:三栏布局)
BFC
就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此;
计算 BFC
的高度时,浮动元素也參与计算。(运用:清除浮动)
当一个元素设置了新的 BFC
后,就和这个元素外部的 BFC
没有关系了,这个元素只会去约束自己内部的子元素。(应用:外边距塌陷)
怎么触发BFC:
overflow:不为 visible
;
float: 不为 none
;
display: 为 inline-block
、table
、table-cell
、table-caption
、flex
、inline-flex
、grid
、inline-grid
、flow-root
;
position: 为 absolute
或者 fixed
;
页面生成的过程:
1.HTML 被 HTML 解析器解析成 DOM 树;
2.CSS 被 CSS 解析器解析成 CSSOM 树;
3.结合 DOM 树和 CSSOM 树,生成一棵渲染树(Render Tree),这一过程称为 Attachment;
4.生成布局(flow),浏览器在屏幕上“画”出渲染树中的所有节点;
5.将布局绘制(paint)在屏幕上,显示出整个页面。
第四步和第五步是最耗时的部分,这两步合起来,就是我们通常所说的渲染。
重绘:某些元素的外观被改变,例如:元素的填充颜色
重排:重新生成布局,重新排列元素。
单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分。比如改变元素高度,这个元素乃至周边dom都需要重新绘制。
也就是说:重绘不一定导致重排,但重排一定会导致重绘。
!important > style > id > class > 标签 > 通配符 > 默认 > 继承
新增的选择器(各种伪类选择器)
圆角(border-radius)
阴影(box-shadow)
动画(animation)
过渡(transition)
翻转(transform)
渐变
媒体查询 @media
弹性盒子(flex)
rgba
为什么要清除浮动?
如果子元素浮动,此时子元素不能撑开标准流的块级父元素。
方法:
1、直接给父元素设置高度
优点:简单、方便
缺点:有很多布局不能固定父元素高度:比如长列表、推荐模块,是无法确定子项内容有多少的
2、额外标签法
实现方式:
给父元素内容的最后添加一个块级元素
给添加的块级元素设置clear: both;
(可以认为,设置了clear:both
的当前元素会把前边元素中设有浮动属性的元素,当做没设浮动一样来看待,以此来消除其对自己的影响)
缺点:
会添加进去额外的标签,让HTML结构变得复杂
3、单伪元素清除法
用伪元素替代额外标签
4、双伪元素清除法
除了可以清除浮动的影响,还可以解决外边距折叠的塌陷现象(原理:里面的"display:table;"触发BFC)
5、为父元素设置overflow: hidden
分标准盒模型和怪异盒模型。
标准盒模型采用的W3C标准,盒子的content内容部分由width宽度和height高度决定,添加padding内边距或border外边框后,宽高都会进行相应增长。
怪异盒模型也称为IE盒模型,是IE浏览器设计元素时遵循的规则。怪异盒模型的宽高在div盒子初次设置时就已经规定,添加padding或者border,会从中减少content内容的占据区域,来为padding和border制造空间,宽高不会相对应的进行增长。
盒模型转换用box-sizing:border-box,默认是标准盒模型。
bootstrop框架
媒体查询
rem(用flexible
方案,flexible.js帮我们计算出1rem 等于多少px)
vw和vh
flex
定位
浮动
两边浮动中间margin + overflow: hidden(触发BFC)
两边浮动,中间calc()函数
三个部分都浮动(圣杯和双飞翼布局),然后结合margin和定位调整
display:table;
grid布局
最后我们来总结一下,双飞翼布局其实和圣杯布局的精髓是一样的,都是在三个部分都是浮动的情况下,左右的部分因为父盒子宽度不够被挤下来的问题,通过设置负margin来实现元素的排布。
不同的就是html结构,双飞翼是在middle元素内部又设置了一个milddle-inner并设置它的左右margin,而非圣杯布局的padding,来排除两边元素的覆盖,最后把盒子定位到两侧。
双飞翼布局可以多了一个html结构,但是可以不用设置left,right元素的定位。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成这门语言的核心特征,将来也不会改变。
注:所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。
基本类型分为以下六种:
string(字符串)
boolean(布尔值)
number(数字)
symbol(符号)
null(空值)
undefined(未定义)
BigInt(是ES6中新引入的数据类型,它是一种内置对象,它提供了一种方法来表示表示任意大的整数。即使这个数已经超出了JavaScript构造函数 Number 能够表示的安全整数范围。)
引用数据类型:
数组
对象(函数也是对象)
区别:
值类型的变量会保存在 栈内存 中,如果在一个函数中声明一个值类型的变量,那么这个变量当函数执行结束之后会 自动销毁。
引用类型的变量名会保存在 栈内存 中,但是变量值会存储在 堆内存 中,引用类型的变量不会自动销毁,当没有引用变量引用它时,系统的 垃圾回收机制 会回收它。
判断数据类型方法有很多,实际使用需要根据自己的需求使用最适合自己的方法
1、使用 typeof
测试简单数据类型。对于
null
及数组、对象,typeof均检测出为object,不能进一步判断它们的类型。
2、使用 obj instanceof Object
测试复杂数据类型,因为instanceof 是用来判断数据是否是某个对象的实例
所以对于
null
和undefined
这两个家伙就检测不了因为原型链继承的关系,instanceof 会把数组都识别为 Object 对象,所有引用类型的祖先都是 Object 对象
3、使用Object.prototype.toString.call
Object.prototype.toString.call() 区分的数据类型适用范围更大,但是无法区分自定义对象类型,区分自定义对象类型使用 instanceof 操作符。
Object.prototype.toString()本身是允许被修改的,而我们目前所讨论的关于Object.prototype.toString()这个方法的应用都是假设toString()方法未被修改为前提的。
因为实例对象有可能会自定义toString()方法,会覆盖Object.prototype.toString(), 所以在使用时,最好加上call()。
4、使用.constructor
constructor不能判断undefined和null,并且使用它是不安全的,因为contructor的指向是可以改变的
1、length属性常见于字符串和数组,来判断长度。
2、length属性还可以用于函数,来判断函数的长度,即函数中形参的个数。
注意:
...args
不计入形参个数
设置了默认值的参数及其之后的所有参数都不计入形参个数,之前的仍旧计入
所谓空对象,就是数组的长度等于0
let obj = {name : '你好'}
//是true为空对象,是false则不是空对象
console.log(JSON.stringify(obj) === '{}');//false
let obj = {}
let fn = (obj) => {
for(let key in obj) {
return false
}
return true
}
//返回false代表不为空,返回true则为空对象
console.log(fn(obj));//true
let obj = {name : '1'}
//Object.getOwnPropertyNames()获取到对象中的全部属性名,存到一个数组中
let s = Object.getOwnPropertyNames(obj)
console.log(s); //['name'] //为[],代表空对象
let obj = {name : '1'}
//Object.keys()获取给定对象的所有可枚举属性的字符串数组
let s = Object.keys(obj)
console.log(s);//[ 'name' ] //若为[],则为空对象
let obj = {name : '1'}
// 注意for...in 会将对象原型链上的属性也枚举出来,所以要借hasOwnProperty()方法来判断是不是对象本身的属性
// 如果存在返回true,不存在返回false
let fn = (s) => {
for(let key in s) {
if(s.hasOwnProperty(key)) {
return false
}
return true
}
}
console.log(fn(obj));//false //若是反回true则就是空对象
和判断空对象类似的,我们只要能验证这个对象的keys
长度是0,那就是个空对象了。
Array.isArray(arr) & arr.length === 0
新的定义变量的方式 let const var 的区别
模板字符串
class
Promise
async/await
箭头函数
新的数组方法 filter some map every forEach reduce Array.from() find findIndex includes flat
拓展运算符
赋值解构运算符
Proxy
对象新增方法 Object.assign() Object.keys()
es6的模块化
Set
Map
CommonJS 是对模块的浅拷贝;ES6 Module 是对模块的引用,即 ES6。
CommonJS是动态编译,可以放在代码里动态执行;ES6 Module 是静态编译,引用只能放在最前面。
this指向(普通函数指向调用者,this指向父级作用域的this)
不可以被当做构造函数
不可以使用arguments对象,该对象在函数体内不存在,如果要用就用剩余参数替代
没有prototype属性
new构建函数可以在内存中创建一个空的对象
this会指向刚才创建的空对象
执行构造函数的代码给这个空对象添加属性和方法
返回这个对象(所以构造函数不需要return)
1.构造函数就是一个普通的函数,创建方法和普通函数没有区, 不同的是构造函数习惯上首字母大写。
2.构造函数与普通函数的区别就是调用方式不同,普通函数直接调用,而构造函数使用new关键字调用。
防抖debounce
定义:触发高频事件后n秒内函数只会执行最后一次,如果n秒内高频事件再次被触发,则重新计算时间。
原理:每次触发事件时都取消之前的延时调用方法
function debounce(fn) {
let timer = null; // 创建一个标记用来存放定时器的返回值
return function () {
// 执行这个函数之前先清掉定时器
clearTimeout(timer);
timer = setTimeout(() => {
// 绑定this的原因是为了让this指向正确
// 绑定arguments的原因是为了正确使用函数参数位置的事件对象e
fn.apply(this, arguments);
}, 500);
};
}
应用场景:
搜索框输入查询,如果用户一直在输入中,没有必要不停地调用去请求服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。
表单验证
按钮提交事件。
浏览器窗口缩放,resize事件(如窗口停止改变大小之后重新计算布局)等
节流throttle
定义:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
原理:每次触发事件时都判断当前是否有等待执行的延时函数
方法一:
function throttle(func, wait) {
var prev = 0;
return function() {
let now = Date.now();
if (now - prev > wait) {
func();
// 重置起始时间
prev = now;
}
}
}
方法二:
function throttle(fn) {
// 闭包保存是否可以开启定时器,默认是开启的
let canRun = true;
return function () {
// 如果没有开启就直接返回
if (!canRun) return;
// 开启了就立即关闭定时器入口,然后开启定时器
canRun = false;
setTimeout(() => {
fn.apply(this, arguments);
// 定时器运行后,下次定时器才可开启
canRun = true;
}, 500);
};
}
应用场景:
按钮点击事件
拖拽事件
onScoll
计算鼠标移动的距离(mousemove)
浅拷贝
展开运算符...
const array = [{ type: '' }, '', '']
const copyArray = [...array]
array[0].type = '' // 修改原数组
console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '' }, '', '']
Object.assign()
const array = [{ type: '' }, '', '']
const copyArray = Object.assign([], array)
array[0].type = '' // 修改原数组
console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '' }, '', '']
Array.prototype.concat()
const array = [{ type: '' }, '', '']
const copyArray = array.concat([])
array[0].type = '' // 修改原数组
console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '' }, '', '']
Array.prototype.slice()
const array = [{ type: '' }, '', '']
const copyArray = array.slice()
array[0].type = '' // 修改原数组
console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '' }, '', '']
Array.from()
const array = [{ type: '' }, '', '']
const copyArray = Array.from(array)
array[0].type = '' // 修改原数组
console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '' }, '', '']
深拷贝
1、JSON.parse(JSON.stringify(obj))
会忽略undefined Symbol
不能序列化函数
不能解决循环引用的对象
不能正确处理 new Date()
不能处理正则
2、浅拷贝+递归
module.exports = function clone(target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
对于 JS 数组去重来说,其实万变不离其中,我简单的总结了以下 4 种去重类型
1、数组元素比较型:
该方法是将数组的值取出与其他值比较并修改数组
双层 for 循环
取出一个元素,将其与其后所有元素比较,若在其后发现相等元素,则将后者删掉
function uniq(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN, NaN]
// 与 lodash 结果相比 NaN 没有去掉
复制代码
由于 NaN === NaN 等于 false,所以重复的 NaN 并没有被去掉
排序并进行相邻比较
先对数组内元素进行排序,再对数组内相邻的元素两两之间进行比较,经测试,该方法受限于 sort
的排序能力,所以若数组不存在比较复杂的对象(复杂对象难以排序),则可尝试此方法
function uniq(arr) {
arr.sort();
for (let i = 0; i < arr.length - 1; i++) {
arr[i] === arr[i + 1] && arr.splice(i + 1, 1) && i--;
}
return arr;
}
// 运行结果
//[[], [], 1, "1", [1], NaN, NaN, {}, {}, { a: 1 }, {}, { a: 1 }, "a", null, undefined];
// 与 lodash 结果相比 NaN 没有去掉,且对象的去重出现问题
同样由于 NaN === NaN
等于 false
,所以重复的 NaN 并没有被去掉,并且由于 sort
没有将对象很好的排序,在对象部分,会出现一些去重失效
2、查找数组元素位置型:
该类型即针对每个数组元素进行一次查找其在数组内的第一次出现的位置,若第一次出现的位置等于该元素此时的索引,即收集此元素
indexOf
以 indexOf
来查找元素在数组内第一次出现的位置,若位置等于当前元素的位置,则收集
function uniq(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (arr.indexOf(arr[i]) === i) {
res.push(arr[i]);
}
}
return res;
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null];
// 与 lodash 结果相比 少了 NaN
indexOf
采用与 ===
相同的值相等判断规则,所以在数组内没有元素与 NaN 相等,包括它自己,所以 NaN 一个都不会被留下
findIndex
以 findIndex
方法来查找元素在数组内第一次出现的位置
function uniq(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (arr.findIndex(item => item === arr[i]) === i) {
res.push(arr[i]);
}
}
return res;
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null];
// 与 lodash 结果相比 少了 NaN
结果原理和 indexOf
相同,因为用了 ===
的规则来判断元素是否相等,但此方法相当于双层 for
循环
3、查找元素是否存在型:
该方法基本依托 includes
方法来判断对应元素是否在新数组内存在,若不存在则收集
function uniq(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (!res.includes(arr[i])) {
res.push(arr[i]);
}
}
return res;
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 与 lodash 结果相同
includes
方法采用 SameValueZero 判断规则,所以可以判断出并去重 NaN
4、依托数据类型特性型:
该方案依托于数据类型的不重复特性,以达到去重效果
Map
Map 类型的数据可以像 Object 一样,在设定元素键值对的时候可以保证键的唯一,并且将键的类型拓展到了基本所有元素,包括对象,在设定好一个唯一键的 Map 数据类型后,再用其自带的 Map.prototype.keys()
方法取到相应的键类数组,最后将类数组进行一次转化即可
function uniq(arr) {
let map = new Map();
for (let i = 0; i < arr.length; i++) {
!map.has(arr[i]) && map.set(arr[i], true);
}
return [...map.keys()];
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 与 lodash 结果相同
Set
与 Map 类似,但是它里面每一项的值是唯一的,没有重复的值,Set是一个构造函数,用来生成set的数据结构。运用数据类型的特性完成去重,这个方法也是最热门的方法。
function uniq(arr) {
return [...new Set(arr)];
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 与 lodash 结果相同
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链。
简单地说:原型链就是查找规则,__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
区别 : 传参不一样
call()
改变this指向
可以调用函数
参数: 接受的是若干个参数的列表 call(this,1,2,3)
apply()
改变this指向
可以调用函数
参数: 参数的数组 apply(this,[1,2,3])
bind()
只改变this指向,不调用函数,比如用来改变定时器内部的this指向
class本质还是function
类的所有方法都定义在类的 prototype属性上
类创建的实例 里面也有 __ proto __ 指向类的 prototype 原型对象
所以 ES6 的类 他的绝大部分功能 es5 都可以做到 新的 calss 写法 只是让对象原型的写法 更加清晰 更像面向对象编程的语法而已
所以 ES6 的类 其实就是语法糖
类的内部所有定义的方法,都是不可枚举的(但是在es5中prototype的方法是可以进行枚举的)
类的构造函数,不使用new是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者可以直接调用
Class不存在变量提升(hoist),这一点与ES5完全不同
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
原型链继承
function Parent() {
this.name = 'kevin';
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Child() {
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child();
console.log(child1.getName()) // kevin
缺点:
在创建 Child 的实例时,不能向Parent传参
引用类型的属性被所有实例共享
借用构造函数继承
function Parent() {
this.names = ['kevin', 'daisy'];
}
function Child() {
Parent.call(this);
}
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy"]
优点:
可以在 Child 中向 Parent 传参
避免了引用类型的属性被所有实例共享
缺点:
只能继承父类的实例属性和方法,不能继承原型属性/方法
无法实现复用,每个子类都有父类实例函数的副本,影响性能
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child('kevin', '18');
child1.colors.push('black');
console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
var child2 = new Child('daisy', '20');
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]
缺点:
构建原型链的时候,Child.prototype的原型上面会有name、colors两个属性;在创建child1的时候它的实例上也会有name、colors。实例对象child1上的两个属性就屏蔽了其原型对象Child.prototype的两个同名属性。
所以,组合模式的缺点就是在使用子类创建实例对象时,会调用两次父构造函数,其原型链中会存在两份相同的属性/方法。
原型式继承
略。不重要可不说
寄生式继承
略。不重要可不说
寄生组合式继承⭐
对于组合式继承的缺点,思考一下,如何不调用两次父构造函数,不使用 Child.prototype = new Parent() ,而是间接的让 Child.prototype 访问到 Parent.prototype 呢?
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
// 关键的三步
var F = function () {};
F.prototype = Parent.prototype;
// Child.prototype = new F();
let f = new F()
f.constructor = Child
Child.prototype = f
var child1 = new Child('kevin', '18');
console.log(child1);
最后我们把第三步的方法封装一下
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function prototype(child, parent) {
var prototype = object(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
// 当我们使用的时候:
prototype(Child, Parent);
这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
ES6类继承extends⭐
ES6继承的原理跟寄生组合式继承是一样的。
定义
闭包就是能够读取其他函数内部变量的函数。例如在javascript
中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成定义在一个函数内部的函数。在本质上,闭包就是将函数内部和函数外部连接起来的桥梁。
优缺点:
能够访问局部变量
保护局部变量
由于闭包会将它的外部函数的作用域也保存在内存中
,因此会比其他函数更占用内存,这样的话,如果过度使用闭包,就会有内存泄露的威胁。解决方法——使用完变量后,手动将它赋值为null。
应用:
构造函数的私有属性。由于javascript中天然没有类的实现,某些不希望被外部修改的私有属性
可以通过闭包的方式实现
函数防抖、节流
浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。
浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强制缓存和协商缓存。
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存,协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存。
首先我们需要明白以下几件事情:
JS分为同步任务和异步任务
同步任务都在主线程上执行,形成一个执行栈
所谓 "异步",简单说就是一个任务不是连续完成的,先执行第一段,等做好了准备,再回过头执行第二段,第二段也被叫做回调;同步则是连贯完成的。
主线程之外,有一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
宏任务
可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
主要包含:script(整体代码)、setTimeout、setInterval、DOM事件
微任务
可以理解是在当前(主线程)任务执行结束后立即执行的任务。也就是说,在当前任务任务后,下一个任务之前,在渲染之前。
所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。
主要包含:Promise.then
运行机制(时间循环)
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
执行一个宏任务(全局Script脚本)
产生的的宏任务和微任务进入各自的队列中
宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
利用事件冒泡的原理,将原本绑定给子元素的事件,绑定给父元素,子元素触发的事件冒泡给父元素,让父元素触发事件,执行函数。
事件委托好处:1. 效率好;2. 对于新增的子元素事件依然生效
GET
GET请求是向服务端请求获取某个或某些资源(resource),比如查询数据库单个或list数据,服务端成功的话,一般状态码返回200。
POST
POST请求是用来向服务端请求新增资源(resource),处理成功的话,服务端一般返回状态码201。
PUT
PUT请求一般是用来向服务端请求修改某个已存在的资源(resource),服务端一般返回状态码200,204等。
DELETE
DELETE请求一般是用来向服务端请求删除某个已存在的资源(resource),服务端一般返回200,202等。
PATCH
PATCH请求一般是对某个资源做局部修改,如个别字段
Promise
Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息
promise有三种状态: pending(等待态),fulfiled(成功态),rejected(失败态);
状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
resolve reject都是函数, resolve用来处理成功的状态,reject用来处理失败的状态
如何访问promise实例中的数据
promise提供了两个方法 .then()处理成功 .catch()处理失败(不会同时存在2中状态)
promise基本概念
Promise 是一个构造函数
promise.prototype上包含一个 .then 方法
.then()方法 可以用来预先指定失败的回调函数
p.then(成功的回调函数 ,失败的回调函数) p.then(result=>{},error=>{})
调用p.then()方法时,成功的函数是必选的 失败的回调函数是可选的
async和await
await必须结合async使用,async通常不会单独使用
async表示这个函数是一个异步函数,它的返回值如果是一个普通数据类型,默认会用Promise将数据包裹
await 等待?等待什么?等待的是一个异步的结果
异步和同步从上往下的顺序执行
相对于promise的优点:
解决了地狱回调问题
更简洁 更容易调试 有更好的错误处理 有条件分支处理
处理中间值更简单 更清楚异常堆栈来自哪里
同源:2个页面的协议/域名/端口均相同为同源
同源策略 :A网站的js不允许和非同源的网站进行资源交互
跨域 :协议/域名/端口不一致时(会被浏览器拦截)
解决方案 :
JSONP 和 CORS和代理服务器
CORS:出现的较晚,它是 W3C 标准,属于跨域 Ajax 请求的根本解决方案。支持 GET 和 POST 请求。缺点是不兼容某些低版本的浏览器
原理: 设置允许的响应头(需要浏览器和服务器同时支持).后台里面设置
JSONP:出现的早,兼容性好(兼容低版本IE)。是前端程序员为了解决跨域问题,被迫想出来的一种临时解决方案。
原理:根据< script>标签的src属性不受浏览器同源策略的限制
动态创建< script>标签,结合它的src属性传递可查询参数
参数的值是一个函数名(函数名最好随机生成,防止变量污染)
服务器返回的是一段函数执行代码
缺点是只支持 GET 请求,不支持 POST 请求。
实现过程:
script标签里的src属性动态创建和移除
在Jsonp请求时,动态向
中append(添加)一个