HTML,CSS
1.flex布局
基本概念
容器与项目
什么叫容器 采用flex布局的元素被称作容器。
什么叫项目 在flex布局中的子元素被称作项目。
即父级元素采用flex布局,则父级元素为容器,全部子元素自动成为项目
容器的一些属性
有六个常用属性设置在容器上,分别为:
flex-direction,flex-wrap,flew-flow,justify-content,align-items,align-content
flex-direction 属性
.wrap{
flex-direction:row | row-reverse | column | column-reverse;
}
包含四个属性值:
row: 默认值,表示沿水平方向,由左到右。
row-reverse :表示沿水平方向,由右到左
column:表示垂直方向,由上到下
column-reverse:表示垂直方向,由下到上
flex-flow属性
lex-flow属性是flex-deriction和flex-wrap属性的简写,默认值为[row nowrap];,
第一个属性值为flex-direction的属性值
第二个属性值为flex-wrap的属性值
项目的一些属性
flex-grow 属性
flex-grow属性用来控制当前项目是否放大显示。默认值为0,表示即使容器有剩余空间也不放大显示。如果设置为1,则平均分摊后放大显示
.green-item{
flex-grow:2;
}
flex-shrink 属性
flex-shrink属性表示元素的缩小比例。默认值为1,如果空间不够用时所有的项目同比缩小。如果一个项目的该属性设置为0,则空间不足时该项目也不缩小。
flex-basis属性
flex-basis属性表示表示项目占据主轴空间的值。默认为auto,表示项目当前默认的大小。如果设置为一个固定的值,则该项目在容器中占据固定的大小。
flex属性
flex属性是 flex-grow属性、flex-shrink属性、flex-basis属性的简写。默认值为:0 1 auto;
flex:1; 即就是代表均匀分配元素
flex: 1; === flex: 1 1 0;
第一个参数表示: flex-grow 定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大
第二个参数表示: flex-shrink 定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
第三个参数表示: flex-basis给上面两个属性分配多余空间之前, 计算项目是否有多余空间, 默认值为 auto, 即项目本身的大小;设置为auto的时候,会根据盒子内容的多少自动撑开盒子,它里面的每个盒子的宽度是不一样的
.item{
flex:(0 1 auto) | auto(1 1 auto) | none (0 0 auto)
}
align-self 属性
align-self属性表示当前项目可以和其他项目拥有不一样的对齐方式。它有六个可能的值。默认值为auto
auto:和父元素align-self的值一致
flex-start:顶端对齐
flex-end:底部对齐
center:竖直方向上居中对齐
baseline:item第一行文字的底部对齐
stretch:当item未设置高度时,item将和容器等高对齐
JS
1.原型与原型链
参考https://juejin.cn/post/6844903749345886216
JavaScript中一切引用类型都是对象,对象就是属性的集合。
Array类型、Function类型、Object类型、Date类型、RegExp类型等都是引用类型
也就是说 数组是对象、函数是对象、正则是对象、对象还是对象。
原型和原型链是什么
上面我们说到对象就是属性(property)的集合,有人可能要问不是还有方法吗?其实方法也是一种属性,因为它也是键值对的表现形式,具体见下图。
可以看到
obj上确实多了一个sayHello的属性,值为一个函数,但是问题来了,obj上面并没有hasOwnProperty这个方法,为什么我们可以调用呢?这就引出了 原型。
每一个对象从被创建开始就和另一个对象关联,从另一个对象上继承其属性,这个另一个对象就是 原型。
当访问一个对象的属性时,先在对象的本身找,找不到就去对象的原型上找,如果还是找不到,就去对象的原型(原型也是对象,也有它自己的原型)的原型上找,如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回undefined。
这条由对象及其原型组成的链就叫做原型链。
现在我们已经初步理解了原型和原型链,到现在大家明白为什么数组都可以使用push、slice等方法,函数可以使用call、bind等方法了吧,因为在它们的原型链上找到了对应的方法。
总结:
- 原型存在的意义就是组成原型链:引用类型皆对象,每个对象都有原型,原型也是对象,也有它自己的原型,一层一层,组成原型链。
- 原型链存在的意义就是继承:访问对象属性时,在对象本身找不到,就在原型链上一层一层找。说白了就是一个对象可以访问其他对象的属性。
Array Object都是构造函数 console.log('数组原型',Array.prototype)
console.log('对象原型',Object.prototype)
// 惯例,构造函数应以大写字母开头
function Person (name) {
this.name = name
this.say = function () {
console.log('Hi'+name)
}
}
// 对象的创建方式主要有两种,一种是new操作符后跟函数调用,另一种是字面量表示法
// new操作符 创建对象
let newPerson = new Person('小花')
console.log('newPerson',newPerson) //{name:'小花',f() {}}
console.log(newPerson.name) //小花
console.log(newPerson.say()) //Hi小花
// 总结一下:构造函数用来创建对象,同一构造函数创建的对象,其原型相同
let newPerson2 = new Person('小名')
console.log(newPerson.prototype === newPerson2.prototype) //true
//Array Object都是构造函数
// 对象有__proto__属性,函数有__proto__属性,数组也有__proto__属性,只要是引用类型,就有__proto__属性,指向其原型。
// 只有函数有prototype属性,只有函数有prototype属性,只有函数有prototype属性,指向new操作符加调用该函数创建的对象实例的原型对象。
console.log(Person.prototype === newPerson.__proto__) // true
2.js本地存储
cooKie,session,localStorage
共同点:都是保存在浏览器端、且同源的.
区别:
1、cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
2、存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
3、数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
4、作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的
3.常用的数组方法
01.Array.push()
向数组的末尾添加一个或多个元素,并返回新的数组长度。原数组改变
02.Array.pop()
删除并返回数组的最后一个元素,若该数组为空,则返回undefined。原数组改变
03.Array.unshift()
向数组的开头添加一个或多个元素,并返回新的数组长度。原数组改变
04.Array.shift()
删除数组的第一项,并返回第一个元素的值。若该数组为空,则返回undefined。原数组改变。
05.Array.join()
将数组的每一项用指定字符连接形成一个字符串。默认连接字符为 “,” 逗号
05.Array.slice()
参数:
array.slice(n, m),从索引n开始查找到m处(不包含m)
array.slice(n) 第二个参数省略,则一直查找到末尾
array.slice(0)原样输出内容,可以实现数组克隆
array.slice(-n,-m) slice支持负参数,从最后一项开始算起,-1为最后一项,-2为倒数第二项
返回值:返回一个新数组
是否改变原数组:不改变
06.Array.splice()
Array.splice(index,howmany,arr1,arr2…) ,用于添加或删除数组中的元素。从index位置开始删除howmany个元素,并将arr1、arr2…数据从index位置依次插入。howmany为0时,则不删除元素。
原数组改变。
4.forEach如何终止循环
forEach专门用来循环数组,可以直接取到元素,同时也可以取到index值
存在局限性,不能continue跳过或者break终止循环,没有返回值,不能return
终止foreach循环 :运用抛出异常(try catch)可以终止foreach循环
错误用法一:
var array = ["第一","第二","第三","第四"];
// 直接就报错了
array.forEach(function(item,index){
if (item == "第三") {
break;
}
alert(item);
});
报错如下:
错误用法2:使用return fasle (只是终止本次循环)
var array = ["第一","第二","第三","第四"];
// 会遍历数组所有元素,只是执行跳过"第三",return false下面的代码不再执行而已
array.forEach(function(item,index){
if (item == "第三") {
return false;
}
console.log(item);// "第一" "第二" "第四"
});
console.log("以下代码")// 以下代码
正确用法:运用抛出异常(try catch)
try {
var array = ["第一","第二","第三","第四"];
// 执行到第3次,结束循环
array.forEach(function(item,index){
if (item == "第三") {
throw new Error("第三");
}
console.log(item);// 第一 第二
});
} catch(e) {
if(e.message!="第三") throw e;
};
// 下面的代码不影响继续执行
console.log("下方代码");//下方代码
5.数组去重的方法
6.call,apply,bind的区别
参考:call,apply,bind区别,手写call,bind.apply call.bind.apply都是改变 this指向的方法
1.call
把找到的call方法执行
当call方法执行的时候,内部处理了一些事情
1.首先把要操作的函数中的this关键字变为call方法第一个传递的实参
2.把call方法第二个及之后的实参获取到
3.把要操作的函数执行,并且把第二个以后传递进来的实参传递给函数
fn.call(thisArg, arg1, arg2, ...)
call细节
1.非严格模式下
如果不传参数,或者第一个参数是`null`或`nudefined`,`this`都指向`window`
let fn = function(a,b){
console.log(this,a,b);
}
let obj = {name:"obj"};
fn.call(obj,1,2); // this:obj a:1 b:2
fn.call(1,2); // this:1 a:2 b:undefined
fn.call(); // this:window a:undefined b:undefined
fn.call(null); // this=window a=undefined b=undefined
fn.call(undefined); // this=window a=undefined b=undefined
2.严格模式下
第一个参数是谁,this就指向谁,包括null和undefined,如果不传参数this就是undefined
"use strict"
let fn = function(a,b){
console.log(this,a,b);
}
let obj = {name:"obj"};
fn.call(obj,1,2); // this:obj a:1 b:2
fn.call(1,2); // this:1 a:2 b=undefined
fn.call(); // this:undefined a:undefined b:undefined
fn.call(null); // this:null a:undefined b:undefined
fn.call(undefined); // this:undefined a:undefined b:undefined
2.apply
apply把需要传递给fn的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给fn一个个的传递
fn.call(obj, 1, 2);
fn.apply(obj, [1, 2]);
3.bind
call/apply 改变了函数的 this 上下文后 马上 执行该函数
bind 则是返回改变了上下文后的函数, 不执行该函数 。
4.手写实现call
const name = '李四'
const person = {
getName:function() {
console.log(this)
return this.name
}
}
const man = {
name:'张三'
}
Function.prototype.myCall = function () {
1.getName函数要执行
const res = this()
return res;
}
console.log(person.getName.myCall(man)) // 这时候this指向window,打印出李四
Function.prototype.myCall = function (context) {
1.getName函数要执行
1-2 这个时候getName函数的this指向的是window
1-3 传过来的形参是我想指向的man
this是一个函数, context就是我们想要改变指向的man对像
给context挂在一个函数 把this赋值给他
context.fn = this;
const res = context.fn()
return res;
}
7.数据类型
JavaScript(以下简称js)的数据类型分为两种:原始类型(即基本数据类型)和对象类型(即引用数据类型)
js常用的基本数据类型包括undefined - - (未定义)、null- - (空的)、number - - (数字)、boolean- - (布尔值)、string- - (字符串)、Symbol - - (符号);
js的引用数据类型也就是对象类型Object- - (对象),比如:array - - (数组)、function - - (函数)、date - - (时间)等;
8.判断数据类型的方式
1.typeof
- 识别所有值类型;
- 识别函数类型;
- 识别引用类型,但是无法区分对象,数组以及
null - Infinity 和 NaN 会被识别为 number,尽管 NaN 是 Not-A-Number 的缩写,意思是"不是一个数字"
let a
const b = null
const c = 100
const d = 'warbler'
const e = true
const f = Symbol('f')
const foo = () => {}
const arr = []
const obj = {}
console.log(typeof a) //=> undefined
console.log(typeof b) //=> object
console.log(typeof c) //=> number
console.log(typeof d) //=> string
console.log(typeof e) //=> boolean
console.log(typeof f) //=> symbol
console.log(typeof foo) //=> function
console.log(typeof arr) //=> object
console.log(typeof obj) //=> object
console.log(typeof Infinity) //=> number
console.log(typeof NaN) //=> number
instanceof方法
无法区分是Array还是Object
[] instenceof Object true
{} instenceof Object true
用来检测引用数据类型,值类型都会返回 false
左操作数是待检测其类的对象,右操作数是对象的类。如果左侧的对象是右侧的实例,则返回 true,否则返回false。
检测所有 new 操作符创建的对象都返回 true。
检测 null 和 undefined 会返回 false
const foo = () => { }
const arr = []
const obj = {}
const data = new Date()
const number = new Number(3)
console.log(foo instanceof Function) //=> true
console.log(arr instanceof Array) //=> true
console.log(obj instanceof Object) //=> true
console.log(data instanceof Object) //=> true
console.log(number instanceof Object) //=> true
console.log(null instanceof Object) //=> false
console.log(undefined instanceof Object) //=> false
Object.prototype.toString.call
对于 Object.prototype.toString() 方法,会返回一个形如 [object XXX] 的字符串。 使用Object.prototype.toString.call 的方式来判断一个变量的类型是最准确的方法。 Object.prototype.toString.call 换成 Object.prototype.toString.apply 也可以
let a
const b = null
const c = 100
const d = 'warbler'
const e = true
const f = Symbol('f')
const reg = /^[a-zA-Z]{5,20}$/
const foo = () => { }
const arr = []
const obj = {}
const date = new Date();
const error = new Error();
const args = (function() {
return arguments;
})()
console.log(Object.prototype.toString.call(a)) //=> [object Undefined]
console.log(Object.prototype.toString.call(b)) //=> [object Null]
console.log(Object.prototype.toString.call(c)) //=> [object Number]
console.log(Object.prototype.toString.call(d)) //=> [object String]
console.log(Object.prototype.toString.call(e)) //=> [object Boolean]
console.log(Object.prototype.toString.call(f)) //=> [object Symbol]
console.log(Object.prototype.toString.call(reg)) //=> [object RegExp]
console.log(Object.prototype.toString.call(foo)) //=> [object Function]
console.log(Object.prototype.toString.call(arr)) //=> [object Array]
console.log(Object.prototype.toString.call(obj)) //=> [object Object]
console.log(Object.prototype.toString.call(date)) //=> [object Date]
console.log(Object.prototype.toString.call(error)) //=> [object Error]
console.log(Object.prototype.toString.call(args)) //=> [object Arguments]
封装成简单的函数使用
const getPrototype = (item) => Object.prototype.toString.call(item).split(' ')[1].replace(']', '');
console.log(getPrototype('abc')) //=> String
9.递归算法
递归的概念
就是函数自己调用自己本身,或者在自己函数调用的下级函数中调用自己。
递归经典案例
案例1:求和
求1-100的和
function sum (n) {
if (n == 1) return 1
return sum(n - 1) + n
}
案例2. 斐波拉契数列
1,1,2,3,5,8,13,21,34,55,89...求第 n 项
function fib(n) {
if (n == 1 || n == 2) return 1
return fib(n - 1) + fib(n - 2)
}
案例3. 爬楼梯
JS 递归 假如楼梯有 n 个台阶,每次可以走 1 个或 2 个台阶,请问走完这 n 个台阶有几种走法
function climp (n) {
if (n == 1) return 1
if (n == 2) return 2
return climp(n - 1) + climp(n - 2)
}
10.深拷贝,浅拷贝
浅拷贝:只是拷贝一层,更深层次对象级别的只拷贝了地址。
深拷贝:深拷贝就会拷贝多层,即使是嵌套了对象,也会都拷贝出来,内容和原对象一样,更改原对象,拷贝对象不会发生变化
1.直接赋值
// 直接赋值 浅拷贝
const obj1 = {
a:1,
b:2,
c:3
}
const obj2 = obj1;
obj2.a = 4;
// 直接赋值属于浅拷贝,修改obj2时 影响到obj1元数据
console.log('obj1',obj1) //{a:4,b:1,c:2}
2. ...展开运算符
//...展开运算符 实现深拷贝的时候 只针对基础数据类型
const obj3 = {
a:1,
b:2,
c:3
}
const obj4 = {...obj3}
obj4.a = 5;
console.log('obj3',obj3) // {a:1,b:2,c:3} //不影响源数据
//...展开运算符 实现浅拷贝,基础数据类型实现了深拷贝没影响源数据,引用数据类型还是浅拷贝
const obj5 = {
a:1,
b:2,
c:3,
d:{
e:6
}
}
const obj6 = {...obj5}
obj6.a = 5;
obj6.d.e= 7;
console.log('obj5',obj5) // {a:1,b:2,c:3,d:{e:7}} 基础类型实现了深拷贝 引用类型还是浅拷贝
3.object.assign()
// object.assign(target,...sources)
//target 要拷贝给谁 sources 要拷贝的对象
//object.assign 实现深拷贝 只针对基本数据类型,针对更深层次的数据 无法实现深拷贝
const obj7 = {
a:1,
b:2,
c:3
}
const newobj = {}
Object.assign(newobj,obj7)
console.log('newobj',newobj) // {a:1,b:2,c:3}
newobj.a = 4;
console.log('obj7',obj7) // {a:1,b:2,c:3} 此时实现了深拷贝
//object.assign 实现浅拷贝
const obj8 = {
a:1,
b:2,
c:3,
d:{
e:4
}
}
const newobj2 = {}
Object.assign(newobj2,obj8)
console.log('newobj2',newobj2) // {a:1,b:2,c:3,d:{e:4}}
newobj2.a = 5;
newobj2.d.e = 6;
console.log('obj8',obj8) // {a:1,b:2,c:3,d:{e:6}} a未改变 e改变,因为obj8.d是引用数据类型
4.JSON.stringfy JSON,parse() 实现深拷贝
const obj9 = {
a:1,
b:2,
c:{
d:4
}
}
const obj10 = JSON.parse(JSON.stringify(obj9))
console.log('obj10',obj10) // {a:1,b:2,c:{d:4}}
obj10.c.d = 5;
console.log('onj9',obj9) // {a:1,b:2,c:{d:4}} //实现了深拷贝 没有影响到源数据
//但是JSON.stringfy有个缺点,当要拷贝的对象含有function regExp 时间对象时 无法实现深拷贝
//对象中有时间类型的时候,序列化之后会变成字符串类型。
// 2. 对象中有undefined和Function类型数据的时候,序列化之后会直接丢失。
// 3. 对象中有NaN、Infinity和-Infinity的时候,序列化之后会显示 null。
const obj11 = {
a:1,
b:2,
c:function () {
console.log()
}
}
const obj12 = JSON.parse(JSON.stringify(obj11))
obj12.a = 'a'
console.log('obj11',obj11)
console.log('obj12',obj12) // {a:'a',b:2} //c函数丢失
//那么怎么避免呢?下面手写实现深拷贝
//手写实现深拷贝
const copyObj = (obj = {}) => {
//变量先置空
let newobj = null;
//判断是否需要继续进行递归
if (typeof (obj) == 'object' && obj !== null) {
newobj = obj instanceof Array ? [] : {};
//进行下一层递归克隆
for (var i in obj) {
newobj[i] = copyObj(obj[i])
}
//如果不是对象直接赋值
} else newobj = obj;
return newobj;
}
const obj13 = copyObj(obj11)
obj13.a = 3
console.log('obj11',obj11) //{a:1,b:2,c:f()}
console.log('obj13',obj13) //{a:3,b:2,c:f()}
11.this指向
1.是一个指针型变量,他动态的指向当前函数的执行环境
2.在不同的场景中调用同一个函数,this的指向也可能会发生变化,但是它永远指向其所在函数的真实调用者;如果没有调用者,就指向全局对象window
12.跨域解决
13.性能优化
1.减少http请求
2.使用服务端渲染
3.静态资源使用CDN
4.使用字体图标iconfont代替图片
5.图片延迟加载,降低图片质量
6.按需加载
7.减少重绘 重排
14.怎么遍历数组、对象,for in遍历对象时会不会访问原型
遍历数组:
Array.some(),Array.map(),Array.forEach(),Array.every(),Array.fllter(),Array.find(),Array.findIndex(), for of只能遍历数组,不能遍历对象,
for in遍历数组得到数组的下标,遍历对象得到对象的key
for - in特点
for-in循环会遍历原型链上可枚举的所有属性,如果不想遍历原型上的属 性,如果想要只遍历实例对象的属性,可以使用hasOwnProperty 方法过滤一下。
let obj = {
a: 1,
b: 2,
c: 3,
__proto__: {
lastName: 'xie'
}
};
Object.prototype.addMsg = 'LM';
for (let i in obj) (
console.log(i),//a b c lastName addMsg
console.log(obj[i])//1 2 3 xie LM
);
//现在你只想遍历obj里面的属性,原型上面的属性不需要
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
console.log(obj[i]);// 1 2 3
}
}
15.事件循环机制
同步任务和异步任务
其实我们每个任务都是在做两件事情,就是发起调用和得到结果。
而同步任务和异步任务最主要的差别就是,同步任务发起调用后,很快就可以得到结果,而异步任务是无法立即得到结果,比如请求接口,每个接口都会有一定的响应时间,根据网速、服务器等等因素决定,再比如定时器,它需要固定时间后才会返回结果。
因此,对于同步任务和异步任务的执行机制也不同。
同步任务的执行,其实就是跟前面那个案例一样,按照代码顺序和调用顺序,支持进入调用栈中并执行,执行结束后就移除调用栈。
而异步任务的执行,首先它依旧会进入调用栈中,然后发起调用,然后解释器会将其响应回调任务放入一个任务队列,紧接着调用栈会将这个任务移除。当主线程清空后,即所有同步任务结束后,解释器会读取任务队列,并依次将已完成的异步任务加入调用栈中并执行
16.输入url到页面展示的过程
# 史上最详细的经典面试题 从输入URL到看到页面发生了什么
简单来说,共有以下几个规程
- DNS解析
- 发起TCP连接
- 发送HTTP请求
- 服务器处理请求并返回HTTP报文
- 浏览器解析渲染页面
- 连接结束。
17.手写防抖 节流
概念
防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
一个经典的比喻:
想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应
假设电梯有两种运行策略 debounce 和 throttle,超时设定为15秒,不考虑容量限制
电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖。
电梯第一个人进来后,15秒后准时运送一次,这是节流。
防抖 只执行最后一次
因为每次都会重新let timer,所以这个时候我们就要使用闭包(延迟了变量的生命周期)了。
function debounce(fun, time) {
let timer;
return function(){
// 如果之前就存在定时器,就要把之前那个定时器删除
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fun.apply(this, arguments)
}, time)
}
}
function addOne(){
console.log('this',this) // <button id="btn">clickMe</button>,如果不写fun.apply(this,argmuments) 打印出的this是window 这显然不是我们要的
console.log('增加一个')
}
btn.addEventListener('click', debounce(addOne,2000))
节流 只执行一次
function scrollTest(){
console.log('现在我触发了')
}
let btn2 = document.getElementById('btn2')
function throttle(func,time) {
let t1 = 0;
return function () {
let t2 = new Date()
if (t2 - t1 > time) {
func.apply(this,arguments)
}
t1 = t2;
}
}
btn2.addEventListener('click',throttle(scrollTest,1000) //连续触发时,在delay秒内只执行一次
19. js内存泄漏
什么是内存泄漏?
由于疏忽或错误造成程序未能释放已经不再使用的内存
引起内存泄漏的情况
1.声明了一个全局变量,但是又没有用上,那么就有点浪费内存了
function foo(arg) {
bar = 'this is a hidden global variable'
}
2.定时器没清除
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// 处理 node 和 someResource
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);
如果id为 Node 的元素从DOM中移除,该定时器仍会存在,同时,因为回调函数中包含对someResource的引用,定时器外面的someResource也不会被释放
3.闭包
function bindEvent() {
var obj = document.createElement('XXX')
var unused = function () {
console.log(obj, '闭包内引用obj obj不会被释放')
}
obj = null // 解决方法
}
解决内存泄露
我们编译器有一个自动的内存清理。常见的主要是引用记数 和 标记清除。 谷歌浏览器主要是用标记清除,大概流程是给每一个变量添加一个标记,通过内部算法计算引用情况,当不使用的时候就会自动清除。如果遇到定时器的话,我一般会在页面关闭的时候手动清除。如果遇到循环引用,我一般会手动把变量赋值为 null 来清除
垃圾回收机制
Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存
原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存
通常情况下有两种实现方式:
标记清除
引用计数
20. Promise特点,手写promise和promise.all()
Promise有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
Promise优点
1.解决了回调地狱的问题,将异步操作以同步操作的流程表达出来
2.Promise 带来的额外好处是包含了更好的错误处理方式(包含了异常处理)
Promise缺点
1.无法取消Promise,一旦新建它就会立即执行,无法中途取消 2.如果不设置回调函数,Promise内部抛出的错误,不会反应到外部 3.当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
21. 数组扁平化
1.flat()
const arr = [1,[2,3,[6,7]],4,5]
console.log('flat',arr.flat()) //[1,2,3,[6,7],4,5] //不能无限扁平化
console.log('flat',arr.flat(Infinity)) //[1,2,3,6,7,4,5] //无限扁平化
2.使用正则
const arr1 = JSON.stringify(arr).replace(/\[|\]/g,'')
console.log('arr1',JSON.parse('['+arr1+']'))
// reduce() 和concat
function flatten (arr) {
return arr.reduce((pre,current) => {
return pre.concat(Array.isArray(current) ? flatten(current) : current )
},[])
}
console.log('flatten',flatten(arr))
3.函数递归
function flatten(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
console.log(flatten(arr));
22. 闭包的理解,什么是闭包,闭包的应用场景,闭包的缺点
1.闭包概念
函数嵌套函数,内部函数就是闭包。 或者说如果一个函数访问了此函数的父级及父级以上的作用域变量,那么这个函数就是一个闭包
正常情况下,函数执行完,内部变量会销毁 闭包,内部函数没有执行完成,外部函数变量没有销毁。 下面就是一个简单的闭包
function outerFunc () {
let a = 10;
function innereFunc () {
console.log(a)
}
return innerFunc()
}
let fun = outerFunc()
fun() //10
闭包的特点
1.被闭包函数访问的父级及以上的函数的局部变量(如范例中的局部变量 a )会一直存在于内存中,不会被JS的垃圾回收机制回收。
2.闭包函数实现了对其他函数内部变量的访问
闭包的用途
1.访问函数内部的变量
2.让变量始终保持在内存中
闭包的使用场景
模拟面向对象的代码风格
比如模拟两人对话
function person(name) {
function say(content) {
console.log(name + ':' + content)
}
return say
}
a = person('张三')
b = person('李四')
a("在干啥?")
b("没干啥。")
a("出去玩吗?")
b("去哪啊?")
控制台打印结果为:
张三:在干啥?
李四:没干啥。
张三:出去玩吗?
李四:去哪啊
通过闭包实现setTimeout第一个函数传参(默认不支持传参)
function func(param){
alert(param)
}
var f1 = func(1);
setTimeout(f1,3000); //不使用闭包 会立即alert 不会延迟3s
//使用闭包 延迟3s弹出
function func(param){
return function(){
alert(param)
}
}
var f1 = func(1);
setTimeout(f1,3000);
封装私有变量
用闭包定义能访问私有函数和私有变量的公有函数。
var counter = (function () {
var privateCounter = 0; //私有变量
function change(val) {
privateCounter += val;
}
return {
increment: function () {
change(1);
},
decrement: function () {
change(-1);
},
value: function () {
return privateCounter;
}
};
})();
console.log(counter.value());//0
counter.increment();
console.log(counter.value());//1
counter.increment();
console.log(counter.value());//2
模拟块作用域
以此点击4个li,结果都弹出4
解析:onclick绑定的function中没有变量 i,解析引擎会寻找父级作用域,最终找到了全局变量 i,for循环结束时,i 的值已变成了4,所以onclick事件执行时,全都弹出 4
下面使用闭包来解决这个问题:
var elements = document.getElementsByTagName('li');
var length = elements.length;
for(var i = 0; i < length;i ++) {
elements[i].onclick = function (num) {
return function () {
alert(num)
}
}(i)
}
通过匿名闭包,把每次的 i 都保存到一个变量中,实现了预期效果。
闭包的优点:
可以减少全局变量的定义,避免全局变量的污染
能够读取函数内部的变量
在内存中维护一个变量,可以用做缓存
闭包的缺点:
1)造成内存泄露(只在iE中)
闭包会使函数中的变量一直保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
解决方法——使用完变量后,手动将它赋值为null;
2)闭包可能在父函数外部,改变父函数内部变量的值。
3)造成性能损失
由于闭包涉及跨作用域的访问,所以会导致性能损失。
各自独立的闭包
function outerFn(){
var i = 0;
function innerFn(){
i++;
console.log(i);
}
return innerFn;
}
var inner = outerFn(); //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2(); //1 2 3 1 2 3
function fn(){
var a = 3;
return function(){
return ++a;
}
}
alert(fn()()); //4
alert(fn()()); //4
访问全局变量的闭包
var i = 0;
function outerFn(){
function innnerFn(){
i++;
console.log(i);
}
return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2(); //1 2 3 4
23. 箭头函数和普通函数的区别
1.箭头函数比普通函数更加简洁
如果没有参数,就直接写一个空括号即可 如果只有一个参数,可以省去参数的括号 如果函数体的返回值只有一句,可以省略大括号
2.箭头函数没有自己的this
箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。
箭头函数的this指向的是在你书写代码时候的上下文环境对象的this,如果没有上下文环境对象,那么就指向最外层对象window
3.箭头函数继承来的this指向永远不会改变
var id = 'GLOBAL';
var obj = {
id: 'OBJ',
a: function(){
console.log(this.id);
},
b: () => {
console.log(this.id);
}
};
obj.a(); // 'OBJ'
obj.b(); // 'GLOBAL'
对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象
4. call()、apply()、bind()等方法不能改变箭头函数中this的指向
var id = 'Global';
let fun1 = () => {
console.log(this.id)
};
fun1(); // 'Global'
fun1.call({id: 'Obj'}); // 'Global'
fun1.apply({id: 'Obj'}); // 'Global'
fun1.bind({id: 'Obj'})(); // 'Global'
5.箭头函数没有自己的arguments
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。
箭头函数的this指向哪⾥?
箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。
24. 柯里化是什么,有什么用,怎么实现
1.什么是函数柯里化?
在计算机科学中,柯里化(英语:Currying ),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回结果的新函数的技术。
用大白话来说就是只传递给函数一部分参数来调用它,让它返回一个新函数去处理剩下的参数
2.简单的柯里化实现
没有柯里化实现的案例
将其转化为柯里化的案例
自动柯里化
function myCurried(fn) {
return function curry(...args1) {
if (args1.length >= fn.length) {
return fn.call(null, ...args1)
} else {
return function (...args2) {
return curry.apply(null, [...args1, ...args2])
}
}
}
}
function sum(a, b, c, d, e) {
return a + b + c + d + e
}
let resFunc = myCurried(sum)
console.log(resFunc(1,3,4)(1)(23))
//解析:
//1、这里的fn.length获取的是函数传入参数的长度
//2、这里使用递归的思想
柯里化的作用
单一原则:在函数式编程中,往往是让一个函数处理的问题尽可能单一,而不是一个函数处理多个任务。
提高维护性以及降低代码的重复性
柯里化的场景
1、比如我们在求和中,以一定的数字为基数进行累加的时候,就用到了函数柯里化。当然函数柯里化感觉上是把简答的问题复杂化了,其实不然。比如:
// 比如,基础分值是30 + 30;
const fractionFn = (x) => {
const totalFraction = x + x;
return function(num) {
return totalFraction + num;
}
};
const baseFn = fractionFn(30);
const base1Fn = baseFn(1);
const base2Fn = baseFn(2);
console.log(base2Fn) //62
这样来进行累加的话,是不是就简单、清晰明了呢.
我们常用的日志输出,是不是都是具体的日期、时间以及加上具体的原因呢。
其实date, type每次还需要传参。是不是可以进行抽离呢,当然了,函数柯里化就可以完美的解决这个。
const date = new Date()
const logFn = (date) => (type) => (msg) => {
return `${date.getHours()}:${date.getMinutes()} ${type} - ${msg}`
}
const newLogfn = logFn(date)('warning')('变量未声明')
console.log('newLogfn',newLogfn) // newLogfn 10:40 warning - 变量未声明
其他使用场景 表单正则验证
在 react 项目中使用 antd 表单的时候,遇到一些老项目,需要校验密码的强弱、校验输入的规则等,如果每次都是传正则和需要校验的字符串,有点麻烦。
import React from "react";
const accountReg = /^[a-zA-Z0-9_-]{4,16}$/;
const passwordReg = /^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)(?=.*?[!@#$])[a-zA-Z\d!@#$]{10,16}$/;
const FormCom = () => {
const checkReg = (reg, txt) => {
return reg.test(txt)
}
//账号
const checkAccount = (event) => {
checkReg(accountReg, event.target.value);
// 其他逻辑
};
//密码
const checkPassword = (event) => {
checkReg(passwordReg, event.target.value);
// 其他逻辑
};
...
// 省去其他函数校验
render() {
return (
<form>
账号:
<input onChange={checkAccount} type="text" name="account" />
密码:
<input onChange={checkPassword} type="password" name="password" />
</form>
);
}
}
export default FormCom;
我们怎么解决类似的问题呢,我们可以使用柯里化函数来解决类似的问题
const accountReg = /^[a-zA-Z0-9_-]{4,16}$/;
const passwordReg = /^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)(?=.*?[!@#$])[a-zA-Z\d!@#$]{10,16}$/;
const FormCom = () => {
// 柯里化封装
const curryCheck = (reg) => {
return function(txt) {
return reg.test(txt)
}
}
//账号,这样就省去了一个参数的传递
const checkAccount = curryCheck(accountReg);
//密码,这样就省去了一个参数的传递
const checkPassword = curryCheck(passwordReg);
const checkAccountFn = () => {
checkAccount(event.target.value);
// 其他逻辑
}
const passwordFn = (event) => {
checkPassword(event.target.value);
// 其他逻辑
};
...
// 省去其他函数校验
render() {
return (
<form>
账号:
<input onChange={checkAccountFn} type="text" name="account" />
密码:
<input onChange={passwordFn} type="password" name="password" />
</form>
);
}
}
export default FormCom;
25. common.js和esm区别
CommonJS
CommonJs可以动态加载语句,代码发生在运行时
let lists = ["./index.js", "./config.js"]
lists.forEach((url) => require(url)) // 动态导入
if (lists.length) {
require(lists[0]) // 动态导入
}
CommonJs导出值是拷贝,可以修改导出的值,这在代码出错时,不好排查引起变量污染 CommonJs导入的值是拷贝的,所以可以修改拷贝值,但这会引起变量污染,一不小心就重名
// index.js
let num = 0;
module.exports = {
num,
add() {
++ num
}
}
let { num, add } = require("./index.js")
console.log(num) // 0
add()
console.log(num) // 0
num = 10
上面example中,可以看到exports导出的值是值的拷贝,更改完++ num值没有发生变化,并且导入的num的值我们也可以进行修改
es module
混合导入:
import语句必须先是默认导出,后面再是单个导出,顺序一定要正确否则报错。
// index,js
export const name = "蛙人"
export const age = 24
export default {
msg: "蛙人"
}
import msg, { name, age } from './index.js'
console.log(msg) // { msg: "蛙人" }
Es Module是静态的,不可以动态加载语句,只能声明在该文件的最顶部,代码发生在编译时
就是Es Module语句``import只能声明在该文件的最顶部,不能动态加载语句,Es Module`语句运行在代码编译时
if (true) {
import xxx from 'XXX' // 报错
}
Es Module混合导出,单个导出,默认导出,完全互不影响
Es Module导出是引用值之前都存在映射关系,并且值都是可读的,不能修改
export导出的值是值的引用,并且内部有映射关系,这是export关键字的作用。而且导入的值,不能进行修改也就是只读状态
// index.js
export let num = 0;
export function add() {
++ num
}
import { num, add } from "./index.js"
console.log(num) // 0
add()
console.log(num) // 1
num = 10 // 抛出错误
26. 使用new创建对象的过程
27 let const var区别
28 ES5继承和Class继承区别
VUE
1.VUe声明周期,作用
beforeCreate:是 new Vue() 之后触发的第一个钩子,在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问
created:在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发 updated 函数。可以做一些初始数据的获取,在当前阶段无法与 Dom 进行交互,如果非要想,可以通过 vm.$nextTick 来访问 Dom
beforeMount:发生在挂载之前,在这之前 template 模板已导入渲染函数编译。而当前阶段虚拟 Dom 已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发 updated。
mounted:在挂载完成后发生,在当前阶段,真实的 Dom 挂载完毕,数据完成双向绑定,可以访问到 Dom 节点,使用 $refs 属性对 Dom 进行操作。
2.VUE组件通信方式?兄弟组件的通信方式
父子组件通信
1.props $emit
2.$parent $child
3.$attr $listeners
4.provide inject
兄弟组件通信
1.eventBus
2.vuex
3.computed和watch的区别
computed
1.它支持缓存,只有他赖的数据发生变化,才会重新计算
2.不支持异步,当computed有异步操作时,无法监听数据的变化
watch
1.不支持缓存,当数据发生变化时,就会触发相应的操作
2.支持异步监听
3.监听的函数接收2个参数,一个是新的值,一个变化之前的值
4.监听数据必须是 data 中声明的或者父组件传递过来的 props 中的数据,当发生变化时,会触发其他操作
函数接收2个参数
immediate:组件加载立即触发回调函数
deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化
4.vuex
5.第一次页面加载会触发哪几个钩子
beforeCreate created beforeMount mounted
6.在哪个生命周期中发起数据请求
可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
推荐在 created 钩子函数中调用异步请求,有以下优点:
- 能更快获取到服务端数据,减少页面
loading时间; ssr不支持beforeMount、mounted钩子函数,所以放在created中有助于一致性;
7.vue优缺点
渐进式框架:可以在任何项目中轻易的引入
双向数据绑定:操作数据更加方便
组件化:很大程度上实现了逻辑的封装和重用,在构建单页面应用方面有着独特的优势
视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
8.什么是虚拟dom?
Virtual DOM 是DOM节点在 JavaScript 中的一种抽象数据结构,之所以需要虚拟 DOM,是因为浏览器中操作 DOM 的代价比较昂贵,频繁操作 DOM 会产生性能问题。
虚拟 DOM 的作用是在每一次响应式数据发生变化引起页面重渲染时,Vue 对比更新前后的虚拟 DOM,匹配找出尽可能少的需要更新的真实 DOM,从而达到提升性能的目的。
9.如何解决 vue 初始化页面闪动问题
使用 vue 开发时,在 vue 初始化之前,由于 div 是不归 vue 管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于 {{message}} 的字样,虽然一般情况下这个时间很短暂,但是我们还是有必要让解决这个问题的。
首先:在 css 里加上 [v-cloak] { display: none; } 。如果没有彻底解决问题,则在根元素加上 style="display: none;" :style="{display: block }"
10.什么是 SPA,有什么优点和缺点
SPA 仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
优点:
用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染; 有利于前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
缺点:
初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载; 不利于 SEO:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
11.vue2和vue3的区别
响应式原理
生命周期钩子名称
自定义指令钩子名称
新的内置组件
diff 算法
Composition API
12.watch watchEffect区别
用了一遍watch和watchEffect之后,发现他俩主要有以下几点区别:
1.watch是惰性执行的,而watchEffect不是,不考虑watch第三个配置参数的情况下,watch在组件第一次执行的时候是不会执行的,只有在之后依赖项变化的时候再执行,而watchEffect是在程序执行到此处的时候就会立即执行,而后再响应其依赖变化执行。 2.watch需要传递监听的对象,watchEffect不需要
vue3 数组直接赋值
1.把数组放到obj里面
const tableData = reactive({
arr: [ ]
}) //表格数据
const res = [1,2,3] //假设是接口返回的数据
tableData.arr = res
uniapp和微信原生小程序的区别
uniapp优点
可以使用sass less编译语言,使用vue语法,编写方便,可以使用vue的计算属性,可以使用vuex
区别
传参方式不同
微信小程序使用data-属性,uniapp可直接传参
input的value值绑定并监听
uniapp适应v-model,原生写法<input value='{{sex}}' bindinput='jianting'></input>
小程序js和页面的渲染机制
渲染流程
1.在渲染层,宿主环境会把wxml转换成对应的JS对象(宿主环境指的就是微信客户端)
2.将JS对象再次转换成真实DOM树,交由渲染层线程渲染
3.数据变化时,逻辑层提供最新的变化数据,生成新的JS对象与之前的JS对象进行diff算法对比
4.将最新变化的内容反映到真实的DOM树中,更新UI
通讯模型
通讯模型是多线程的:
小程序的渲染层和逻辑层分别由2个线程管理:
渲染层的界面使用了WebView 进行渲染;
逻辑层采用JsCore线程运行JS脚本,js仍是单线程。