常见小综合
一. css
1. css的盒模型
在HTML页面中的所有元素都可以看作成一个盒子
盒子组成:内容content、外边距margin、内边距padding、边框border
盒模型的类型:
标准盒模型:margin + border + padding + content
IE盒模型: margin + content(border + padding)
控制盒模型的模式: box-sizing:content-box(默认值,标准盒模型)、border-box(IE盒模型)
2. css选择器的优先级
css的特性:继承性、层叠性、优先级
优先级:写css样式的时候,会给同一个元素添加多个样式,此时谁的权重高就显示谁的样式
标签、类/伪类/属性、全局、行内、id、!important
!important > 行内 > id > 类/伪类/属性 > 标签 > 全局
3. 隐藏元素的方法有哪些
display: none
元素在页面上消失,不占据空间
opacity: 0
设置元素透明度为0,元素不可见,占据空间位置
visibility:hidden
让元素消失,占据空间位置,一种不可见的状态
position:absolute
clip-path
4. px 和 rem 的区别
px 是像素,显示器上给我们呈现画面的像素,每个像素的大小都一样,绝对单位长度
rem, 相对单位,相对于 html 根节点的font-size的值,直接给html节点的 font-size:62.5%
1 rem = 10px (16px*62.5%=10px)
5. 重绘重排有什么区别
重排(回流):布局引擎会根据所有的样式计算出盒模型在页面位置上的大小
重绘:计算好盒模型位置、大小和其他的一些属性之后,浏览器就会根据每个盒模型的特性进行绘制
浏览器渲染机制:
对DOM的大小、位置进行修改后,浏览器需要重新计算元素的这些几何属性,就叫重排
对DOM的样式进行修改,比如color和background-color,浏览器不需要重新计算任何属性的时候,直接绘制了该元素的新样式,那么这里就发生了重绘
重排一定会触发重绘(因为位置变了,必然要重新画),但重绘不一定会触发重排(改个颜色,位置没动)。
6. 让一个元素水平垂直居中的方式
- 定位 + margin
- 定位 + translate
- flex 布局
- gird 布局
- table 布局
7. css哪些属性可以继承?哪些不可以继承
css的三大特性:继承、层叠、优先级
子元素可以继承父类元素样式
- 字体的一些属性:font
- 文本的一些属性:line-height
- 元素可见性:visibility:hidden
- 表格布局属性:border-spacing
- 列表属性:list-style
- 页面样式属性:page
- 声音的样式属性
8. 有没有用过预处理器
预处理语言增加了变量、函数、混入等强大的功能
SASS LESS
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
.box {
width: 100px;
height: 100px;
background-color: red;
}
p {
color: blue;
}
@global: #000;
.box {
color: @global;
}
</style>
<body>
<div class="box">
<span></span>
</div>
<p></p>
</body>
</html>二. JavaScript
1. js由哪三部分组成
- ECMAscript:js 的核心内容,描述了语言的基础语法,比如var,for,数据类型(数组、字符串)
- 文档对象模型 (DOM):DOM把整个HTML页面规划为元素构成的文档
- 浏览器对象模型 (BOM):对浏览器窗口进行访问和操作
2. js 有哪些内置对象
String Boolean Number Array Object Function Math Date RegExp...
Math
abs() 绝对值 sqrt() 平方 max() min()
Date
new Date() getYear()
Array
Sting
contact() length slice() split()
3. 操作数组的方法有哪些
push() pop() sort() splice() unshift() reverse() contact() join()
map() filter() every() some() reduce() isArray() findIndex()
哪些方法会改变数组
push() pop() unshift() shift() sort() reverse() splice()
1. 改变原数组(会修改调用它的数组)——口诀:“增删改排反”
push():在末尾添加一个或多个元素。javascript
const arr = [1, 2]; arr.push(3, 4); // 结果:arr 变成 [1, 2, 3, 4]pop():删除末尾最后一个元素,并返回它。javascript
const last = arr.pop(); // last = 4,arr 变成 [1, 2, 3]unshift():在开头添加一个或多个元素。javascript
arr.unshift(0); // arr 变成 [0, 1, 2, 3]shift():删除开头第一个元素,并返回它。javascript
const first = arr.shift(); // first = 0,arr 变成 [1, 2, 3]splice():万能增删改(删除/替换/插入)。参数:(起始索引, 删除个数, 要插入的元素...)javascript
arr.splice(1, 1, 100); // 从索引1开始,删除1个,然后插入100 -> arr 变成 [1, 100, 3]sort():排序(默认按字符串排序,数字排序要传比较函数)。javascript
arr.sort((a, b) => a - b); // 升序排序reverse():颠倒顺序。javascript
arr.reverse(); // arr 变成 [3, 100, 1]
2. 不改变原数组(返回新数组或某个值)——口诀:“拼切连,找遍查”
concat():拼接多个数组。javascript
const newArr = arr.concat([4, 5]); // newArr = [3, 100, 1, 4, 5],arr不变slice():切片提取,参数(开始索引, 结束索引)(不包含结束)。javascript
const sliced = arr.slice(1, 3); // 提取索引1到2的元素 -> [100, 1]join():连接成字符串。javascript
const str = arr.join('-'); // str = "3-100-1"indexOf()/lastIndexOf():找某个元素的索引。includes():查看是否包含某个元素(返回布尔值)。
3. 遍历和归并(ES6+高频使用)——口诀:“过映还,某滤归”
这类方法都接收一个函数作为参数,对每个元素执行。
forEach():过一遍,单纯做点事(比如打印),没有返回值。javascript
arr.forEach(item => console.log(item * 2)); // 打印 6, 200, 2map():映射,对每个元素处理,返回等长的新数组。javascript
const doubled = arr.map(item => item * 2); // [6, 200, 2]filter():过滤,返回符合条件的元素组成的新数组(可能会变短)。javascript
const filtered = arr.filter(item => item > 5); // [100] (3和1被筛掉)some():看是否有某一个元素满足条件(返回布尔值)。javascript
const hasHundred = arr.some(item => item === 100); // trueevery():看是否所有元素都满足条件(返回布尔值)。javascript
const allPositive = arr.every(item => item > 0); // truereduce():归纳汇总,将数组“累加”成单个值(可以求和、求积、甚至拼对象)。javascript
const sum = arr.reduce((total, current) => total + current, 0); // sum = 104
4. js 对数据类检测方式有哪些
typeof()
instanceof()
constructor
object.prototype.toString.call()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script>
// typeof() 对于基本数据类型没问题,遇到引用数据类型就不管用
console.log(typeof 1); // number
console.log(typeof '1'); // string
console.log(typeof [1, 2, 3]); // object
// 这里 typeof 对于数组返回的是 object,无法区分数组和对象
// instanceof() 只能判断引用数据类型 不能判断基本数据类型
console.log([1, 2, 3] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(1 instanceof Number); // false
// 这里 instanceof 对于基本数据类型返回的是 false,无法区分基本数据类型
// constructor 几乎可以判断基本数据类型和引用数据类型,但是有些特殊情况无法判断
console.log((1).constructor === Number); // true
console.log('1'.constructor === String); // true
console.log([1, 2, 3].constructor === Array); // true
// object.prototype.toString.call() 可以判断基本数据类型和引用数据类型
const opt = Object.prototype.toString;
console.log(opt.call(1)); // [object Number]
console.log(opt.call('1')); // [object String]
</script>
<body>
</body>
</html>
- typeof() 对于基本数据类型没问题,遇到引用数据类型就不管用
- instanceof() 只能判断引用数据类型 不能判断基本数据类型
- constructor 几乎可以判断基本数据类型和引用数据类型,但是有些特殊情况无法判断( 因为可以随便修改函数指定 )
- object.prototype.toString.call() 可以判断基本数据类型和引用数据类型
5. 说一下什么是闭包 闭包有什么特点
闭包:函数嵌套函数,内部函数被外部函数返回并保存下来,就会产生闭包
特点:可以重复利用变量,并且这个变量不会污染全局的一种机制;这个变量是一直保存在内存中,不会被垃圾回收机制回收
缺点:闭包较多的时候,会消耗内存,导致页面的性能下降,在 IE 浏览器中会造成内存泄漏
使用场景:防抖、节流、函数嵌套函数避免全局污染
6. 前端的内存泄露怎么理解
js里已经分配内存地址的对象,但是由于长时间没有释放或者没办法清除,造成长期占用内存的现象,会让内存资源
7. 事件委托
原理利用了事件冒泡的机制来实现,也就是说把子元素的事件绑定到了父元素的身上
如果子元素阻止了事件冒泡,那么委托也将不成立了
阻止事件冒泡: event.stopPropagation()
addEventListener('click', 函数名, true/false ) 默认是false(事件冒泡),true(事件捕获)
好处:提高性能,减少事件的绑定,也减少内存的占用
常见的是我在一个小卡片上有一个删除按钮,小卡片和删除按钮分别有两个点击事件,在点击按钮的过程中,也会触发点击卡片的事件,为了不让事件冒泡,加上阻止事件冒泡的代码,点击删除按钮就是只是删除
8. 基本数据类型和引用数据类型的区别
基本数据类型:String Number Boolean Undefined null
基本数据类型保存在栈当中,保存的就是一个具体的值
引用数据类型(复杂数据类型)Object Array Function
引用数据类型是保存在堆 内存当中 ,声明一个引用类型的变量,它保存的引用数据类型的地址
假如声明两个引用类型同时指向一个地址的时候,修改其中一个,那么另外一个就会改变
9. 说一下原型链
原型就是一个普通的对象,它是为构造函数的实例共享属性和方法;所有实例中引用的原型都是同一个对象
使用prototype方法,可以把方法挂在原型上,内存只保存一份
proto 可以理解为指针,实例对象中的属性,指向了构造函数的原型(prototype)
例如 小写的 =student.__proto__ = Student.prototype
每个对象都有 proto 属性,但只有函数对象才有 prototype 属性。proto 指向创建该对象的构造函数的 prototype。
当试图访问一个对象的某个属性时,JavaScript 会首先在该对象自身的属性中查找。如果没有找到,它就会沿着原型链往上查找,直到找到为止。如果整个原型链上都没有找到这个属性,那么返回 undefined。
10. new操作符具体做了什么
- 先创建一个空对象
- 把空对象和构造函数通过原型链进行链接
- 把构造函数的this绑定到新的空对象里面
- 根据构建函数返回的类型判断,如果是值的类型,则返回对象,如果是引用类型,就要返回这个引用类型
<script>
function newFun(Fun, ...args) {
// 1. 先创建一个空对象
let newObj = {};
// 2. 把空对象和构造函数通过原型链进行链接
newObj.__proto__ = Fun.prototype;
// 3. 把构造函数的this绑定到新的空对象里面
const result = Fun.apply(newObj, args);
// 4. 根据构建函数返回的类型判断,如果是值的类型,则返回对象,如果是引用类型,就要返回这个引用类型
return result instanceof Object ? result : newObj;
}
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log(`My name is ${this.name}, I am ${this.age} years old.`);
};
const p1 = newFun(Person, 'Alice', 30);
p1.say();
console.log(p1);
</script>11. js如何实现继承
- 原型链继承
- 借用构造函数继承
- 组合式继承
- Es6和class继承
12. js的设计原理是什么
js引擎 运行上下文 调用栈 事件循环 回调
13. js中 this 的指向问题
全局对象中指向的是window
全局作用域或者普通函数中的 this 执行全局 window
this 永远指向最后调用它的那个对象 (在不是箭头函数的情况下)
new 关键词改变了 this 的指向
apply、call、bind 可以改变 this 的指向,不是箭头函数
箭头函数的 this,它的指向在定义的时候就已经确定,箭头函数它没有 this ,
有就是外层函数的 this,没有就是window
匿名函数中的 this 永远指向window,匿名函数的执行环境具有全局性,因此 this 指向 window
| 调用方式 | this 指向 | 示例 |
|---|---|---|
| 默认绑定(独立函数调用) | window(严格模式下为 undefined) | fn() → this === window |
| 隐式绑定(作为对象方法调用) | 调用该方法的对象 | obj.fn() → this === obj |
显式绑定(call / apply / bind) | 传入的第一个参数 | fn.call(obj) → this === obj |
构造函数调用(new) | 新创建的实例对象 | new Fn() → this === 实例 |
| 箭头函数 | 定义时外层作用域的 this(无法改变) | 看外层,call/bind 无效 |
| DOM 事件处理 | 绑定事件的 DOM 元素 | btn.onclick = fn → this === btn |
定时器(setTimeout / setInterval) | window(箭头函数除外) | setTimeout(fn, 1000) → this === window |
14. script标签里的async和defer有什么区别
当没有async和defer这两个属性脚本的时候,
浏览器会立即加载并执行指定脚本
有 async
加载和渲染后面元素的过程将和script的加载和执行并行进行(异步)
有defer
加载和渲染后面元素的过程将和script的加载并行进行(异步),但是它的执行事件要等所有元素解析完成之后才执行
属性 加载 执行 顺序 使用场景 无 同步阻塞 加载完立即执行 按顺序 必须立即执行的脚本 async异步(不阻塞) 加载完立即执行 不保证顺序 独立脚本(统计、广告) defer异步(不阻塞) HTML解析完执行 保证顺序 操作DOM、有依赖的脚本
15. setTimeout最小执行时间是多少?
setTimeout最小执行时间是 4ms
setInterval最小执行时间是 10ms
18. call,apply,bind三者有什么区别
都是改变 this 指向和函数的调用,call 和 apply 的功能相似,只是传参形式不同
call 传的是一个参数列表
apply 传递是一个数组
bind 传参之后不会立刻执行,会返回一个改变 this 指向的函数,这个函数还是可以传参的,bind()()
call 方法的性能要比 apply 要更好,所以 call 用的更多一点
“
call、apply和bind都是用来改变函数内部this指向的。相同点:它们三个的第一个参数,都是要绑定的
this对象。不同点:
- 传参方式不同:
call是一个一个传(fn.call(obj, a, b)),apply是通过数组传(fn.apply(obj, [a, b]))。- 执行时机不同:
call和apply是立即执行函数;而bind是返回一个新函数,不会立即执行,需要手动调用。- 性能上:
call在传参少的时候比apply性能略好,因为不用解析数组,所以日常开发中call用得多一些。
19. 用递归的时候有没有遇到什么问题
如果一个函数内可以调用函数本身,那么这个函数就是递归函数
函数内部调用自己
(特别注意:写递归必须要有退出条件 return )
20. 如何实现一个深拷贝
深拷贝就是完全拷贝一个新的对象,会在堆内存中开辟一个新的空间,拷贝的对象被修改后,原对象不受影响
主要针对的是引用数据类型
1. 扩展运算符
1. JSON.parse(JSON.stringify())
1. 利用递归函数实现
浅拷贝:拷贝后的对象和原对象,里面的引用数据类型(对象、数组)指向同一个内存地址,所以改了其中一个,另一个也跟着变
深拷贝:完全新的一片内存空间,完全新的数据
function exten(origin,deep) {
let obj = {}
obj = origin instanceof Array ? [] : {};
for(let key in origin) {
let value = origin[key]
obj[key] = (!!deep && typeof value ==='object'&& value!==null)
? exten(value,deep)
:value
}
return obj
}