作者: 文章来源:
本文原地址:https://www.mimiwuqi.com/webqianduan/196073.html
目录文章目录本文整理自 2021 年开始,技术面是常考的面试题,虽然已经 2202 年了,相信这些面试题依然是常考点。接下来你要面对上百道面试题,如果你是小白菜,推荐使用 2~3 周的时间来消化接下来的面试题,遇到不会的没听说过名词请立刻去搜,文章中只是简答,如果想要详细了解的话还需要你自觉去搜索。当然,大佬就绕道了。如果有错误的地方烦请各位一定要指出,免得误导更多人。接下来的题我会根据重点程度使用⭐来标记,⭐越多标明越重点,满星是 5 颗星。好了,废话不多说,让我们一起觉醒吧!
基本数据类型有:
引用数据类型统称为 Object 类型,细分的话有:
基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,在栈中保存数据的引用地址,这个引用地址指向的是对应的数据,以便快速查找到堆内存中的对象。
顺便提一句,栈内存是自动分配内存的。而堆内存是动态分配内存的,不会自动释放。所以每次使用完对象的时候都要把它设置为 null,从而减少无用内存的消耗。
因为在 JS 中,浮点数是使用 64 位固定长度来表示的,其中的 1 位表示符号位,11 位用来表示指数位,剩下的 52 位尾数位,由于只有 52 位表示尾数位。
而0.1转为二进制是一个无限循环数0.0001100110011001100......(1100 循环)
// 0.1 和 0.2 都转化成二进制后再进行运算 0.00011001100110011001100110011001100110011001100110011010 + 0.0011001100110011001100110011001100110011001100110011010 = 0.0100110011001100110011001100110011001100110011001100111 // 转成十进制正好是 0.30000000000000004
// 0.2 和 0.3 都转化为二进制后再进行计算 0.001100110011001100110011001100110011001100110011001101 + 0.0100110011001100110011001100110011001100110011001101 = 0.10000000000000000000000000000000000000000000000000001 //尾数为大于 52 位 // 而实际取值只取 52 位尾数位,就变成了 0.1000000000000000000000000000000000000000000000000000 //0.5
0.2 和0.3分别转换为二进制进行计算:在内存中,它们的尾数位都是等于 52 位的,而他们相加必定大于 52 位,而他们相加又恰巧前 52 位尾数都是0,截取后恰好是0.1000000000000000000000000000000000000000000000000000也就是 0.5
在console.log的时候会二进制转换为十进制,十进制再会转为字符串的形式,在转换的过程中发生了取近似值,所以打印出来的是一个近似值的字符串。
typeof 缺点:typeof null的值为Object,无法分辨是null还是Object。instanceof 缺点:只能判断对象是否存在于目标对象的原型链上。constructorObject.prototype.toString.call()
Object.prototype.toString.call();它可以区分 null、string、boolean、number、undefined、array、function、object、date、math数据类型。示例:
// -----------------------------------------typeof
typeof undefined // 'undefined'
typeof '10' // 'String'
typeof 10 // 'Number'
typeof false // 'Boolean'
typeof Symbol() // 'Symbol'
typeof Function // ‘function'
typeof null // ‘Object’
typeof [] // 'Object'
typeof {} // 'Object'
// -----------------------------------------instanceof
function Foo() { }
var f1 = new Foo();
var d = new Number(1)
console.log(f1 instanceof Foo);// true
console.log(d instanceof Number); //true
console.log(123 instanceof Number); //false -->不能判断字面量的基本数据类型
// -----------------------------------------constructor
var d = new Number(1)
var e = 1
function fn() {
console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;
console.log(e.constructor);//ƒ Number() { [native code] }
console.log(e.constructor.name);//Number
console.log(fn.constructor.name) // Function
console.log(date.constructor.name)// Date
console.log(arr.constructor.name) // Array
console.log(reg.constructor.name) // RegExp
//-----------------------------------------Object.prototype.toString.call()
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call(123)); // "[object Number]"
console.log(Object.prototype.toString.call("abc")); // "[object String]"
console.log(Object.prototype.toString.call(true)); // "[object Boolean]"
function fn() {
console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;
console.log(Object.prototype.toString.call(fn));// "[object Function]"
console.log(Object.prototype.toString.call(date));// "[object Date]"
console.log(Object.prototype.toString.call(arr)); // "[object Array]"
console.log(Object.prototype.toString.call(reg));// "[object RegExp]"
instanceof原理实际上就是查找目标对象的原型链。
function myInstance(L, R) {//L 代表 instanceof 左边,R 代表右边
var RP = R.prototype
var LP = L.__proto__
while (true) {
if (LP == null) {
return false
}
if (LP == RP) {
return true
}
LP = LP.__proto__
}
}
console.log(myInstance({}, Object));
因为在JavaScript中,不同的对象都是使用二进制存储的,如果二进制前三位都是 0 的话,系统会判断为是Object类型,而 null 的二进制全是 0,自然也就判断为Object。
这个 bug 是初版本的 JavaScript 中留下的,扩展一下其他五种标识位:
000 对象1 整型010 双精度类型100字符串110布尔类型===是严格意义上的相等,会比较两边的数据类型和值大小:
==是非严格意义上的相等:
| Null == Undefined | true |
| String == Number | 先将 String 转为 Number,在比较大小 |
| Boolean == Number | 先将 Boolean 转为 Number,在进行比较 |
| Object == String,Number,Symbol | Object 转化为原始类型 |
fn设置为需要调用的函数fn__proto__指向_this的prototypeArray.prototype.slice.call()call 方法:
Function.prototype.myCall = function (context) {
// 先判断调用 myCall 是不是一个函数
// 这里的 this 就是调用 myCall 的
if (typeof this !== 'function') {
throw new TypeError("Not a Function")
}
// 不传参数默认为 window
context = context || window
// 保存 this
context.fn = this
// 保存参数
let args = Array.from(arguments).slice(1) //Array.from 把伪数组对象转为数组
// 调用函数
let result = context.fn(...args)
delete context.fn
return result
}
apply 方法:
Function.prototype.myApply = function (context) {
// 判断 this 是不是函数
if (typeof this !== "function") {
throw new TypeError("Not a Function")
}
let result
// 默认是 window
context = context || window
// 保存 this
context.fn = this
// 是否传参
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
bind 方法:
Function.prototype.myBind = function (context) {
// 判断是否是一个函数
if (typeof this !== "function") {
throw new TypeError("Not a Function")
}
// 保存调用 bind 的函数
const _this = this
// 保存参数
const args = Array.prototype.slice.call(arguments, 1)
// 返回一个函数
return function F() {
// 判断是不是 new 出来的
if (this instanceof F) {
// 如果是 new 出来的
// 返回一个空对象,且使创建出来的实例的 __proto__ 指向 _this 的 prototype,且完成函数柯里化
return new _this(...args, ...arguments)
} else {
// 如果不是 new 出来的改变 this 指向,且完成函数柯里化
return _this.apply(context, args.concat(...arguments))
}
}
}
字面量:
new 内部:
__proto__指向原函数的prototype;手写 new:
// 手写一个 new
function myNew(fn, ...args) {
// 创建一个空对象
let obj = {}
// 使空对象的隐式原型指向原函数的显式原型
obj.__proto__ = fn.prototype
// this 指向 obj
let result = fn.apply(obj, args)
// 返回
return result instanceof Object ? result : obj
}
字面量和 new 创建出来的对象会继承 Object 的方法和属性,他们的隐式原型会指向 Object 的显式原型,
而 Object.create(null)创建出来的对象原型为 null,作为原型链的顶端,自然也没有继承 Object 的方法和属性
规定变量和函数的可使用范围称作作用域;
每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。
执行上下文分为:
window对象,并规定this指向window,执行 js 的时候就压入栈底,关闭浏览器的时候才弹出。arguments对象(并赋值)、函数声明(并赋值)、变量声明(不赋值),函数表达式声明(不赋值);会确定this指向;会确定作用域;eval执行上下文。执行栈:
很多人都吃不透 js 闭包,这里推荐一篇文章:JavaScript 闭包实例讲解
函数执行,形成私有的执行上下文,使内部私有变量不受外界干扰,起到保护和保存的作用。
作用:
应用:
缺点:
原型:原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。
原型链:多个__proto__组成的集合成为原型链。
__proto__都指向他们构造函数的prototypeprototype都是对象,自然它的__proto__指向的是Object()的prototypeFunction()的显示原型Object的隐式原型是null关于原型与原型链,推荐阅读:我对 js 中原型原型链的理解(有图有真相)
常用的继承方式:原型继承、组合继承、寄生组合继承、ES6 的 extend。
原型继承:
// ----方法一:原型继承
// 原型继承
// 把父类的实例作为子类的原型
// 缺点:子类的实例共享了父类构造函数的引用属性 不能传参
var person = {
friends: ["a", "b", "c", "d"]
}
var p1 = Object.create(person)
p1.friends.push("aaa")//缺点:子类的实例共享了父类构造函数的引用属性
console.log(p1);
console.log(person);//缺点:子类的实例共享了父类构造函数的引用属性
组合继承:
// ----方法二:组合继承
// 在子函数中运行父函数,但是要利用 call 把 this 改变一下,
// 再在子函数的 prototype 里面 new Father(),使 Father 的原型中的方法也得到继承,
// 最后改变 Son 的原型中的 constructor
// 缺点:调用了两次父类的构造函数,造成了不必要的消耗,父类方法可以复用
// 优点可传参,不共享父类引用属性
function Father(name) {
this.name = name
this.hobby = ["篮球", "足球", "乒乓球"]
}
Father.prototype.getName = function () {
console.log(this.name);
}
function Son(name, age) {
Father.call(this, name)
this.age = age
}
Son.prototype = new Father()
Son.prototype.constructor = Son
var s = new Son("ming", 20)
console.log(s);
寄生组合继承:
// ----方法三:寄生组合继承
function Father(name) {
this.name = name
this.hobby = ["篮球", "足球", "乒乓球"]
}
Father.prototype.getName = function () {
console.log(this.name);
}
function Son(name, age) {
Father.call(this, name)
this.age = age
}
Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son
var s2 = new Son("ming", 18)
console.log(s2);
ES6 的extend:
// ----方法四:ES6 的 extend(寄生组合继承的语法糖)
// 子类只要继承父类,可以不写 constructor,一旦写了,
// 则在 constructor 中的第一句话必须是 super 。
class Son3 extends Father { // Son.prototype.__proto__ = Father.prototype
constructor(y) {
super(200) // super(200) => Father.call(this,200)
this.y = y
}
}
内存泄露是指不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内存泄漏。
内存泄漏指我们无法在通过 js 访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃。
// ----浅拷贝
// 只是把对象的属性和属性值拷贝到另一个对象中
var obj1 = {
a: {
a1: { a2: 1 },
a10: { a11: 123, a111: { a1111: 123123 } }
},
b: 123,
c: "123"
}
// 方式 1
function shallowClone1(o) {
let obj = {}
for (let i in o) {
obj[i] = o[i]
}
return obj
}
// 方式 2
var shallowObj2 = { ...obj1 }
// 方式 3
var shallowObj3 = Object.assign({}, obj1)
let shallowObj = shallowClone1(obj1);
shallowObj.a.a1 = 999
shallowObj.b = true
console.log(obj1); //第一层的没有被改变,一层以下就被改变了
// ----深拷贝
// 简易版
function deepClone(o) {
let obj = {}
for (var i in o) {
// if(o.hasOwnProperty(i)){
if (typeof o[i] === "object") {
obj[i] = deepClone(o[i])
} else {
obj[i] = o[i]
}
// }
}
return obj
}
var myObj = {
a: {
a1: { a2: 1 },
a10: { a11: 123, a111: { a1111: 123123 } }
},
b: 123,
c: "123"
}
var deepObj1 = deepClone(myObj)
deepObj1.a.a1 = 999
deepObj1.b = false
console.log(myObj);
// 简易版存在的问题:参数没有做检验,传入的可能是 Array、null、regExp、Date
function deepClone2(o) {
if (Object.prototype.toString.call(o) === "[object Object]") { //检测是否为对象
let obj = {}
for (var i in o) {
if (o.hasOwnProperty(i)) {
if (typeof o[i] === "object") {
obj[i] = deepClone(o[i])
} else {
obj[i] = o[i]
}
}
}
return obj
} else {
return o
}
}
function isObject(o) {
return Object.prototype.toString.call(o) === "[object Object]" || Object.prototype.toString.call(o) === "[object Array]"
}
// 继续升级,没有考虑到数组,以及 ES6 中的 map、set、weakset、weakmap
function deepClone3(o) {
if (isObject(o)) {//检测是否为对象或者数组
let obj = Array.isArray(o) ? [] : {}
for (let i in o) {
if (isObject(o[i])) {
obj[i] = deepClone(o[i])
} else {
obj[i] = o[i]
}
}
return obj
} else {
return o
}
}
// 有可能碰到循环引用问题 var a = {}; a.a = a; clone(a);//会造成一个死循环
// 循环检测
// 继续升级
function deepClone4(o, hash = new map()) {
if (!isObject(o)) return o//检测是否为对象或者数组
if (hash.has(o)) return hash.get(o)
let obj = Array.isArray(o) ? [] : {}
hash.set(o, obj)
for (let i in o) {
if (isObject(o[i])) {
obj[i] = deepClone4(o[i], hash)
} else {
obj[i] = o[i]
}
}
return obj
}
// 递归易出现爆栈问题
// 将递归改为循环,就不会出现爆栈问题了
var a1 = { a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' };
var b1 = { b: { c: { d: 1 } } }
function cloneLoop(x) {
const root = {};
// 栈
const loopList = [ //->[]->[{parent:{a:1,b:2},key:c,data:{ c1: 3, c2: { c21: 4, c22: 5 } }}]
{
parent: root,
key: undefined,
data: x,
}
];
while (loopList.length) {
// 深度优先
const node = loopList.pop();
const parent = node.parent; //{} //{a:1,b:2}
const key = node.key; //undefined //c
const data = node.data; //{ a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' } //{ c1: 3, c2: { c21: 4, c22: 5 } }}
// 初始化赋值目标,key 为 undefined 则拷贝到父元素,否则拷贝到子元素
let res = parent; //{}->{a:1,b:2,d:'asd'} //{a:1,b:2}->{}
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
})
} else {
res[k] = data[k];
}
}
}
}
return root
}
function deepClone5(o) {
let result = {}
let loopList = [
{
parent: result,
key: undefined,
data: o
}
]
while (loopList.length) {
let node = loopList.pop()
let { parent, key, data } = node
let anoPar = parent
if (typeof key !== 'undefined') {
anoPar = parent[key] = {}
}
for (let i in data) {
if (typeof data[i] === 'object') {
loopList.push({
parent: anoPar,
key: i,
data: data[i]
})
} else {
anoPar[i] = data[i]
}
}
}
return result
}
let cloneA1 = deepClone5(a1)
cloneA1.c.c2.c22 = 5555555
console.log(a1);
console.log(cloneA1);
// ------------------------------------------JSON.stringify()实现深拷贝
function cloneJson(o) {
return JSON.parse(JSON.stringify(o))
}
// let obj = { a: { c: 1 }, b: {} };
// obj.b = obj;
// console.log(JSON.parse(JSON.stringify(obj))) // 报错 // Converting circular structure to JSON
深拷贝能使用 hash 递归的方式写出来就可以了,不过技多不压身,推荐还是看一看使用 while 实现深拷贝方法。
因为 JS 里面有可视的 Dom,如果是多线程的话,这个线程正在删除 DOM 节点,另一个线程正在编辑 Dom 节点,导致浏览器不知道该听谁的。
回调函数
next方法来请求一个一个的值,返回的对象有两个属性,一个是value,也就是值;另一个是done,是个布尔类型,done为true说明生成器函数执行完毕,没有可返回的值了;done为true后继续调用迭代器的next方法,返回值的value为undefined状态变化:
yield属性的时候,都会返回一个对象next方法的时候,生成器又从挂起状态改为执行状态,继续上一次的执行位置执行yield依次循环yield了,就会返回一个结果对象done为 true,value为undefined做到会写简易版的 promise 和 all 函数就可以
class MyPromise2 {
constructor(executor) {
// 规定状态
this.state = "pending"
// 保存 `resolve(res)` 的 res 值
this.value = undefined
// 保存 `reject(err)` 的 err 值
this.reason = undefined
// 成功存放的数组
this.successCB = []
// 失败存放的数组
this.failCB = []
let resolve = (value) => {
if (this.state === "pending") {
this.state = "fulfilled"
this.value = value
this.successCB.forEach(f => f())
}
}
let reject = (reason) => {
if (this.state === "pending") {
this.state = "rejected"
this.value = value
this.failCB.forEach(f => f())
}
}
try {
// 执行
executor(resolve, reject)
} catch (error) {
// 若出错,直接调用 reject
reject(error)
}
}
then(onFulfilled, onRejected) {
if (this.state === "fulfilled") {
onFulfilled(this.value)
}
if (this.state === "rejected") {
onRejected(this.value)
}
if (this.state === "pending") {
this.successCB.push(() => { onFulfilled(this.value) })
this.failCB.push(() => { onRejected(this.reason) })
}
}
}
Promise.all = function (promises) {
let list = []
let count = 0
function handle(i, data) {
list[i] = data
count++
if (count == promises.length) {
resolve(list)
}
}
return Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) { promises[i].then(res => {
handle(i, res)
}, err => reject(err))
}
})
}
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
async1()
console.log('script start')
await时,如果返回的不是一个promise对象,await会阻塞下面代码(当前async代码块的代码),会先执行async外的同步代码(在这之前先看看await中函数的同步代码,先把同步代码执行完),等待同步代码执行完之后,再回到async内部继续执行await时,如果返回的是一个promise对象,await会阻塞下面代码(当前async代码块的代码),会先执行async外的同步代码(在这之前先看看 await 中函数的同步代码,先把同步代码执行完),等待同步代码执行完之后,再回到async内部等promise状态达到fulfill的时候再继续执行下面的代码所以结果为:
async1 start async2 script start async1 end
宏任务:script、setTimeOut、setInterval、setImmediate
微任务:promise.then,process.nextTick、Object.observe、MutationObserver
注意:Promise 是同步任务。
例题 1:
setTimeout(function(){
console.log('1')
});
new Promise(function(resolve){
console.log('2');
resolve();
}).then(function(){
console.log('3')
});
console.log('4');
new Promise(function(resolve){
console.log('5');
resolve();
}).then(function(){
console.log('6')
});
setTimeout(function(){
console.log('7')
});
function bar(){
console.log('8')
foo()
}
function foo(){
console.log('9')
}
console.log('10')
bar()
解析:
setTimeout,把setTimeout分发到宏任务Event Queue中;new Promise属于主线程任务直接执行打印 2;Promis下的then方法属于微任务,把 then 分到微任务 Event Queue中;console.log(‘4’)属于主线程任务,直接执行打印 4;new Promise也是直接执行打印 5,Promise 下到 then 分发到微任务Event Queue中;setTimouse也是直接分发到宏任务Event Queue中,等待执行;console.log(‘10’)属于主线程任务直接执行;bar函数中调用foo函数,执行foo函数到中代码,打印 9;Event Queue中代码,实行先进先出,所以依次打印 3,6;Event Queue中代码执行完,就执行宏任务Event Queue中代码,也是先进先出,依次打印 1,7。最终结果:2,4,5,10,8,9,3,6,1,7
例题 2:
setTimeout(() => {
console.log('1');
new Promise(function (resolve, reject) {
console.log('2');
setTimeout(() => {
console.log('3');
}, 0);
resolve();
}).then(function () {
console.log('4')
})
}, 0);
console.log('5'); //5 7 10 8 1 2 4 6 3
setTimeout(() => {
console.log('6');
}, 0);
new Promise(function (resolve, reject) {
console.log('7');
// reject();
resolve();
}).then(function () {
console.log('8')
}).catch(function () {
console.log('9')
})
console.log('10');
运行结果: 5 7 10 8 1 2 4 6 3
var
var声明的变量可进行变量提升,let和const不会var可以重复声明var在非函数作用域中定义是挂在到window上的let
let声明的变量只在局部起作用let防止变量污染const
let的所有特征const声明的是对象的话,是可以修改对象里面的值的this指向问题,箭头函数的this指向它定义时所在的对象,而不是调用时所在的对象arguments对象,不能使用arguments,如果要获取参数的话可以使用rest运算符;yield属性,不能作为生成器Generator使用;new
this,不能调用call和apply;prototype,new关键字内部需要把新对象的_proto_指向函数的prototype。get和set;get和set;target,第二个对象是专门放get和set的handler对象。Proxy和上面两个的区别在于Proxy专门对对象的属性进行get和set。Object.defineProperty,vue3 用的是proxy;为什么要使用模块化:
立即执行函数
在早期,使用立即执行函数实现模块化是常见的手段,通过函数作用域解决了命名冲突、污染全局作用域的问题。
(function(globalVariable){
globalVariable.test = function() {}
// ... 声明各种变量、函数都不会污染全局作用域
})(globalVariable)
AMD 和 CMD
鉴于目前这两种实现方式已经很少见到,所以不再对具体特性细聊,只需要了解这两者是如何使用的。
// AMD
define(['./a', './b'], function(a, b) {
// 加载模块完毕可以使用
a.do()
b.do()
})
// CMD
define(function(require, exports, module) {
// 加载模块
// 可以把 require 写在函数体的任意地方实现延迟加载
var a = require('./a')
a.doSomething()
})
CommonJS
CommonJS 最早是 Node 在使用,目前也仍然广泛使用,比如在 Webpack 中你就能见到它,当然目前在 Node 中的模块管理已经和 CommonJS 有一些区别了。
// a.js
module.exports = {
a: 1
}
// or
exports.a = 1
// b.js
var module = require('./a.js')
module.a // -> log 1
因为 CommonJS 还是会使用到的,所以这里会对一些疑难点进行解析
先说 require 吧
var module = require('./a.js')
module.a
// 这里其实就是包装了一层立即执行函数,这样就不会污染全局变量了,
// 重要的是 module 这里,module 是 Node 独有的一个变量
module.exports = {
a: 1
}
// module 基本实现
var module = {
id: 'xxxx', // 我总得知道怎么去找到他吧
exports: {} // exports 就是个空对象
}
// 这个是为什么 exports 和 module.exports 用法相似的原因
var exports = module.exports
var load = function (module) {
// 导出的东西
var a = 1
module.exports = a
return module.exports
};
// 然后当我 require 的时候去找到独特的
// id,然后将要使用的东西用立即执行函数包装下,over
另外虽然 exports 和 module.exports 用法相似,但是不能对 exports 直接赋值。因为 var exports = module.exports 这句代码表明了 exports 和 module.exports 享有相同地址,通过改变对象的属性值会对两者都起效,但是如果直接对 exports 赋值就会导致两者不再指向同一个内存地址,修改并不会对 module.exports 起效。
ES Module
ES Module 是原生实现的模块化方案,与 CommonJS 有以下几个区别
require(${path}/xx.js),后者目前不支持,但是已有提案require/exports 来执行的// 引入模块 API
import XXX from './a.js'
import { XXX } from './a.js'
// 导出模块 API
export function a() {}
export default function() {}
exports.xxx='xxx'module.export = {}exports是module.exports的引用,两个指向的是用一个地址,而 require 能看到的只有module.exportsAMD
CMD
commonjs模块输出的是值的拷贝,而 ES6 输出的值是值的引用
commonjs是在运行时加载,是一个对象,ES6 是在编译时加载,是一个代码块
commonjs的 this 指向当前模块,ES6 的 this 指向 undefined
JSONP⭐⭐⭐⭐⭐
JSONP通过同源策略涉及不到的”漏洞”,也就是像img中的src,link标签的href,script的src都没有被同源策略限制到JSONP只能 get 请求function addScriptTag(src) {
var script = document.createElement("script")
script.setAttribute('type', 'text/javascript')
script.src = src
document.appendChild(script)
}
// 回调函数
function endFn(res) {
console.log(res.message);
}
// 前后端商量好,后端如果传数据的话,返回`endFn({message:'hello'})`
document.domain⭐
- 只能跨一级域名相同的域(www.xxx.om 和 www.id.xxx.com , 二者都有 xxx.com)
- 使用方法
>表示输入,<表示输出 ,以下是在www.id.aa.com网站下执行的操作> var w = window.open("https://www.xxx.com") < undefined > w.document ✖ VM3061:1 Uncaught DOMException: Blocked a frame with origin "https://id.xxx.com" from accessing a cross-origin frame. at :1:3 > document.domain < "id.xxx.com" > document.domain = 'xxx.com' < "xxx.com" > w.document < #document
location.hash + iframe⭐⭐
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 向 b.html 传 hash 值
setTimeout(function() {
iframe.src = iframe.src + '#user=admin';
}, 1000);
// 开放给同域 c.html 的回调方法
function onCallback(res) {
alert('data from c.html ---> ' + res);
}
</script>
<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 监听 a.html 传来的 hash 值,再传给 c.html
window.onhashchange = function () {
iframe.src = iframe.src + location.hash;
};
</script>
<script>
// 监听 b.html 传来的 hash 值
window.onhashchange = function () {
// 再通过操作同域 a.html 的 js 回调,将结果传回
window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
};
</script>
window.name + iframe⭐⭐
postMessage⭐
CORS⭐⭐⭐⭐⭐
HEAD、POST、GETAccept、AcceptLanguage、ContentType、ContentLanguage、Last-Event-IdAccess-Control-Allow-Origin –必须XMLHttpRequest只能拿到六个字段,要想拿到其他的需要在这里指定Access-Control-Allow-Credentials –是否可传 cookiecookie,前端需要设置xhr.withCredentials = true,后端设置Access-Control-Allow-CredentialsOriginAccess-Control-Request-MethodAccess-Control-Request-HeaderOrigin、Access-Control-Allow-Method和Access-Control-Request-Header之后确认允许就可以做出回应了;nginx 代理跨域⭐⭐⭐⭐
请求行、请求头、空行、请求体
七层模型:
TCP 属于传输层。
document.cookie(“名字 = 数据;expire=时间”)
目前没有提供删除的操作,但是可以把它的 Max-age 设置为 0,也就是立马失效,也就是删除了
更详细的推荐阅读这篇文章:传送门
cache-control里的max-age,判断数据有没有过期,如果没有直接使用该缓存 ,有些用户可能会在没有过期的时候就点了刷新按钮,这个时候浏览器就回去请求服务端,要想避免这样做,可以在cache-control里面加一个immutable.If-None-Match,也就是响应头中的etag属性,每个文件对应一个etag;另一个参数是If-Modified-Since,也就是响应头中的Last-Modified属性,带着这两个参数去检验缓存是否真的过期,如果没有过期,则服务器会给浏览器返回一个 304 状态码,表示缓存没有过期,可以使用旧缓存。etag的作用
last-modified属性的时间就会改变,导致服务器会重新发送资源,但是etag的出现就完美的避免了这个问题,他是文件的唯一标识缓存位置:
相关文章推荐:浏览器缓存知识
推荐阅读:前端浏览器输入 URL 后发生什么?
defer或者async的标签。
DOMContentloaed之前// ------防抖函数
function debounce(func, delay) {
let timeout
return function () {
let arg = arguments
if (timeout) clearTimeout(timeout)
timeout = setTimeout(() => {
func(arg)
}, delay);
}
}
// ------立即执行防抖函数
function debounce2(fn, delay) {
let timer
return function () {
let args = arguments
if (timer) clearTimeout(timer)
let callNow = !timer
timer = setTimeout(() => {
timer = null
}, delay);
if (callNow) { fn(args) }
}
}
// ------立即执行防抖函数+普通防抖
function debounce3(fn, delay, immediate) {
let timer
return function () {
let args = arguments
let _this = this
if (timer) clearTimeout(timer)
if (immediate) {
let callNow = !timer
timer = setTimeout(() => {
timer = null
}, delay);
if (callNow) { fn.apply(_this, args) }
} else {
timeout = setTimeout(() => {
func.apply(_this, arguments)
}, delay);
}
}
}
// ------节流 ,时间戳版
function throttle(fn, wait) {
let previous = 0
return function () {
let now = Date.now()
let _this = this
let args = arguments
if (now - previous > wait) {
fn.apply(_this, arguments)
previous = now
}
}
}
// ------节流 ,定时器版
function throttle2(fn, wait) {
let timer
return function () {
let _this = this
let args = arguments
if (!timer) {
timer = setTimeout(() => {
timer = null
fn.apply(_this, arguments)
}, wait);
}
}
}
function add() {
var args = Array.prototype.slice.call(arguments)
var adder = function () {
args.push(...arguments)
return adder
}
adder.toString = function () {
return args.reduce((prev, curr) => {
return prev + curr
}, 0)
}
return adder
}
let a = add(1, 2, 3)
let b = add(1)(2)(3)
console.log(a)
console.log(b)
console.log(add(1, 2)(3));
console.log(Function.toString)
requestAnimationFrame请求数据帧可以用做动画执行;var s = 0
function f() {
s++
console.log(s);
if (s < 999) {
window.requestAnimationFrame(f)
}
}
window.requestAnimationFrame(f)
var Single = (function () {
var instance = null
function Single(name) {
this.name = name
}
return function (name) {
if (!instance) {
instance = new Single(name)
}
return instance
}
})()
var oA = new Single('hi')
var oB = new Single('hello')
console.log(oA);
console.log(oB);
console.log(oB === oA);
function Animal(o) {
var instance = new Object()
instance.name = o.name
instance.age = o.age
instance.getAnimal = function () {
return "name:" + instance.name + " age:" + instance.age
}
return instance
}
var cat = Animal({ name: "cat", age: 3 })
console.log(cat);
class Watcher {
// name 模拟使用属性的地方
constructor(name, cb) {
this.name = name
this.cb = cb
}
update() {//更新
console.log(this.name + "更新了");
this.cb() //做出更新回调
}
}
class Dep {//依赖收集器
constructor() {
this.subs = []
}
addSubs(watcher) {
this.subs.push(watcher)
}
notify() {//通知每一个观察者做出更新
this.subs.forEach(w => {
w.update()
});
}
}
// 假如现在用到 age 的有三个地方
var w1 = new Watcher("我{{age}}了", () => { console.log("更新 age"); })
var w2 = new Watcher("v-model:age", () => { console.log("更新 age"); })
var w3 = new Watcher("I am {{age}} years old", () => { console.log("更新 age"); })
var dep = new Dep()
dep.addSubs(w1)
dep.addSubs(w2)
dep.addSubs(w3)
// 在 Object.defineProperty 中的 set 中运行
dep.notify()
setInterval,加载 10000 个节点);with;requestAnimationFrame的使用;defer和async;数据劫持: vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调
vue 是采用数据劫持配合发布者-订阅者的模式的方式,通过Object.defineProperty()来劫持各个属性的 getter 和 setter,在数据变动时,发布消息给依赖收集器(dep 中的 subs),去通知(notify)观察者,做出对应的回调函数,去更新视图。
MVVM 作为绑定的入口,整合 Observer,Compile 和 Watcher 三者,通过 Observer 来监听 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer,Compile 之间的通信桥路,达到数据变化=>视图更新;视图交互变化=>数据 model 变更的双向绑定效果。
杂乱笔记
const dep = new Dep()
// 劫持并监听所有属性
Object.defineProperty(obj, key, {
enumerable: true,
configurable: false,
get() {
// 订阅数据变化时,在 Dep 中添加观察者
Dep.target && dep.addSub(Dep.target)
return value
},
set: (newVal) => {
if (newVal !== value) {
this.observe(newVal)
value = newVal
}
// 告诉 Dep 通知变化
dep.notify()
},
})
beforeCreate
Created
beforeMount
mounted
vm实例中已经添加完$el了,已经替换掉那些 DOM 元素了(双括号中的变量),这个时候可以操作 DOM 了(但是是获取不了元素的高度等属性的,如果想要获取,需要使用nextTick())beforeUpdate
data改变后,对应的组件重新渲染之前updated
data改变后,对应的组件重新渲染完成beforeDestory
destoryed
beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mountedbeforeUpdate->子beforeUpdate->子updated->父updatedbeforeUpdate->父updatedbeforeDestroy->子beforeDestroy->子destroyed->父destroyednextTick
解释:
nextTick:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。应用:
created()操作 DOM 可以使用Vue.nextTick()回调函数nextTickcomputed的值会有缓存v-if 和v-show;Object.freeze()方式冻结 data 中的属性,从而阻止数据劫持;addEventListener,所以当一个组件销毁的时候需要手动去removeEventListener;diff 算法是指对新旧虚拟节点进行对比,并返回一个 patch 对象,用来存储两个节点不同的地方,最后利用 patch 记录的消息局部更新 DOM。
缺点:
优点:
key 主要用在虚拟 Dom 算法中,每个虚拟节点 VNode 有一个唯一标识 Key,通过对比新旧节点的 key 来判断节点是否改变,用 key 就可以大大提高渲染效率,这个 key 类似于缓存中的 etag。
v-bind:/:
v-on/@
Vue.prototype.Event = new Vue();
Event.$emit(事件名, 数据);
Event.$on(事件名, data => { });
...mapState(['方法名','方法名'])...mapGetters(['方法名','方法名'])v-bind:/:传值,子组件通过this.$attrs获取
this.$attrs获取到的是一个对象(所有父组件传过来的集合)// 入口文件
entry: "./src/index.js",
output: {
// 输出文件名称
filename: "bundle.js",
// 输出的路径(绝对路径)
path: path.resolve(__dirname, "dist") //利用 node 模块的 path 绝对路径
},
// 设置模式
mode: "development"
module:{
// 对某种格式的文件进行转换处理(转换规则)
rules:[
{
// 用到正则表达式
test:/.css$/, //后缀名为 css 格式的文件
use:[
// use 数组解析顺序是从下到上逆序执行的
// 先用 css-loader 再用 style-loader
// 将 js 的样式内容插入到 style 标签里
"style-loader",
// 将 css 文件转换为 js
"css-loader"
]
}
]
}
// -----vue 的
module.exports={
module:{
rules:[
{
test: /.vue$/,
use:["vue-loader"]
}
]
}
}
const uglifyJsPlugin = reqiure('uglifyjs-webpack-plugin')
module.exports={
plugin:[
new uglifyJsPlugin() //丑化
]
}
devServer:{
// 项目构建路径
contentBase:path.resolve(__dirname,"dist"),
// 启动 gzip 亚索
compress:true,
// 设置端口号
port:2020,
// 自动打开浏览器:否
open:false,
//页面实时刷新(实时监听)
inline:true
}
module.exports= {
resolve:{
//如果导入的时候不想写后缀名可以在 resolve 中定义 extensions
extensions:['.js','.css','.vue']
//alias:别名
alias:{
//导入以 vue 结尾的文件时,会去寻找 vue.esm.js 文件
'vue$':"vue/dist/vue.esm.js"
}
}
}
babel-loader,在 module(loader)中配置相关阅读:
doctype
<script src="d.js" defer></script> <script src="e.js" defer></script>
DOMContentLoaded事件之前执行对应d.js、e.js<script src="b.js" async></script> <script src="c.js" async></script>
DOMContentLoaded事件
load事件之前。额外标签clear:both:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.fahter{
width: 400px;
border: 1px solid deeppink;
}
.big{
width: 200px;
height: 200px;
background: darkorange;
float: left;
}
.small{
width: 120px;
height: 120px;
background: darkmagenta;
float: left;
}
.clear{
clear:both;
}
</style>
</head>
<body>
<div >
<div >big</div>
<div >small</div>
<div >额外标签法</div>
</div>
</body>
利用 BFC:overflow:hidden
.fahter{
width: 400px;
border: 1px solid deeppink;
overflow: hidden;
}
使用 after(推荐)
<style>
.clearfix:after{/*伪元素是行内元素 正常浏览器清除浮动方法*/
content: "";
display: block;
height: 0;
clear:both;
visibility: hidden;
}
.clearfix{
*zoom: 1;/*ie6 清除浮动的方式 *号只有 IE6-IE7 执行,其他浏览器不执行*/
}
</style>
<body>
<div >
<div >big</div>
<div >small</div>
</div>
</body>
// 冒泡排序
/* 1.比较相邻的两个元素,如果前一个比后一个大,则交换位置。
2.第一轮的时候最后一个元素应该是最大的一个。
3.按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。 */
function bubbleSort(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length; j++) {
if (arr[j] > arr[j + 1]) {
var temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
}
var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
bubbleSort(Arr)
console.log(Arr, "after");
/*
快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。
然后递归调用,在两边都实行快速排序。
*/
function quickSort(arr) {
if (arr.length <= 1) {
return arr
}
var middle = Math.floor(arr.length / 2)
var middleData = arr.splice(middle, 1)[0]
var left = []
var right = []
for (var i = 0; i < arr.length; i++) {
if (arr[i] < middleData) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat([middleData], quickSort(right))
}
var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
var newArr = quickSort(Arr)
console.log(newArr, "after");
function insertSort(arr) {
// 默认第一个排好序了
for (var i = 1; i < arr.length; i++) {
// 如果后面的小于前面的直接把后面的插到前边正确的位置
if (arr[i] < arr[i - 1]) {
var el = arr[i]
arr[i] = arr[i - 1]
var j = i - 1
while (j >= 0 && arr[j] > el) {
arr[j + 1] = arr[j]
j--
}
arr[j + 1] = el
}
}
}
var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
insertSort(Arr)
console.log(Arr, "after");
function isHuiWen(str) {
return str == str.split("").reverse().join("")
}
console.log(isHuiWen("mnm"));
function thousand(num) {
return (num + "").replace(/d(?=(d{3})+$)/g, "$&,")
}
console.log(thousand(123456789));
// num1 前一项
// num2 当前项
function fb(n, num1 = 1, num2 = 1) {
if (n == 0) return 0
if (n <= 2) {
return num2
} else {
return fb(n - 1, num2, num1 + num2)
}
}
var arr = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
// 最 low1
let newArr2 = []
for (let i = 0; i < arr.length; i++) {
if (!newArr2.includes(arr[i])) {
newArr2.push(arr[i])
}
}
console.log(newArr2);
// 最 low2
let arr2 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
for (let i = 0; i < arr2.length; i++) {
var item = arr2[i]
for (let j = i + 1; j < arr2.length; j++) {
var compare = arr2[j];
if (compare === item) {
arr2.splice(j, 1)
j--
}
}
}
console.log(arr2);
// 基于对象去重
let arr3 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
let obj = {}
for (let i = 0; i < arr3.length; i++) {
let item = arr3[i]
if (obj[item]) {
arr3[i] = arr3[arr3.length - 1]
arr3.length--
i-- continue;
}
obj[item] = item
}
console.log(arr3);
console.log(obj);
// 利用 Set
let newArr1 = new Set(arr)
console.log([...newArr1]);
let arr4 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
//利用 reduce
newArr4 = arr4.reduce((prev, curr) => prev.includes(curr)? prev : [...prev,curr],[])
console.log(newArr4);
console.log(document);

更多 建站教程 请访问 https://www.mimiwuqi.com/webqianduan/