JS
继承
- 通过原型链继承
function Parent() {
this.lastName = "wang" //父类属性
}
Parent.prototype.asset = ['house', 'car'] //父类原型属性
function Child() {
}
Child.prototype = new Parent() //将父类的实例作为子类的原型 !!! 原型链继承的核心
var child = new Child();
var child1 = new Child();
child.asset.push("plane")
console.log(child.lastName);//"wang" //继承父类属性
console.log(child1.asset);//[ 'house', 'car', 'plane' ] // 继承父类属性 可以修改父类属性
优点:可以访问父类的属性和方法和原型上的属性和方法
缺点:继承方法如果是引用类型,其中一个子类进行修改,那么全部都会受到影响
- 通过构造函数继承
用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
function Parent() {
this.lastName = "wang";
this.hobby = ['a', 'b']
}
Parent.prototype.asset = ['house', 'car']
function Child() {
Parent.call(this) // 在子类构造函数中调用父类的构造函数 !!! 构造函数继承的核心
}
var child = new Child();
var child1 = new Child();
child.hobby.push("c")
console.log(child.lastName);//“wang" //继承父类属性
console.log(child1.hobby);//['a', 'b'] //引用类型不会被修改
console.log(child1.asset);//undefined // 无法继承父类原型链上的属性
优点:可以保证每个子类维护自己的属性
缺点:无法访问原型链上的属性和方法
- 组合继承(组合原型链继承和借用构造函数继承)
function Parent() {
this.lastName = "wang";
this.hobby = ['a', 'b']
}
Parent.prototype.asset = ['house', 'car']
function Child() {
Parent.call(this) //调用父类构造函数
}
Child.prototype = new Parent(); // 第二次调用父类构造函数
var child = new Child();
var child1 = new Child();
child.hobby.push("c")
console.log(child.lastName); // wang // 继承父类属性
console.log(child1.hobby); // [ 'a', 'b' ] 引用类型不会被修改
console.log(child1.asset); // [ 'house', 'car' ] 继承父类原型链上的属性
- 原型式继承 没有使用构造函数,借助原型可以基于已有的对象创建新对象,同时还不必创建自定义类型。就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型
function createObj(o){ //传入一个对象
function F(){}
F.prototype = o;
return new F(); // 相当于对传入对象的浅拷贝
}
let dog = {
name: "wang"
}
dog1 = createObj(dog); //dog1.name = wang
- 寄生式继承
function create(obj){
var clone = Object.create(obj);
clone.getColor = function(){
console.log(clone.color)
}
return clone
}
var dog = {
species: '贵宾犬',
color: 'yellow'
}
var dog1 = create(dog);
dog1.getColor(); // yellow
- 寄生式组合继承(优化组合继承)
function Parent() {
this.lastName = "wang";
this.hobby = ['a', 'b']
}
Parent.prototype.asset = ['house', 'car']
function Child() {
Parent.call(this) //调用父类构造函数
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype = Parent.prototype; //子类原型指向父类原型
Child.prototype.constructor = Child; // 子类构造函数指向自身构造函数
var child = new Child();
var child1 = new Child();
child.hobby.push("c")
console.log(child.lastName); // wang // 继承父类属性
console.log(child1.hobby); // [ 'a', 'b' ] 引用类型不会被修改
console.log(child1.asset); // [ 'house', 'car' ] 继承父类原型链上的属性
- ES6 Class继承
class Parent{
constructor(){
this.name = 'Parent'
this.arr = [1, 2, 3]
}
say(){
console.log(this.name)
}
}
class Child extends Parent{
constructor(){
super() //通过super()调用父类构造函数
this.type="Child"
}
}
let child1 = new Child()
let child2 = new Child()
child1.arr.push(4)
console.log(child1.say()) // Parent // 继承父类方法和属性
console.log(child1.arr, child2.arr) // [1, 2, 3, 4] [1, 2, 3] // 引用类型属性隔离
console.log(child1.constructor === Child) // true // 子类指向其构造函数
console.log(child2.constructor === Parent) // false
跨域
1、JSONP
基于script标签不会进行同源验证的原理,实现跨域
2、cors同源
cors同源是值服务端修改自己的请求头,修改“Access-Control-Allow-Origin”的值,需要服务端进行配置
3、webSocket
this指向
- 在对象的方法中使用,this指向当前的对象
- 通过call\apply\bind来指定
- 箭头函数 this指向作用域链上一层的this
- new 关键字调用函数时,函数中的 this 一定是 JS 创建的新对象
new过程
- 创建一个新对象
- 把新对象的_proto_隐式原型指向构造函数的原型
- 执行构造函数中的代码,并把this指向新对象
- 返回新对象
function my_new(constructor, ...args) {
// 创建一个新的对象
const obj = {}
// 将 构造函数 的 原型对象 赋值给新建对象的 隐式原型
obj._proto_ = constructor.prototype
// 执行 构造函数 并将 构造函数里面的this指向 新建对象obj
const result = constructor.call(obj, ...args)
// 如果 执行结构是一个对象,那么就返回执行结果 否则返回新建对象
return typeof result === 'object' ? result : obj
}
数据类型判断
typeof
typeof可以判断原始数据类型,除了null之外
console.log(typeof 2) //number
console.log(typeof 'hello') //string
console.log(typeof null) //object
console.log(typeof true)//boolean
console.log(typeof undefined) //undefined
console.log(typeof []) //object
instanceof
instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的prototype:
console.log([] instanceof Array) //true
console.log({} instanceof Object)//true
console.log(function(){} instanceof Function)//true
console.log(1 instanceof Number)//false
Object.prototype.toString.call
使用 Object 对象的原型方法toString,返回值是[object 类型]字符串,该方法基本上能判断所有的数据类型.
var toString = Object.prototype.toString;
console.log(toString.call(2)) //[object Number]
console.log(toString.call(true)) //[object Boolean]
console.log(toString.call(function(){})) //[object Function]
Promise
Promise对象的状态不受外界影响,它有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。它的状态可以从pending变为fulfilled,或者从pending变为rejected
// MyPromise.js
// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 新建 MyPromise 类
class MyPromise {
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
executor(this.resolve, this.reject)
}
// 储存状态的变量,初始值是 pending
status = PENDING;
// resolve和reject为什么要用箭头函数?
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 用箭头函数就可以让this指向当前实例对象
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
}
}
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
}
}
then(onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
}
}
}
作用域
函数在执行的时候遇到一个变量,他会先看看自己的作用域里有没有该变量,没有的话就会向上从父级作用域里去查找,直到找到位置,否则报错undefined
闭包及闭包常用应用场景
闭包的定义:当函数可以记住并访问所在的作用域时,就产生了闭包,即使这个函数是在当前词法作用域之外执行。 再来说的直白一点,闭包就是一个有权限访问其所在作用域中变量的一个函数。
应用场景: 节流防抖 https://segmentfault.com/a/1190000018428170
function foo(){
var a=3;
function bar(){
console.log(a);
}
return bar;
}
const a=foo()
a()
节流、防抖
//节流 执行第一次,间隔时间到后再执行后续
function throttle(fn, time) {
var timer;
return function() {
if (timer) return; //如果标记没更新 不执行函数
timer = setTimeout(() => {
clearTimeout(timer)
timer = null;
fn();
}, time);
}
}
//防抖 执行最后一次,间隔时间内触发方法 重新计时
function debounce(fn, time) {
var timer;
return function() {
if (timer) {
clearTimeout(timer) // 如果第二次执行 还在间隔时间内 清空定时器
timer = null;
}
timer = setTimeout(() => { //延迟time执行fn
clearTimeout(timer)
timer = null;
fn();
}, time);
}
}
原型和原型链
原型就是一个叫做 prototype的对象。
在js中每个数据类型都是一个个对象,即使像number、string这样的简单数据类型也是由Number、String这些构造函数实例出来的。
这些个实例对象本身都有一个__proto__属性,这个属性保存的(一般情况下)是它构造函数原型的引用。
函数也是对象,但它能作为构造函数生成实例对象。它的特殊在于它除了拥有__proto__之外还拥有一个Prototype 这个Prototype就是主角
function Foo(){
}
var foo = new Foo();
// 原型链
foo.__proto__ === Foo.prototype // 对象的隐式原型 指向其构造函数的 原型
Foo.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ = null
Event Loop
在js中,任务进入执行栈,先判断任务类型,如果是同步任务,直接进入到主线程执行。如果是异步任务,会把任务放到异步队列,等同步任务执行完以后,事件触发线程会从消息队列中取出刚才加入队列的函数,如果有,就一条一条的去执行。
一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么
1、浏览器会开启一个线程来处理这个请求,对 URL 分析判断如果是 http 协议就按照 Web 方式来处理;
2、调用浏览器内核中的对应方法,比如 WebView 中的 loadUrl 方法;
3、通过DNS解析获取网址的IP地址,设置 UA 等信息发出第二个GET请求;
4、进行HTTP协议会话,客户端发送报头(请求报头);
5、进入到web服务器上的 Web Server,如 Apache、Tomcat、Node.JS 等服务器;
6、进入部署好的后端应用,如 PHP、Java、JavaScript、Python 等,找到对应的请求处理;
7、处理结束回馈报头,此处如果浏览器访问过,缓存上有对应资源,会与服务器最后修改时间对比,一致则返回304;
8、浏览器开始下载html文档(响应报头,状态码200),同时使用缓存;
9、文档树建立,根据标记请求所需指定MIME类型的文件(比如css、js),同时设置了cookie;
10、页面开始渲染DOM,JS根据DOM API操作DOM,执行事件绑定等,页面显示完成。
CSS
怎么让一个div水平垂直居中
- position + transform
- 使用flex
.parent{
display: flex;
align-items: center;
justify-content: center;
}
- display:table
.parent{
display: table;
}
.child{
display: table-cell;
vertical-align: middle;
text-align: center;
}
css实现三角形
div{
width: 0;
height: 0;
border-top: 10px solid transparent;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid #000;
}
flex布局
- display:flex; 在父元素设置,子元素受弹性盒影响,默认排成一行,如果超出一行,按比例压缩
- flex:1; 子元素设置,设置子元素如何分配父元素的空间,flex:1,子元素宽度占满整个父元素
- align-items:center 定义子元素在父容器中的对齐方式,center 垂直居中
- justify-content: center 设置子元素在父元素中居中,前提是子元素没有把父元素占满,让子元素水平居中
- flex-direction:决定主轴的方向(即项目的排列方向)
flex:1 含义
flex 是 flex-grow、flex-shrink、flex-basis的缩写。
- flex-grow:属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
- flex-shrink: 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
- flex-basis: 属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
伪元素和伪类
- 伪类
伪类就是一个选择处于特定状态的元素的选择器 一般用单冒号
:avtive :disabled :hover :first-child 等等 - 伪元素
伪元素是创建了一个文档外的元素 一般用双冒号
::before ::after
重绘和重排
- 重排:当渲染树的一部分必须更新并且节点的尺寸发生了变化,浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树
- 重绘: 是在一个元素的外观被改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观
BFC
- BFC里面的box都会以垂直方向排列
- 同一个BFC里面中相邻的两个盒子的外边距会重叠
- BFC的区域不会和float Box重叠
- BFC就是一个独立的容器,容器里面的元素不会影响到外面的元素,反之也如此
VUE
生命周期 https://segmentfault.com/a/1190000019743049
1. beforeCreat() 创建前
实例初始化在这个生命周期遍历 data 对象下所有属性将其转化为 getter/setter, 也就是说添加一个被观察者
2. created()创建
这个生命周期的时候 实例已经被创建完毕 属性已经绑定 属性是可以操作的 但是dom还不存在
3. beforeMount()挂载前
这个生命周期做的是模板编译 el还未对数据进行渲染 还是不能操作dom
4. mounted()挂载
这个生命周期 虚拟dom已经被渲染到了真实的dom
5. beforeupdate()数据更改前 6. updated()数据更改
mounted更新一个属性的话 beforeUpdate 和 updated 生命周期函数 会被触发 但是仅限于 被观察的属性做出的变化且被引用他们才会触发
而且需要注意的是 不要在updated 函数里边直接就修改属性 会进入死循环
7. beforeDestroy()销毁前
这个生命周期 是组件销毁前的时候 还是可以对元素进行操作的
8. destroyed()销毁
销毁这个生命周期执行过后 实例的属性和方法就不能再用了
vue双向绑定的原理是什么
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:
1、需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
2、compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
3、Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
4、MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
总体流程:
- 组件初始化时候 对每个vue属性用Object.defineProperty()实现数据劫持,为每个属性分配一个订阅者集合的管理数组dep
- Compile解析模板指令的时候(v-model、{{}}、v-bind)在该属性的数组dep中添加订阅者
- Dep是一个订阅器,被observe的数据都有一个Dep实例,Dep实例里面存放了N个订阅者
- 数据变化时 触发setter,会遍历观察者列表(dep.subs),通知相关的Watcher
- Watcher 订阅者会接收到来自dep的通知,执行自己的update()
nexttick原理
- 修改数据,这是同步任务。同一事件循环的所有的同步任务都在主线程上执行,形成一个执行栈,此时还未涉及 DOM
- Vue 开启一个异步队列,并缓冲在此事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次
- 同步任务执行完毕,开始执行异步 watcher 队列的任务,更新 DOM
data()为什么是函数
组件中的data写成一个函数,data数据以函数返回值形式定义,这样每复用一次组件,就会生成一个新的data对象,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。
keep-alive
缓存实现原理
patch阶段,会执行createComponent函数
- 首次访问时,会把组件缓存到this.cache里
- 再次访问被包裹组件时,vnode.componentInstance的值就是已经缓存的组件实例,这样就直接把上一次的DOM插入到父元素中
虚拟DOM
- 左边:DOM
- 右边:虚拟DOM
Diff算法
- 只比较同一层级,不跨级比较
- tag不相同,则直接删掉重建不再深度比较
- tag和key,两者都相同,则认为是相同节点,不再深度比较
组件渲染过程
- generate(将AST语法树转化成render function字符串的过程)得到render函数
- 执行render函数,生成vnode(即虚拟DOM,VNode理解为虚拟DOM的总节点)
Computed实现原理 https://segmentfault.com/a/1190000010408657
- data 属性初始化 getter setter
- computed 计算属性初始化,提供的函数将用作属性 vm.reversedMessage 的 getter
- 当首次获取 reversedMessage 计算属性的值时,Dep 开始依赖收集
- 在执行 message getter 方法时,如果 Dep 处于依赖收集状态,则判定 message 为 reversedMessage 的依赖,并建立依赖关系
- 当 message 发生变化时,根据依赖关系,触发 reverseMessage 的重新计算
父子、兄弟组件传值
- props
- vue.$emit / vue.$on
- vuex
VUEX
store是一个仓库,仓库保存着项目的数据(store.state);
为了解决无法追踪状态变化的问题,仓库里的数据不直接通过修改store.state来改变,而是通过commit一个动作告诉仓库,直接修改将会报错 [vuex] Do not mutate vuex store state outside mutation handlers
仓库收到了commit的动作后,将会在store.mutation里查找对应事件,并改变store.state
this.$store.commit(mutation) 是更改状态的唯一方法,并且这个过程是同步的 异步逻辑都应该封装到 action 里面 调用时候dispatch(action) 异步完成后回调里再commmit mutation
vue-router
import router from './router'
// 路由守卫
router.beforeEach((to, from, next)=>{
if(window.sessionStorage.userData){ // 判断是否登录
if(to.path === '/' || to.path === '/login'){ //
//登录状态下 访问login.vue页面 会跳到index.vue
next({path: '/index'});
}else{
next();
}
}else{
// 如果没有session ,访问任何页面。都会进入到 登录页
if (to.path === '/') { // 如果是登录页面的话,直接next() -->解决注销后的循环执行bug
next();
} else { // 否则 跳转到登录页面
next({ path: '/' });
}
}
})
webpack vueCli
webpack构建原理
Webpack在启动后,会从Entry开始,递归解析Entry依赖的所有Module,每找到一个Module,就会根据Module.rules里配置的Loader规则进行相应的转换处理,对Module进行转换后,再解析出当前Module依赖的Module,这些Module会以Entry为单位进行分组,即为一个Chunk。因此一个Chunk,就是一个Entry及其所有依赖的Module合并的结果。最后Webpack会将所有的Chunk转换成文件输出Output。在整个构建流程中,Webpack会在恰当的时机执行Plugin里定义的逻辑,从而完成Plugin插件的优化任务。
常用loader
- style-loader : 用于将css编译完成的样式,挂载到页面style标签上。需要注意loader执行顺序,style-loader放到第一位,因为loader都是从下往上执行,最后全部编译完成挂载到style上
- css-loader : 用于识别.css文件, 处理css必须配合style-loader共同使用,只安装css-loader样式不会生效。
- postcss-loader: 打包css样式,自动添加前缀
- file-loader / url-loader: 图片打包使用的是url-loader。可以设置当打包图片小于某个值时打包成base64。
- babel-loader @babel/core @babel/preset-env es6+ 转 es5
loader plugin
HTTP
HTTP和TCP
- TCP 建立起一个TCP连接需要经过“三次握手”,断开过程需要经过“四次挥手”
- HTTP 协议是建立在TCP协议之上的一种应用 请求结束后,会主动释放连接
三握四挥
浏览器缓存
强缓存: Expires、Cache-Control
协商缓存:Last-Modified / If-Modified-Since 和 ETag / If-None-Match
-
Expires
Expires: Thu, 10 Dec 2015 23:21:37 GMT在此日期之前,客户端都会认为缓存是有效的 -
Cache-Control
Cache-Control: max-age=3600在一段时间(3600秒)内缓存 -
Last-Modified / If-Modified-Since
Last-Modified: Mon, 30 Nov 2015 23:21:37 GMT客户端收到资源最后一次修改时间
If-Modified-Since: Mon, 30 Nov 2015 23:21:37 GMT下次请求时候 带上时间 服务端判断是否缓存 304 -
ETag / If-None-Match
Etag是个标记 请求时候通过If-None-Match带上标记 服务端判断标记是否修改
HTTP状态码
- 200 - 请求成功
- 301 - 资源(网页等)被永久转移到其它URL
- 404 - 请求的资源(网页等)不存在
- 500 - 内部服务器错误
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误
HTTP和HTTPS
HTTPS和HTTP的区别主要如下:
- https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
- http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
HTTPS
- 客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
- Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
- 客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
- 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
- Web服务器利用自己的私钥解密出会话密钥。
- Web服务器利用会话密钥加密与客户端之间的通信。