理解知识
-
valueOf和toString区别 总结:valueOf偏向于运算,toString偏向于显示。
-
在进行对象转换时,将优先调用toString方法,如若没有重写 toString,将调用 valueOf 方法;如果两个方法都没有重写,则按Object的toString输出。
-
在进行强转字符串类型时,将优先调用 toString 方法,强转为数字时优先调用 valueOf。
-
使用运算操作符的情况下,valueOf的优先级高于toString。
-
使用==会优先触发valueof,没有valueof会触发tostring(基本都是有valueof先用valueof的,没有才使用tostring)。而===不会触发这两个方法,不过会触发
let value = 1;
Object.defineProperty(window, 'a', {
get() {
return value++
}
})
if (a === 1 && a === 2 && a === 3) {
console.log("Hi Libai!")
}
装箱:把基本数据类型转化为对应的引用数据类型的操作。每当读取一个基本类型的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。 拆箱: 将引用类型对象转换为对应的值类型对象。是通过引用类型的valueOf()或者toString()方法来实现的。如果是自定义的对象,你也可以自定义它的valueOf()/tostring()方法,实现对这个对象的拆箱。 例子: 为什么基本数据类型可以像对象那样调用方法?
//装箱
var s1 = "abc";
var s2 = s1.indexOf("a")
//装箱过程
(1)创建String类型的一个实例;
(2)在实例上调用指定的方法;
(3)销毁这个实例;
代码方式实现:
var s1 = new String("abc");
var s2 = s1.indexOf('a');
s1 = null;
//拆箱
var objNum = new Number(123);
var objStr =new String("123");
console.log( typeof objNum ); //object
console.log( typeof objStr ); //object
console.log( typeof objNum.valueOf() ); //number
console.log( typeof objStr.valueOf() ); //string
console.log( typeof objNum.toString() ); // string
console.log( typeof objStr.toString() ); // string
技巧: 判断==隐式转换: + 对象==其他,对象使用toString进行转换,[0,1,2].toString()为0,1,2 + null == undefined为真,其他都假 + 剩余的所有都转换为数字进行比较
-
- 作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6的到来,为我们提供了‘块级作用域’,可通过新增命令let和const来体现。
- 作用域链为当前作用域没有定义的变量会向父级作用域寻找(说法不严谨),正确为要到创建fn函数的那个作用域中取,无论fn函数将在哪里调用。要到创建这个函数的那个域”。 作用域中取值,这里强调的是“创建”,而不是“调用”,切记切记——其实这就是所谓的"静态作用域"
var x = 10 function fn() { console.log(x) } function show(f) { var x = 20 (function() { f() //10,而不是20 })() } show(fn)- 执行上下文和作用域链区别:执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。
-
DOM和BOM
- DOM 事件三要素:事件源,事件类型和事件处理程序(谁,什么事件,做啥)
常见鼠标事件
操作元素总结:
- 模块化 参考
为什么需要模块化:
- 模块化解决的痛点:如下代码,多人作业的时候会导致全局变量污染的问题,因此会出现一些不必要的错误
// 小明写的 a.js
var flag = true
// 小红写的 c.js
var flag = false
// 小明写的 b.js
if (flag) {
console.log('我接受到了true的flag');
}
//在html文件引入的时候
<script src="a.js"></script>
<script src="b.js"></script>
<script src="c.js"></script>
使用自执行函数,因为自执行函数会产生属于自己的作用域从而函数内部变量不会被暴露。但是引发的问题就是其他地方不可以使用自定义函数定义的变量和方法,代码复用性变低。因此我们出现模块化,在自执行函数执行后返回一个对象,对象中只要挂载着需要暴露的方法和属性,这样我们只需要保证返回的moduleA不要被重名就可以解决问题。代码如下所示:
// 小明写的 a.js
var moduleA = (function () {
var obj = {}
obj.flag = true;
return obj
})()
// 小红写的 c.js
var moduleB = (function () {
var obj = {}
obj.flag = false;
return obj
})()
// 小明写的 b.js
(function () {
if (moduleA.flag) {
console.log('我接受到了true的flag');
}
})()
其他解决办法: - 如果不考虑兼容可以使用ES6的export/export default和import来解决 ```js //a.js let a = 1; let b= {}; function sum(){} class Person(){constuctor{}} export {a,b,sum,Person}
//b.js使用
import {a,b,sum,Person} from '/a.js'
//或者 import * as aObj from '/a.js' ---- console.log(aObj.a)
let p = new Person()
console.log(a,b,sum)
//a.js
export const b = 1;
export var d = 3;
//b.js使用
import {b,d} from '/a.js'
console.log(b,d)
//a.js
const aa = '11';
let bb = 111;
export default {
aa,bb
};
//b.js使用
import aObj from '/a.js'
console.log(aObj.aa,aObj.bb)
```
引入其他库CommonJS等
vue
+ 插槽
```vue
//具名插槽
//组件
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
//使用
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
//等价于,更明确显示
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
//缩写
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
//渲染后效果
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
```
```vue
//作用域插槽
//组件
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
//使用
//slotProps为任意名字
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
//插槽名字为other
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
```
- vue插件编写
- vue-install
- 更多参考 实现的效果为:
import Comp from './comp.js'
Vue.use(Comp)
插件的几种方式: Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:
export default {
install(Vue, options) {
Vue.myGlobalMethod = function () { // 1. 添加全局方法或属性,如: vue-custom-element
// 逻辑...
}
Vue.directive('my-directive', { // 2. 添加全局资源:指令/过滤器/过渡等,如 vue-touch
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
Vue.mixin({
created: function () { // 3. 通过全局 mixin方法添加一些组件选项,如: vuex
// 逻辑...
}
...
})
Vue.prototype.$myMethod = function (options) { // 4. 添加实例方法,通过把它们添加到 Vue.prototype 上实现
// 逻辑...
}
}
}
//封装公用组件
import component from './Cmponent.vue'
const component = {
install:function(Vue){//vue为构造函数
Vue.component('component-name',component)
} //'component-name'这就是后面可以使用的组件的名字,install是默认的一个方法 component-name 是自定义的,我们可以按照具体的需求自己定义名字
}
// 导出该组件
export default component
//main.js
import componentName from './component
Vue.use(componentName)
- vue父子组件生命周期执行顺序
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程
父beforeUpdate->父updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
-
(vue3新特性)[zhuanlan.zhihu.com/p/92143274] 其他参考
- Object.defineProperty -> Proxy
- 代理的是对象而不是对象的属性
- Virtual DOM 重构 +
- 更多编译时优化
- 会自动识别某个节点是否是动态的,如果是动态的会自动生成标识(不同的动态会有不同的标识对应,如内容文本的动态,或者id的动态),从而在每次更新dom时,直接跳过哪些静态的节点,直接定位到动态的节点,大大节省效率。
- 事件开缓存
- template中不需要用一个div包裹即没必要只有一个根节点,可以多个标签(节点)并列
- Object.defineProperty -> Proxy
-
webpack---webpack面试题
-
Vue.js的template编译的理解? 答:简而言之,就是先转化成AST树,再得到的render函数返回VNode(Vue的虚拟DOM节点) 详情步骤: 首先,通过compile编译器把template编译成AST语法树(abstract syntax tree 即 源代码的抽象语法结构的树状表现形式),compile是createCompiler的返回值,createCompiler是用以创建编译器的。另外compile还负责合并option。 然后,AST会经过generate(将AST语法树转化成render funtion字符串的过程)得到render函数,render的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有(标签名、子节点、文本等等)
-
vue模板编译由.vue等html代码经过安装的依赖vue-teplate-compiler来解析成h('a',{props: {...}},[h('div','内容'])这个形式的?
-
vue如何通过h('a',{props:{...}},'内容')进行转换为VNode,即{elm: 'a',sel: 'a',data: {props:...},text:'内容'}这个结构?
-
VNode如何通过pacth打补丁的形式来改变渲染真实的dom,即diff算法?
- diff算法中如果没有带key的时候实现在同一个虚拟节点上后面添加子节点,如果使用patch(oldVNode,newVNode)时候,会进行比较最小化比较,即老的节点会不更新,只会在后面添加新的节点。但是如果进行的为插入或头部来添加的时候,即使方法还是使用patch(oldVNode,newVNode)也会将插入会开始插入的位置开始到最后都会被重新渲染,即如下图所示:
- diff算法中如果没有带key的时候实现在同一个虚拟节点上后面添加子节点,如果使用patch(oldVNode,newVNode)时候,会进行比较最小化比较,即老的节点会不更新,只会在后面添加新的节点。但是如果进行的为插入或头部来添加的时候,即使方法还是使用patch(oldVNode,newVNode)也会将插入会开始插入的位置开始到最后都会被重新渲染,即如下图所示:
-
pacth打补丁这个过程是在改变数据的时候调用发布者dep的notify()来通知维护的订阅者watcher,然后遍历该watcher的update()从而调用pacth实现真实dom打补丁
-
this.$set()原理参考
-
vue的数组的原型不是直接指向 Array.prototype,是指向了一层arrayMethods,然后arrayMethods的原型才指向Array.prototype,而arrayMethods该层重写了array的7个方法(push,pop,unshift,shift,splice,sort,reverse),重写的这7 个方法中都实现了响应式,因此调用这7种方法下才会触发vue的响应式,除此之外是没有实现响应式效果的。例如arr[1]=1,这些因为没有被在arrayMthods的重写的7个方法中,因此没有实现响应式,因此用该实现的赋值是不能实现响应式的。
-
调用Vue.set()或者this.$set()方法其实都是调用了vue的set(),区别就是一个在构造函数的挂载的方法,一个是在原型上挂载的方法。内部是通过调用arrayMthods来取代arr[1]来实现替换效果。因此实现了响应式。
-
对象的时候,如果一个对象有_ob_属性说明该对象是被vue实现响应式了,即直接更改该属性就可以直接实现响应式操作。
-
vue.set()真正处理对象的地方为:
defineReactive(ob.value, key, val).使用了该方法之后就会给新加的属性添加响应式处理,以后直接修改该属性就可以实现响应式。
-
-
浏览器缓存
- ajax缓存参考:在$.ajax设置cache属性为false。实际工作就是在请求的URL上添加一个时间戳。因为ajax缓存是当请求是get请求的时候,会对URL和参数都没变化的请求进行缓存。而添加了时间戳就可以实现不变缓存的效果。其他办法添加
xmlHttpRequest.setRequestHeader(“Cache-Control”,”no-cache”);在请求头上添加后无论采用的强缓存还是协商缓存都不会生效,优先级最高,这就是我们使用强制刷新(Crtl+f5)效果。 代码参考
//方法1 $.ajax({ type: "get", url:"http://127.0.0.1:4564/bsky-app/template/testPost", ... //这里是重点 beforeSend: function (XMLHttpRequest) { xmlHttpRequest.setRequestHeader(“Cache-Control”,”no-cache”) } }); //方法2 $.ajax({ type: type, headers: { 'Access-Token':$.cookie('access_token') }, url: url, data: data, success: function(data) { }, error: function(err) { }, complete: function(XMLHttpRequest, status) { //请求完成后最终执行参数 } }); - ajax缓存参考:在$.ajax设置cache属性为false。实际工作就是在请求的URL上添加一个时间戳。因为ajax缓存是当请求是get请求的时候,会对URL和参数都没变化的请求进行缓存。而添加了时间戳就可以实现不变缓存的效果。其他办法添加
浏览器相关
- 浏览器渲染机制 参考
网络请求
- 构建请求
- 查找强缓存
- DNS解析,先查找浏览器的DNS缓存(如果之前被解析过会被缓存起来)
- TCP三次握手四次挥手
- TCP四次挥手(待完善)
- TCP握手
- 请求:请求行,请求头,请求体
- 响应:响应行,响应头,响应体
- 是否请求发完就会进行四次握手操作断开请求?否,还取决请求头或者响应头中是否包含Connection:keep-alive,若存在则说明会建立持久连接,不过还是一请求一响应形式,服务器不能主动响应,即省去了频繁三次握手和四次挥手操作。
- 是否请求发完就会进行四次握手操作断开请求?否,还取决请求头或者响应头中是否包含Connection:keep-alive,若存在则说明会建立持久连接,不过还是一请求一响应形式,服务器不能主动响应,即省去了频繁三次握手和四次挥手操作。
解析算法
完成网络请求和响应,若响应头中Content-Type的值为text/html,浏览器就会开始解析和渲染
- 构建DOM树(生成DOM树)
- (页面打开的时候就会启动一个执行栈,执行栈是为js执行的地方)。拿到需要解析的文本进行从上往下执行,一行一行执行,执行完后的代码会进行出栈操作,如果碰到为img,script,link,video等会由浏览器另起线程和js线程并行操作。即异步请求。
- 为什么需要构建dom树,因为浏览器无法直接理解html字符串。dom树本质是一个以document为根节点的多叉树。
- 解析算法:标记化算法(词法解析)和建树(语法解析)
- 标记化算法: 遇到'<'标记打开,遇到'>'标记记录完成。
- 建树算法:解析器首先会构建一个documenmt对象, 标记生成器会把每个标记的信息发送给建树器。建树器接收到相应的标记时,会创建对应的 DOM 对象。
- 样式计算(生成CSSOM)
- link标签引用
- style标签中的样式
- 元素嵌套style属性
- 生成布局树(Render Tree)
css
+ css可继承的属性:color,text-align
+ 文字溢出省略号显示,`white-space:nowrap;text-overflow:ellipsis;overflow:hidden;`
+ 多行文本溢出
```
overflow : hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
```
- scss使用技巧
- 嵌套
- 变量:
$red: red; body{ color: $red;} - 引用父级选择器“&”
- 混合
@mixin border-radius($radius) { border-radius: $radius; -ms-border-radius: $radius; -moz-border-radius: $radius; -webkit-border-radius: $radius; } .box { @include border-radius(10px); }
算法
- 多维数组转换为一维数组
//es6的flat,默认只拉平一层
let arr = [1, [2, 3, [4, 5], 6]];
//es6
arr.flat(Infinity); //let arr = [1, 2, 3, 4, 5, 6];
//es6的reduce方法
const flatten=arr=>arr.reduce((a,b)=>a.concat(Array.isArray(b)?flatten(b):b),[])
//递归
function flat(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
Array.isArray(arr[i]) ? res = res.concat(flat(arr[i])) : res.push(arr[i]);
}
return res;
}