AMD、CMD、CommonJs、ES6的对比
他们都是用于在模块化定义中使用的,AMD、CMD、CommonJs是ES5中提供的模块化编程的方案,import/export是ES6中定义新增的。
区别:
1、AMD---是RequireJS在推广过程中对模块定义产出的规范,RequireJS:是一个AMD框架,可以异步加载JS文件,通过define()函数定义,第一个参数是一个数组,里面定义一些需要依赖的包,第二个参数是一个回调函数。通过变量来引用模块里面的方法,最后通过return来输出,是一个依赖前置、异步定义的AMD框架。
2、CMD---是SeaJS在推广过程中对模块定义产出的规范,SeaJS是一个CMD框架,同步加载JS文件,通过define()定义,没有依赖前置,通过require加载插件,CMD是依赖就近,在什么地方使用到插件就在什么地方require该插件,这是一个同步的概念。
3、CommonJS---是通过module.exports定义的,在前端浏览器里面并不支持module.exports,通过node.js后端使用的。Nodejs端是使用CommonJS规范的,前端浏览器一般使用AMD、CMD、ES6等定义模块化开发的
4、ES6模块化---通过export/import对模块进行导出导入。ES6模块是编译时加载的。
继承的方式有哪些
1,原型链继承:创建一个父构造函数,再创建一个子构造函数,子构造函数的prototype等于父构造函数的实例。使用子构造函数创建实例。
缺点,继承单一,无法向父构造函数传参。
2,利用构造函数继承:创建一个父构造函数,再创建一个子构造函数,再子构造函数中调用父构造函数,并且使用call或者apply传入this,改变父构造函数中this的指向,实现继承。
缺点:创建的实例的instanceof(原型)是子构造函数,也就是说只继承了在父构造函数中定义的属性和方法却继承不了在它原型上定义的属性和方法。不过这个方法可以向父构造函数传参了。(但是如果想使用父构造函数的方法的话,这些方法只能在函数内定义,不能再原型上定义。从而产生副本)
3,组合继承:是上面1 和 2 的组合。完美解决了他们两个存在的问题。
创建一个父构造函数,再创建一个子构造函数,再子构造函数中调用父构造函数,并传入this改变父构造函数的this的指向,再把子构造函数的prototype等于父构造函数的实例。使用子构造函数创建实例。
优点:实例和子构造函数还有父构造函数都在同一个原型链上,能继承子构造函数和父构造函数所有的属性和方法,也能继承构造函数定义在原型上的方法和属性。
基础数据类型有哪些,引用数据类型有哪些,两者有什么区别
基础数据类型:Number,String,Boolean,Null,Undefined
引用数据类型:Object,Array,Function,Date
基础数据类型都是一些简单的数据段,
引用数据类型是多个值构成的对象。
a、存储位置不同
基础数据类型大小固定,直接把值存放在变量对象中;引用类型的值大小不固定,会把值放在堆内存中,在变量对象中放的是这个值的引用地址,我们不能直接访问存放在堆内存中的对象,而是通过访问存放在变量对象中的引用地址来访问,这个引用引用和堆内存中的实际对象是互相关联的。
b、复制变量时的不同。
复制基础数据类型的变量,新变量和旧变量是完全独立的,只是值相同而已,
复制引用数据类型的变量,新变量得到的是旧变量的引用地址,这两个引用地址指向同一块堆内存空间存放的对象,所以修改其中一个变量另一个变量也会跟着修改。
如何实现一个深拷贝
// 利用递归逐个拷贝
function deepCopy(obj) {
if(typeof obj != 'object') return obj;
var newObj = Array.isArray(obj) ? [] : {};
for(var key in obj) {
newObj[key] = typeof obj[key] != 'object' ? obj[key] : deepCopy(obj[key]);
}
return newObj;
}
var obj = {
a: 111,
b: 222,
c: [333, 444, 555],
d: {
e: 666,
f: 777,
g: 888
},
h: function() {
return this.a;
}
}
// 利用递归逐个拷贝
var obj2 = deepCopy(obj);
// 简单粗暴,但是无法拷贝对象中的方法
// var obj2 = JSON.parse(JSON.stringify(obj));
// 这个方法只能深拷贝第一级,二级属性后都是浅拷贝了
// var obj2 = Object.assign({}, obj);
// 还可以使用 lodash 这个工具库,_.cloneDeep() 方法 可以很好的实现深拷
// 贝,效果和JSON.parse(JSON.stringify(obj));一样,还能复制函数。
// var obj2 = _.cloneDeep(obj);
obj2.a = '123';
obj2.c.push('哈哈哈');
obj.d.g = 'Yes, I Do!';
console.log("obj::", obj);
console.log("obj2::", obj2);
object.asign()有几个参数,分别有什么意义
object.asign可以有很多个参数,第一个参数是目标对象,后面的参数都是源对象,如果有同名属性,那么后端的属性(值)覆盖前面的。
const target = {a:1};
const source1 = {b:2};
const source2 = {c:3};
const source3 = {c:4};
Object.assign(target, source1,source2,source3);
target // {a: 1, b: 2, c: 4}
object.asign只有一个参数的时候,会直接返回该参数。
var obj = { a: 1 };
Object.assign(obj) === obj; // true
es6用过什么特性
let/const,箭头函数,模板字符串,promise,import/export,展开运算符(...),解构(提取数组或者对象中的值)
说一下迭代器(iterator)是什么?
迭代器给数据结构提供一种统一的遍历方法的接口,任何一种数据结构凡是部署了iterator接口,就可以遍历,或是next() 手动遍历,或是for of 自动遍历。
在解构赋值和使用扩展运算符时,默认调用了iterator方法,(内部调用了应该是用于循环匹配数据)。
iterator对象执行next()方法,返回一个对象,包含value和done两个属性,分别代表当前yield表达式的值和表示遍历是否结束。
给对象添加迭代器接口,让对象也可以使用for of。
var obj = { a: 1, b: 2 };
Object.prototype[Symbol.iterator] = function () {
const keys = Object.keys(this);
let index = 0;
return {
next: () => {
return {
value: this[keys[index++]], // 每次迭代的结果
done: index > keys.length // 迭代结束标识 false停止迭代,true继续迭代
};
}
}
}
for (let n of obj) {
console.log(n);
/**
* 1
* 2
*/
}
说一下generator函数是什么
generator函数是es6提供的一种异步编程解决方案。
generator函数是一个普通函数,但是有两个特征,一是,function关键字与函数名之间有一个星号;二是函数体内部使用yield表达式,定义不同的内部状态。
执行generator函数会返回一个iterator对象(遍历器对象),代表generator函数的内部指针,这个遍历器对象的next()方法可以依次遍历generator函数内部的每个状态,每次遇到yield 会停下来。(换言之,generator是分段执行的,yield是暂停执行标识,而next可以恢复执行)
generator 函数 使用了 yield 和 next() 来实现异步编程
next() 执行函数体,遇到第一个yield,会停下来,等待yield后面的操作完成,比如一个promise。然后再执行下一个next() 会从前一次停止的地方开始向下执行,直到再遇到yield才会停下来。function* 定义一个函数,这个函数定义一些yield。
所以大概这么写
function* gen() {
yield Promise1();
yield Promise2();
yield Promise3();
}
function Promise1() { return new Promise((res, rej) => { res('Promise1') }); }
function Promise2() { return new Promise((res, rej) => { res('Promise2') }); }
function Promise3() { return new Promise((res, rej) => { res('Promise3') }); }
let gren = gen();
gren.next().value.then((res) => { console.log(res); }) // Promise1
gren.next().value.then((res) => { console.log(res); }) // Promise2
gren.next().value.then((res) => { console.log(res); }) // Promise3
async await 是generator yield 的语法糖,所以使用async await可以这么实现
async function gen() {
var res1 = await Promise1();
var res2 = await Promise2();
var res3 = await Promise3();
console.log(res1); // Promise1
console.log(res2); // Promise2
console.log(res3); // Promise3
}
function Promise1() { return new Promise((res, rej) => { res('Promise1') }); }
function Promise2() { return new Promise((res, rej) => { res('Promise2') }); }
function Promise3() { return new Promise((res, rej) => { res('Promise3') }); }
但是平时调用接口等待的时间都是不固定的,Promise模拟调用一个接口:
function* gen() {
yield Promise1();
yield Promise2();
yield Promise3();
}
function Promise1() { return new Promise((res, rej) => { setTimeout(function() { res('Promise1') }, 1000) }); }
function Promise2() { return new Promise((res, rej) => { setTimeout(function() { res('Promise2') }, 5000) }); }
function Promise3() { return new Promise((res, rej) => { setTimeout(function() { res('Promise3') }, 2000) }); }
let gren = gen();
gren.next().value.then((res) => { console.log(res); })
gren.next().value.then((res) => { console.log(res); })
gren.next().value.then((res) => { console.log(res); })
// 得到的结果是
Promise1
Promise3
Promise2
但是我们想要的结果应该是
Promise1
Promise2
Promise3
那么只能改造一下了:
function* gen() {
yield Promise1();
yield Promise2();
yield Promise3();
}
function Promise1() { return new Promise((res, rej) => { setTimeout(function() { res('Promise1') }, 1000) }); }
function Promise2() { return new Promise((res, rej) => { setTimeout(function() { res('Promise2') }, 5000) }); }
function Promise3() { return new Promise((res, rej) => { setTimeout(function() { res('Promise3') }, 2000) }); }
let gren = gen();
gren.next().value.then((res) => {
console.log(res);
gren.next().value.then((res) => {
console.log(res);
gren.next().value.then((res) => {
console.log(res);
})
})
})
或者:
function* gen() {
yield Promise1();
yield Promise2();
yield Promise3();
}
function Promise1() { return new Promise((res, rej) => { setTimeout(function() { console.log('Promise1'); gen1.next(); }, 1000) }) }
function Promise2() { return new Promise((res, rej) => { setTimeout(function() { console.log('Promise2'); gen1.next(); }, 5000) }) }
function Promise3() { return new Promise((res, rej) => { setTimeout(function() { console.log('Promise3'); }, 2000) }) }
let gen1 = gen();
gen1.next();
// 结果
Promise1
Promise2
Promise3
这样的话要是后面的promise的参数需要在前面的promise返回来的值中得到,那就很容易再掉进回调地狱。
var a = 1;
function* gen() {
yield Promise1();
yield Promise2();
yield Promise3();
}
function Promise1() { return new Promise((res, rej) => { setTimeout(function() {res(a)}, 1000) }); }
function Promise2() { return new Promise((res, rej) => { setTimeout(function() {res(a)}, 5000) }); }
function Promise3() { return new Promise((res, rej) => { setTimeout(function() {res(a)}, 2000) }); }
let gren = gen();
gren.next().value.then((res) => {
console.log(res);
a += res;
gren.next().value.then((res) => {
console.log(res);
a += res;
gren.next().value.then((res) => {
console.log(res);
})
})
})
// 得到的结果
1
2
4
或者:
var a = 1;
function* gen() {
yield Promise1();
yield Promise2();
yield Promise3();
}
function Promise1() { return new Promise((res, rej) => { setTimeout(function() { console.log(a); a += a; gen1.next(); }, 1000) }) }
function Promise2() { return new Promise((res, rej) => { setTimeout(function() { console.log(a); a += a; gen1.next(); }, 5000) }) }
function Promise3() { return new Promise((res, rej) => { setTimeout(function() { console.log(a); }, 2000) }) }
let gen1 = gen();
gen1.next();
// 得到的结果
1
2
4
再来看async await的:
var a = 1;
async function gen() {
var res = await Promise1();
console.log(res);
a += res;
var res = await Promise2();
console.log(res);
a += res;
var res = await Promise3();
console.log(res);
}
function Promise1() { return new Promise((res, rej) => { setTimeout(function() {res(a)}, 1000) }); }
function Promise2() { return new Promise((res, rej) => { setTimeout(function() {res(a)}, 5000) }); }
function Promise3() { return new Promise((res, rej) => { setTimeout(function() {res(a)}, 2000) }); }
gen();
// 结果
1
2
4
看得出来 await 会自动暂停了下面的代码,而是等到promise有结果返回了再往下执行,就是说 await 能自动判断出下一步执行的时机。而next() 仍然需要人为的干涉。
有哪些遍历的方法
怎么使用forEach遍历一个对象
因为forEach只能遍历数组,所以使用Object.keys() 把对象拆出来。
Object.keys(obj).forEach(function(key) {
console.log(obj[key]);
})
有一个数组,里面是一个个对象,对象有两个字段,一个是id,id允许相同,一个仍然是数组,数组里又是一个个对象,也有一个id的字段,也允许相同。怎么把这两个数组去重,并且计算重复次数。
var arr = [
{
id: 11314,
list: [
{ id: 13564, extra: '哈哈哈' },{ id: 312, extra: '发士大夫' },{ id: 233, extra: '梵蒂冈的' },{ id: 312, extra: '发热' },{ id: 13564, extra: '是否' },{ id: 312, extra: '发生的' },{ id: 13564, extra: '的发射点' },{ id: 333, extra: '的范围人' },{ id: 4124, extra: '广泛大概' },{ id: 123, extra: 'eternal' }
]
},
{
id: 31233,
list: [
{ id: 324, extra: '第三方' },{ id: 312, extra: '53fdsf' },{ id: 233, extra: 'rfds发生的' },{ id: 231, extra: '认为他' },{ id: 312, extra: '认为' },{ id: 523, extra: '改动到' },{ id: 21321, extra: '发士大夫' },{ id: 333, extra: '65规范' },{ id: 213, extra: '敢死队敢死队' },{ id: 543, extra: '个都过' }
]
},
{
id: 11314,
list: [
{ id: 111, extra: 'e额我er' },{ id: 222, extra: '大概实' },{ id: 333, extra: '原图' },{ id: 222, extra: '方式' },{ id: 222, extra: '当有人问他' },{ id: 111, extra: '热舞' },{ id: 222, extra: '发士大夫' },{ id: 333, extra: '65规范' },{ id: 213, extra: '敢死队敢死队' },{ id: 543, extra: '个都过' }
]
},
{
id: 11314,
list: [
{ id: 311, extra: '对方是否' },{ id: 311, extra: '42人声鼎沸' },{ id: 222, extra: '认为热望' },{ id: 333, extra: '给我如果' },{ id: 222, extra: '访问' },{ id: 111, extra: '士大夫' },{ id: 11, extra: '发士大夫' },{ id: 312, extra: '65规范' },{ id: 213, extra: '敢死队敢死队' },{ id: 222, extra: '个都过' }
]
},
{
id: 123,
list: [
{ id: 444, extra: '打算' },{ id: 233, extra: '和法国' },{ id: 522, extra: '合同号' },{ id: 333, extra: '等到' },{ id: 222, extra: '人情味' },{ id: 333, extra: '任期为一' },{ id: 232, extra: '发士大夫' },{ id: 312, extra: '65规范' },{ id: 213, extra: '敢死队敢死队' },{ id: 423, extra: '个都过' }
]
},
{
id: 31233,
list: [
{ id: 444, extra: '圣' },{ id: 233, extra: '阿萨发' },{ id: 522, extra: '发的好地方' },{ id: 333, extra: '广东富豪' },{ id: 222, extra: '和她' },{ id: 333, extra: '改动到' },{ id: 232, extra: '发士大夫' },{ id: 312, extra: '65规范' },{ id: 213, extra: '敢死队敢死队' },{ id: 423, extra: '个都过' }
]
}
]
var brr = arr.reduce(function(pre, cur, index, _arr) {
if(!pre.some(item => item.id == cur.id)) {
cur.num = 1;
pre.push(cur);
} else {
let _index = pre.findIndex(function(item) { return item.id == cur.id });
pre[_index].num += 1;
}
return pre;
}, []);
for(var i = 0; i < brr.length; i++) {
var crr = brr[i].list.reduce(function(pre, cur, index, _arr) {
if(!pre.some(item => item.id == cur.id)) {
cur.num = 1;
pre.push(cur);
} else {
let _index = pre.findIndex(function(item) { return item.id == cur.id });
pre[_index].num += 1;
}
return pre;
}, [])
brr[i].list = crr;
}
console.log('brr::', brr);
拉平多维数组
var arr = [1,[2,[3,[4,5],6],7], [{a: '111', b: '222'}, 88], null, undefined]
var brr = [];
function flat(ele) {
if(ele == undefined || ele == null) {
return false
}else if(typeof ele != 'object') {
brr.push(ele);
}else if(ele instanceof Array){
for(var i = 0; i < ele.length; i++) {
flat(ele[i]);
}
}else if(ele instanceof Object) {
for(key in ele) {
flat(ele[key]);
}
}
}
flat(arr);
console.log('brr::', brr);
//brr:: [1, 2, 3, 4, 5, 6, 7, "111", "222", 88]
webpack从entry开始的打包过程
Webpack在启动后,会从Entry开始,递归解析Entry依赖的所有Module,每找到一个Module,就会根据Module.rules里配置的Loader规则进行相应的转换处理,对Module进行转换后,再解析出当前Module依赖的Module,这些Module会以Entry为单位进行分组,即为一个Chunk。因此一个Chunk,就是一个Entry及其所有依赖的Module合并的结果。最后Webpack会将所有的Chunk转换成文件输出Output。在整个构建流程中,Webpack会在恰当的时机执行Plugin里定义的逻辑,从而完成Plugin插件的优化任务。
vue响应式原理
当return 一个对象作为data选项的时候,vue会遍历这个对象的所有属性,并且使用Object.defineProperty 给每个属性添加了getter和setter的方法,这些方法在内部让vue能够追踪依赖,在属性被访问和修改的时候通知变更。
每个组件都有一个watcher实例,它会在组件渲染的过程中把接触过的数据属性记录为依赖,之后当依赖的setter触发时会通知watcher,从而使它关联的组件重新渲染。
v-model双向数据绑定
v-model双向数据绑定说的是vue在input,select等表单元素上使用v-model指令,
达到视图和数据自动同步更新更新的效果。v-model其实是语法糖,比方说一个input元素使用了v-model之后,它会在内部给input绑定value属性和input事件,value绑定一个我们在data中定义好的变量,当改变输入框内容时,会自动触发input事件,在input事件里再把输入框的值赋值给绑在value上面的那个变量,这样就可以实现双向数据绑定了。
(value绑定了data中的一个变量,改变表单元素时触发表单事件改变这个变量的值,变量的值又反应到value属性上)
在组件使用v-model也是差不多的,vue先给组件绑上一个value属性和一个input事件,这个value属性绑定的也是在data中定义好的变量,这个变量就是组件的prop,当组件时调用$emit方法,触发input事件时,如果附带了一个新值,那么这个值就会赋值给绑定在value属性上面的那个变量,从而得到更新。
vue的双向数据绑定 和 vue中v-model双向数据绑定 其实是不同的概念。
vue双向数据绑定主要说data和视图的关系,改变data的值会改变视图,改变视图也会改变data的值。至于这个视图通过什么方式改变呢,就不知道了。可以是改变绑了 v-model 的 input,也可以是通过别的方法。
v-model双向数据绑定主要是说v-model在表单元素或者组件上面绑定了value属性和(input,change等表单事件)
实现的双向数据绑定。
过程:value绑定了data中的一个变量,改变表单元素时触发表单事件改变这个变量的值,变量的值又反应到value属性上。
组件的渲染和更新过程
1、组件首次渲染是通过编译器把组件模板编译成一个render函数,执行render函数后生成vnode,开始渲染。
2、vue把data中所有的属性定义了setter和getter方法,首次渲染的时候组件使用的属性会调用getter方法。
3、然后组件的watcher会把这些触发了getter方法的属性收集为依赖,下次修改属性的时候会触发setter方法,就会通知watcher。
4、watcher得到通知后会触发编译器重新编译模板,得到新的render函数,执行render函数后得到新的vnode。
5、再通过patch函数,对比新旧vnode,并对旧的vnode做出处理(比如旧的vnode有children,新的vnode没有,那就移除旧的vnode的children)。
6、最后旧的vnode是经过比对和处理过的准确的vnode,再用这份vnode重新渲染视图。整个渲染和更新的过程就是这样子。
vue为什么无法监测数组(直接俄通过索引修改数组项,修改数组的.length)
通过vue响应式的原理可以知道,vue一开始就把data里面的属性都赋予了setter和getter方法,后面属性改变的时候会触发setter方法更新视图。也就是说只有在data中定义的属性才能得到响应式。如果动态添加一个根级别属性的话,是不会得到响应式的。因为vue不会再为它赋予setter和getter方法。
但可以使用vue的set方法向嵌套对象添加响应式属性。
对于数组,vue不能检测利用索引直接设置一个数组项,也不能检测修改数组的length的属性来修改数组的长度。
之前看过一点源码,是因为vue在给属性赋予setter和getter方法的时候,是没有考虑数组的。而是重新包裹了数组的变更方法,比如push,splice,pop,reverse等,但是对于数组的length和索引是没有处理的,所以我们使用push可以得到响应,使用索引修改数组项却不能。
而且如果vue为数组的索引赋予setter和getter方法,那当数组的长度特别大的时候,可能会带来的性能问题。
vue的作者不那么做,可能也是因为性能代价和用户体验收益不成正比吧。
封装组件
我们有一个自己的组件库的项目,封装了一些常用的组件,在main.js文件中返回一个对象,对象里面有一个install的方法,用来全局注册这些组件的。然后在另一个项目中使用npm安装这个组件库,并且在main.js文件把这个对象导入进来,再使用vue.ues()方法来调用它的install方法完成全局注册。
了解过vue的diff算法吗?
patchNode(oldVnode, vnode); 是用来比较两个节点的,并作出一些增删改的操作。
如果新旧node都有children,并且children不相同,那么就调用updateChildren(oldCh, newCh);
updateChildren(oldCh, newCh); 这两个参数是父节点的children元素集合。两个集合的元素两两比较,找到相同节点再
调用patchNode(oldVnode, vnode);方法,
所以diff的过程其实就是这两个函数不断执行的过程。
部分代码:
function patchVnode(oldVnode, vnode) {
const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(ch)
}
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
removeVnodes(oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text)
}
}
function updateChildren(oldCh, newCh) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let newEndIdx = newCh.length - 1
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
}
对数组 let arr = [3, 5, 1, 9, 4, 2];进行排序
1、冒泡排序
for(var i = 0; i < arr.length - 1; i++) {
for(var j = 0; j < arr.length - 1 - i; j++) {
if(arr[j] > arr[j+1]) {
var temp =arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
2、选择排序
for(var i = 0; i < arr.length - 1; i++) {
var pos = 0;
for(var j = 0; j < arr.length - i; j++) {
if(arr[j] > arr[pos]) {
pos = j;
}
}
var temp = arr[pos];
arr[pos] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = temp;
}
3、插入排序
for(var i = 1; i < arr.length; i++) {
var temp = arr[i];
var j = i - 1;
while(j >= 0 && arr[j] > temp) {
arr[j+1] = arr[j];
j--;
}
arr[j+1] = temp;
}
4、快速排序
三步
1、找基准(一般是以中间项为基准)
2、遍历数组,小于基准的放在left,大于基准的放在right
3、递归
function quikSort(arr) {
if(arr.length <= 1) return arr;
var stan = arr.splice(Math.floor(arr.length / 2), 1)[0]; // 找基准
var left = [];
var right = [];
for(var i = 0; i < arr.length; i++) {
if(arr[i] < stan) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quikSort(left).concat(stan, quikSort(right));
}
数组常用方法
1、concat();
concat() 方法用于链接两个或多个数组。该方法不会改变现有的数组,仅会返回一个连接后的新数组。
var arr1 = [1,2,3];
var arr2 = [4,5];
var arr3 = arr1.concat(arr2);
console.log(arr1); //[1, 2, 3]
console.log(arr3); //[1, 2, 3, 4, 5]
2、join()
join() 方法把数组中的所有元素放入一个字符串。元素是通过指定的分隔符分割的,默认使用 ',' 分隔,不改变原数组。
var arr = [2,3,4];
console.log(arr.join()); //2,3,4
console.log(arr.join('')); //234
console.log(arr); //[2, 3, 4]
3、push()
push() 方法向数组末尾添加一个活多个元素,返回数组长度,改变原数组。
var arr = [1, 2];
arr.push(3); // [1, 2, 3]
arr.push(4, 5, 6); // [1, 2, 3, 4, 5, 6]
4、pop()
pop() 方法用于删除并返回数组的最后一个元素。返回最后一个元素,会改变原数组。
var arr = [2,3,4];
console.log(arr.pop()); //4
console.log(arr); //[2,3]
5、unshift()
unshift() 方法可向数组的开头添加一个或多个元素,并返回新的长度。返回新数组长度,改变原数组。
var arr = [2,3,4,5];
console.log(arr.unshift(3,6)); //6
console.log(arr); //[3, 6, 2, 3, 4, 5]
tip:该方法可以不传参数,不传参数就是不增加元素。
6、shift()
shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。返回第一个元素,改变原数组。
var arr = [2,3,4];
console.log(arr.shift()); //2
console.log(arr); //[3,4]
7、slice()
slice() 返回一个新的数组,包含从 start 到 end (不包括该元素)的 数组中的元素。返回选定的元素,该方法不会修改原数组。
var arr = [2,3,4,5];
console.log(arr.slice(1,3)); //[3,4]
console.log(arr); //[2,3,4,5]
8、splice()
splice() 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组。splice() 方法会直接对数组进行修改。
var a = [5,6,7,8];
console.log(a.splice(1,0,9)); //[]
console.log(a); // [5, 9, 6, 7, 8]
var b = [5,6,7,8];
console.log(b.splice(1,2,3)); //[6, 7]
console.log(b); //[5, 3, 8]
9、sort
sort 按照 Unicode code 位置排序,默认升序,或者自定义排序规则
var fruit = ['cherries', 'apples', 'bananas'];
fruit.sort(); // ['apples', 'bananas', 'cherries']
var scores = [1, 10, 21, 2];
scores.sort(); // [1, 10, 2, 21]
scores.sort(function(a, b) { return a - b }); // [1, 2, 10, 21]
scores.sort(function(a, b) { return b - a }); // [21, 10, 2, 1]
10、reverse()
reverse() 方法用于颠倒数组中元素的顺序。返回的是颠倒后的数组,会改变原数组。
var arr = [2,3,4];
console.log(arr.reverse()); //[4, 3, 2]
console.log(arr); //[4, 3, 2]
11、indexOf
接受两个参数:查找的值、查找起始位置
不存在,返回 -1 ;存在,返回位置
var a = [2, 9, 9];
a.indexOf(2); // 0
a.indexOf(7); // -1
if (a.indexOf(7) === -1) {
// element doesn't exist in array
}
12、every,some,filter,map,forEach,includes,find,findIndex
css垂直居中的方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>垂直居中</title>
<style>
.box {
width: 1000px;
height: 1000px;
background: gray;
display: flex;
justify-content: center;
align-items: center;
}
.item-1 {
width: 200px;
height: 200px;
background: red;
color: #fff;
text-align: center;
line-height: 200px;
}
.item-2 {
width: 300px;
height: 300px;
background: #aadddd;
color: #000;
display: flex;
justify-content: center;
align-items: center;
}
.item-3 {
width: 500px;
height: 500px;
background: red;
position: relative;
}
.item-3 span {
display: inline-block;
text-align: center;
width: 200px;
height: 200px;
background: #ccc;
color: red;
line-height: 200px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
</head>
<body>
<div class="box">
<div class="item-1">113231</div>
<div class="item-2">哈哈哈</div>
<div class="item-3"><span>染黑是否是</span></div>
</div>
</body>
</html>
输入URL回车到渲染过程发生了什么?
从输入URL到渲染出整个页面的过程包括三个部分:
1、DNS解析URL的过程
2、浏览器发送请求与服务器交互的过程
3、浏览器对接收到的html页面渲染的过程
一、DNS解析URL的过程
DNS解析的过程就是寻找哪个服务器上有请求的资源。因为ip地址不容易记忆,一般会使用URL域名(如www.baidu.com)作为网址。DNS解析就是将域名翻译成IP地址的过程。
具体过程:
1)浏览器缓存:浏览器会按照一定的频率 缓存DNS记录
2)操作系统缓存:如果浏览器缓存中找不到需要的DNS记录,就会取操作系统中找
3)路由缓存:路由器也有DNS缓存
4)ISP的DNS服务器:ISP有专门的DNS服务器应对DNS查询请求
5)根服务器:ISP的DNS服务器找不到之后,就要向根服务器发出请求,进行递归查询
二、浏览器与服务器交互过程
1)首先浏览器利用tcp协议通过三次握手与服务器建立连接
http请求包括header和body。header中包括请求的方式(get和post)、请求的协议 (http、https、ftp)、请求的地址ip、缓存cookie。body中有请求的内容。
2)浏览器根据解析到的IP地址和端口号发起http的get请求.
3)服务器接收到http请求之后,开始搜索html页面,并使用http返回响应报文
4)若状态码为200显示响应成功,浏览器接收到返回的html页面之后,开始进行页面的渲染
三、浏览器页面渲染过程
1)浏览器根据深度遍历的方式把html节点遍历成dom 树
2)将css解析成CSS DOM树
3)将dom树和CSS DOM树构造成render树
4)JS根据得到的render树 计算所有节点在屏幕中的位置,进行布局(回流)
5)遍历render树并调用硬件API绘制所有节点(重绘)
补充: 构造render渲染树的过程
1、从DOM树的根节点开始遍历每个可见的节点。
2、对于每个可见的节点,找到CSS树中的对应的规则,并且应用他们。
3、根据每个可见的节点及其对应的样式,组合生成渲染树。