CSS
盒子模型
标准盒模型:外边距margin 内边距padding 边框border 内容content
IE怪异盒模型:外边距margin 内容content(padding + border + content)
转换盒模型: box-sizing: border-box
box-sizing: content-box
line-height 与 height
line-height是每行文字的高,换行则整个盒子高度增大
CSS画三角形
border 设置其他三边为透明
不给宽高设置垂直居中
方式一:设置父元素
.parent {
display: flex;
justify-content: center;
align-items: center;
}
方式二:
.parent {
position: relative;
}
.child {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%)
}
display有哪些值
BFC(块级格式化上下文)的理解
BFC是页面上一个隔离的独立容器,容器里的子元素不会影响外部元素。
1.了解BFC:块级格式化上下文
2.原则:一个有BFC的元素,无论内部元素怎么改变,都不会影响到外层元素。
3.触发BFC:
float的值非none
overflow的值非visible
display值为: inline-block、table-cell…
position值为:absolute、fixed
清除浮动
1.触发BFC
2.多创建一个盒子,添加样式clear:both;
3. :after方式ul:after { content: ''; display: block; clear: both }
position有哪些值,分别根据什么定位。
static
fixed 固定定位 相对于浏览器窗口进行定位
relative 相对定位 相对于自身进行定位,不脱离文档流
absolute 绝对定位 相对于有relative定位的父元素进行定位,脱离文档流
sticky 粘性定位 基于用户的滚动位置来定位,在 position:relative 与 position:fixed 定位之间切换
CSS单位
px 表示像素,所谓像素就是呈现在我们显示器上的一个个小点,每个像素点都是大小等同的,所以像素为计量单位被分在了绝对长度单位中
em 是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸(1em = 16px)为了简化 font-size 的换算,我们需要在css中的 body 选择器中声明font-size= 62.5%,这就使 em 值变为 16px*62.5% = 10px
特点:
em 的值并不是固定的
em 会继承父级元素的字体大小
em 是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸
任意浏览器的默认字体高都是 16px
rem 相对单位,相对的只是HTML根元素font-size的值
同理,如果想要简化font-size的转化,我们可以在根元素html中加入font-size: 62.5%特点:
rem单位可谓集相对大小和绝对大小的优点于一身
和em不同的是rem总是相对于根元素,而不像em一样使用级联的方式来计算尺寸
vw、vh vw,就是根据窗口的宽度,分成100等份,100vw就表示满宽,50vw就表示一半宽。(vw 始终是针对窗口的宽),同理,vh则为窗口的高度
这里的窗口分成几种情况:
在桌面端,指的是浏览器的可视区域
移动端指的就是布局视口
像vw、vh,比较容易混淆的一个单位是%,不过百分比宽泛的讲是相对于父元素:
对于普通定位元素就是我们理解的父元素
对于position: absolute;的元素是相对于已定位的父元素
对于position: fixed;的元素是相对于 ViewPort(可视窗口)
总结:
px:绝对单位,页面按精确像素展示
em:相对单位,基准点为父节点字体的大小,如果自身定义了font-size按自身来计算,整个页面内1em不是一个固定的值
rem:相对单位,可理解为root em, 相对根节点html的字体大小来计算
vh、vw:主要用于页面视口大小布局,在页面布局上更加方便简单
行内与块元素
行内元素
span img input
块元素div footer header section p h1...
JS
延迟加载JS的方式
async 和 defer
页面渲染流程: HTML解析、HTML解析暂停、script下载、script执行
async:在HTML解析过程中,下载script,在解析暂停阶段,执行script。先加载完先执行
defer:在HTML解析过程中,下载script,HTML解析全部完成后,才执行script。是顺次执行的。
JS数据类型
基本类型: string、number、boolean、undefined、null、symbol
引用类型:object
NaN是一个数值类型,但不是一个具体的数字。
JS判断对象类型
如何判断一个对象是否属于某个类typeof
instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。constructor
Object.prototype.toString.call()
null和undefined的区别
null表示为空对象(空对象指针),null会被隐式转换为0。undefined表示未定义,转换为数字为NaN
设计undefined是为了填补null被隐式转换后不容易发现错误的坑。
== 和 ===
==:比较的是值
===:比较的除了是值,还比较类型
JS微任务和宏任务
1.js是单线程的语言。
2.JS代码执行流程:同步执行完成 => 事件循环
3.事件循环(宏任务、微任务)
当同步任务都执行完成,才会执行事件循环的内容
进入事件循环:请求、定时器、事件…
4.事件循环中包含:[宏任务、微任务]
微任务:promise.then
宏任务:setTimeout…
执行宏任务的前提是清空了所有微任务
流程:同步 => 事件循环【宏任务和微任务】 => 微任务 => 宏任务 => 微任务…
闭包
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域
在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁
使用场景:创建私有变量、延长变量生命周期(一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的)
- 回调函数
- 立即执行函数封装的私有方法
- 防抖节流函数
如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响
闭包可能造成内存泄漏
防抖与节流
防抖函数:当用户频繁操作,只取最后一次操作(回城)
1 | function debonce(fnc,wait) { |
节流函数:多次操作只能在规定时间内触发一次(技能冷却)
1 | function debounce(func, wait) { |
深浅拷贝
JS中有两大数据类型:基本数据类型(string、number、null、boolean、undefined、symbol),引用类型(Object)
基本类型数据保存在栈内存中。
引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中
浅拷贝 指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
在JS中,拥有浅拷贝现象的有:Object.assign
[Object.assign() 方法将所有可枚举(Object.propertyIsEnumerable() 返回 true)的自有(Object.hasOwnProperty() 返回 true)属性从一个或多个源对象复制到目标对象,返回修改后的对象。]
Array.prototype.slice()
[slice() 方法返回一个新的数组对象,这一对象是一个由 start 和 end 决定的原数组的浅拷贝(包括 start,不包括 end),其中 start 和 end 代表了数组元素的索引。原始数组不会被改变。]
Array.prototype.concat()
[concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。]
拓展运算符实现的复制
深拷贝 开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:
- _.cloneDeep()
- jQuery.extend()
- JSON.stringify()
- 手写循环递归
1 | _.cloneDeep() |
JSON.stringify() [JSON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串,如果指定了一个 replacer 函数,则可以选择性地替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性。]const obj2=JSON.parse(JSON.stringify(obj1));
但是这种方式存在弊端,会忽略undefined、symbol和函数
浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
call apply bind
call 、apply 、bind 作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向
区别:
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入。改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
1
2
3
4
5
6
7
8
9function fn(...args){
console.log(this,args);
}
let obj = {
myname:"张三"
}
fn.call(obj,1,2); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向windowcall方法的第一个参数也是this的指向,后面传入的是一个参数列表。跟apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
1
2
3
4
5
6
7
8
9function fn(...args){
console.log(this,args);
}
let obj = {
myname:"张三"
}
fn.call(obj,1,2); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向windowbind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)。改变this指向后不会立即执行,而是返回一个永久改变this指向的函数
1
2
3
4
5
6
7
8
9
10function fn(...args){
console.log(this,args);
}
let obj = {
myname:"张三"
}
const bindFn = fn.bind(obj); // this 也会变成传入的obj ,bind不是立即执行需要执行一次
bindFn(1,2) // this指向obj
fn(1,2) // this指向window三者都可以改变函数的this对象指向
三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window
三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入
bind 是返回绑定this之后的函数,apply 、call 则是立即执行
前后端交互,跨域处理
继承
继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码
在子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能
实现继承的方法:
- 原型链继承
- 构造函数继承(借助 call)
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
原型链继承
1 | function Parent() { |
构造函数继承(借助call)
1 | function Parent(){ |
发布订阅模式
作用域链
作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合
作用域决定了代码区块中变量和其他资源的可见性
我们一般将作用域分成:
- 全局作用域
- 函数作用域
- 块级作用域 (ES6引入了let和const关键字,和var关键字不同,在大括号中使用let和const声明的变量存在于块级作用域中。在大括号之外不能访问这些变量)
作用域链
当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域
如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错
原型与原型链
原型
JS没有class类的概念,所以设计了构造函数来实现继承机制(ES6中的class可以看作只是一个语法糖,它的绝大部分的功能,ES5都可以做到,新的class写法只是让原型的写法更加的清晰、更像面向对象编程的语法而已。)
JS通过构造函数来生成实例。但是又出现了一个新的问题,在构造函数中通过this赋值的属性或者方法,是每个实例的实例属性以及实例方法,无法共享公共属性。所以又设计出了一个原型对象,来存储这个构造函数的公共属性以及方法。
JS的每个函数在创建的时候,都会生成一个属性prototype,这个属性指向一个对象,这个对象就是此函数的原型对象。该原型对象中有个属性为constructor,指向该函数。这样原型对象和它的函数之间就产生了联系。
JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾(null)
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非实例对象本身
原型链
原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法
在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会通过它的__proto__隐式属性,找到它的构造函数的原型对象,如果还没有找到就会再在其构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链
原型链的尽头是null。
Vue
MVVM
Vue的设计参考了MVVM模型
M:对应data中的数据
V:(视图view)模版
VM: (视图模型)Vue实例对象
data中所有属性,最后都出现在了VM身上
VM上所有属性,以及Vue原型上所有属性,在Vue模版中都可以直接使用
数据代理
object.defineproperty方法
给对象添加属性:
1 | let person = { |
输出:
通过Object.defineProperty()
方法添加的属性默认不可枚举,也就是不参与遍历Object.keys(person)
该方法可以将对象中所有属性的属性名提取出来,变成一个数组
输出:
添加的属性变为可枚举,需要修改enumerable : true
1 | Object.defineProperty(person, 'age', { |
数据代理
通过一个对象代理对另一个对象属性的操作 读/写
一个简单的数据代理:
1 | let obj1 = {x:100} |
Vue中的数据代理:通过VM对象来代理data函数中属性的操作(读/写)
原理:通过Object.defineProperty()方法将data对象中的属性添加到VM上,为每一个添加到VM上的属性,都指定一个getter和setter,在getter/setter内部去操作(读/写)data中对应的属性。
data属性为什么是函数不是对象
在Vue实例中,data既可以是函数也可以是对象。
组件中定义data属性,只能是一个函数
因为Vue最终是通过vue.extend()构成组件实例,如果使用对象形式定义data属性,修改A组件data属性的值,B组件中的值也会发生变化。
这是因为对象形式中两个组件的data属性采用了同一个内存地址
如果采用函数形式则不会出现这种情况,因为函数返回的对象内存地址并不相同
总结
根实例对象data可以是对象也可以是函数(根实例是单例),不会产生数据污染情况
组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。采用函数的形式,initData时会将其作为工厂函数都会返回全新data对象
slot插槽
- 默认插槽
子组件用标签来确定渲染的位置,标签里面可以放DOM结构,当父组件使用的时候没有往插槽传入内容,标签内DOM结构就会显示在页面
父组件在使用的时候,直接在子组件的标签内写入内容即可
1 | 子组件child.vue |
- 具名插槽
子组件用name属性来表示插槽的名字,不传为默认插槽
父组件中在使用时在默认插槽的基础上加上slot属性,值为子组件插槽name属性值
1 | 子组件child.vue |
- 作用域插槽
子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件v-slot接受的对象上
父组件中在使用时通过v-slot:(简写:#)获取子组件的信息,在内容中使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18子组件child.vue
<template>
<slot name="footer" testProps="子组件的值">
<h3>没传footer插槽</h3>
</slot>
</template>
父组件
<child>
<!-- 把v-slot的值指定为作⽤域上下⽂对象 -->
<template v-slot:default="slotProps">
来⾃⼦组件数据:{{slotProps.testProps}}
</template>
<template #default="slotProps">
来⾃⼦组件数据:{{slotProps.testProps}}
</template>
</child>