js相关
apply call bind 使用场景,区别
- call/apply改变了函数的this上下文后马上执行该函数,bind不会立马调用
apply是第2个参数,这个参数是一个数组:传给fun参数都写在数组中。call从第2~n的参数都是传给fun的。- call/apply 返回
fun的执行结果 - bind返回fun的拷贝,并指定了fun的this指向,保存了fun的参数。 call/apply/bind的核心理念:借用方法。
借助已实现的方法,改变方法中数据的this指向,减少重复代码,节省内存。
function Cat(name){
this.name = name
this.sayHi = function(age, favor){
console.log("this.name + "," + age +"," + favor)
}
}
function Mouse(name){
this.name = name
}
var cat = new Cat("Tom")
cat.sayHi(12, "fish") // Tom, 12 , fish
var mouse = new Mouse("Jerry", 8, "cheese")
mouse.sayHI(10, "cheese") // Uncaught TypeError: mouse.sayHI is not a function
cat.sayHi.apply(mouse, [10, "cheese"]) // Jerry,10 , cheese
cat.sayHi.call(mouse, 10, "cheese") // Jerry,10 ,cheese
cat.sayHi.bind(mouse, 10, "cheese")() // Jerry,10 ,cheese
常用加密方法
一、MD5加密
介绍: MD5中文含义为信息-摘要算法5,就是一种信息摘要加密算法,可以将数据转译为另一固定长度值
特点:
1.压缩性:任意长度的数据,算出的MD5值长度都是固定的。
2.容易计算:从原数据计算出MD5值很容易。
3.抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4.强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
使用:下包后引入
一、 sha.js
介绍: 项目中经常会用的方式,使用起来简单方便
使用方式: 项目中直接引入sha.js包
使用:下包后引入
二、base64加密
介绍:base64是一种加密算法,有着广泛的应用和支持,但却是当今最弱的编码标准之一。它主要是对明文转换后的二进制序列做处理,使之变为不能被人直接识别的形式。
特点:
1.使用最广泛
2.简单易上手
3.可以将图片转译存储
4.编码之后的结果,只有64个字符 az AZ 0~9 / + 再加上一个辅助字符 =
使用方式:直接调用btoa方法转换成base64方法,使用atob方法可解码
function Base64() {
return (
<>
<h3>MD5加密</h3>
<br></br>
{/* btoa用于加密,atob用于解密 */}
<h3>加密前:18888888888 加密后:{window.btoa(18888888888)}</h3>
<h3>解码后:{window.atob(window.btoa(18888888888))}</h3>
</>
)
}
export default Base64
节流和防抖的实现
防抖 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
生活中的实例: 如果有人进电梯(触发事件),那电梯将在10秒钟后出发(执行事件监听器),这时如果又有人进电梯了(在10秒内再次触发该事件),我们又得等10秒再出发(重新计时)。
节流 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。
设置一个定时器,多少秒后触发,如果在定时时间内又点了,则清除之前定时器,重新计时
生活中的实例: 我们知道目前的一种说法是当 1 秒内连续播放 24 张以上的图片时,在人眼的视觉中就会形成一个连贯的动画,所以在电影的播放(以前是,现在不知道)中基本是以每秒 24 张的速度播放的,为什么不 100 张或更多是因为 24 张就可以满足人类视觉需求的时候,100 张就会显得很浪费资源。
设置一个时间间隔,记录开始时间和触发时间,判断时间差是否到大于时间间隔
方法和函数
函数是带有名称(named)和参数的JavaScript代码段,可以一次定义多次调用,function 方法是将函数和对象合写在一起时,函数就变成了 “方法”(method)// 当函数赋值给对象的属性,我们称为"方法"
var obj = {
name : '张三',
age : 18
fun : function(){
console.log(this.name) //这里的this指向的是obj这个对象
}//fun就成了该对象的一个方法
}
或者这样
var abc = function(){
......
}
函数和方法本质上是一样的,只不过方法是函数的特例,是将函数值赋给了对象
方法也是函数,只是比较特殊
常用高阶函数和循环
map
map()返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。map()不会对空数组进行检测。map()如果改变的基本类型和整个对象的话,无法改变原数组,但是改变的是对象某个值的话,可以改变原数组
let newArr = arr.map((item) => { return item * 2; });
filter
filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。filter()不会对空数组进行检测。filter()不会改变原始数组。
let newArr = arr.filter((item) => { return item < 100; });
filter妙用
.filter(item => item) 可以去除数组里面的null,'',undefined
var arr = ['1', '2', undefined, '3.jpg', undefined, '', null]
var newArr = arr.filter(item => item)
console.log(newArr)
// ['1', '2', '3.jpg']
foreach
forEach()方法类似于map(),传入的函数不需要返回值,并将元素传递给回调函数。forEach()不会返回新的数组,总是返回undefined.- 如果改变的基本类型和整个对象的话,无法改变原数组,但是改变的是对象某个值的话,可以改变原数组 如果改变的基本类型和整个对象的话,无法改变原数组,但是改变的是对象某个值的话,可以改变原数组
sort
sort()方法用于对数组的元素进行排序。sort()会修改原数组。
sort() 方法接受一个可选参数,用来规定排序顺序,必须是函数。
sort() 方法接受一个可选参数,用来规定排序顺序,必须是函数。
如果没有传递参数, sort() 方法默认把所有元素先转换为 String 再排序 ,根据 ASCII 码进行排序。
- 若
a小于b,在排序后的数组中a应该出现在b之前,则返回一个小于0的值。 - 若
a等于b,则返回0。 - 若
a大于b,则返回一个大于0的值。
x参数代表的是数组里面第一索引位元素,y参数代表与x相邻的后一位元素,当x>y时,返回1,则数组会按升序(从小到大的顺序)进行排序,返回-1,则按降序进行排序
some
-
some()方法用于检测数组中的元素是否满足指定条件。 -
some()方法会依次执行数组的每个元素。 -
如果有一个元素满足条件,则表达式返回
true, 剩余的元素不会再执行检测。 -
如果没有满足条件的元素,则返回
false。 -
some()不会对空数组进行检测。 -
some()不会改变原始数组。
let arr = [10, 20, 1, 2];
let result = arr.some((item) => { return item > 10; });
console.log(result);//true
every
跟some差不多,不同的是,判断全部,some是判断一个元素满足就行
reduce
reduce()方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let sum = arr.reduce((prev, current) => {
return prev + current;
}, 0);
console.log(sum); //55
reduceRight跟reduce是反的从末尾向前
find
find()方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined。find()不会对空数组进行检测。find()不会改变原始数组。
let arr = [11, 20, 51, 82];
let result = arr.find((item) => { return item > 50; }, 0);
console.log(result);//51
findIndex
跟find一样,查到第一个满足条件的,没有返回-1
for of循环
for...of 可以迭代数组、类数组以及任何可以迭代的对象(maps、sets、DOM集合),并且,for...of 的语句还很短。
const products = ['oranges', 'apples'];
for (const product of products) {
console.log(product);
}
// 'oranges'
// 'apples'
for...of 循环遍历 products 的每一项。迭代项被赋值给变量 product.
class(类)和构造函数
JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
class可以看成一个语法糖
class Point {
constructor (x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
上面代码定义了一个“类”,可以看到里面有一个constructor()方法,这就是构造方法,而this关键字则代表实例对象,类的数据类型就是函数,类本身就指向构造函数。toString方法是定义到Point.prototype上的,
constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。
类的所有实例共享一个原型对象。就是说不管new了多少次,protoType指的都是一个对象
用new操作符调用函数、构造函数、类和实例
一、用new操作符调用函数 JS规定,使用new操作符调用函数会进行“四步走”
1)函数体内会自动创建出一个空白对象
2)函数的上下文(this)会指向这个对象
3)函数体内的语句会执行
4)函数会自动返回上下文对象,即使函数没有return语句
二、举例
第1步:函数体内会自动创建出一个空白对象
第2步:函数的上下文(this)会指向这个对象
function fun() {
//在函数体开头,自动创建了一个空白对象{}
//this指向空白对象
this.a = 3;
this.b = 5;
}
var obj = new fun();
console.log(obj);
第3步:执行函数体中的语句
this.a = 3;
this.b = 5;
//由以上this是{},即 最后语句为{a: 3, b: 5}
第4步:函数会自动返回上下文对象,即使函数没有return语句
最后结果:{a: 3, b: 5} 总结:上下文规则
三、构造函数 1.构造函数:在 JavaScript 中,用 new 关键字来调用的函数,称为构造函数。构造函数首字母一般大写。
function People(name, age, sex) { //接收三个参数
this.name = name;
this.age = age;
this.sex = sex; //this上绑定同名属性
}
var xiaoming = new People('小明', 12,'男'); //传入三个参数
var xiaohong = new People('小红', 10,'女');
var xiaogang = new People('小刚', 13,'男');
(1)用new调用一个函数,这个函数就被称为“构造函数”,任何函数都可以是构造函数,只需要用new调用它
(2)构造函数用来“构造新对象”,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化
(3)构造函数必须用new关键字调用,否则不能正常工作,正因如此,开发者约定构造函数命名时首字母要大写
(4)一个函数是不是构造函数,要看它是否用new调用,而至于名称首字母大写,完全是开发者的习惯约定
(5)构造函数中的this不是函数本身,this是创建的新对象,将来会成为小明、小红、小刚等 四、类和实例:实例是具体的对象
generator函数
generator函数跟普通函数在写法上的区别就是,多了一个星号*,并且只有在generator函数中才能使用yield,什么是yield呢,他相当于generator函数执行的中途暂停点,比如下方有3个暂停点。而怎么才能暂停后继续走呢?那就得使用到next方法,next方法执行后会返回一个对象,对象中有value 和 done两个属性
- value:暂停点后面接的值,也就是yield后面接的值
- done:是否generator函数已走完,没走完为false,走完为true
function fn (num) {
console.log(num)
return num
}
function* gen () {
yield fn(1)
yield fn(2)
return 3
}
const g = gen()
console.log(g.next())
// 1
// { value: 1, done: false }
console.log(g.next())
// 2
// { value: 2, done: false }
console.log(g.next())
// { value: 3, done: true }
next传参
generator函数可以用next方法来传参,并且可以通过yield来接收这个参数,注意两点
- 第一次next传参是没用的,只有从第二次开始next传参才有用
- next传值时,要记住顺序是,先右边yield,后左边接收参数
fn (nums) {
return new Promise(resolve => {
setTimeout(() => {
resolve(nums * 2)
}, 1000)
})
},
* gen () {
const num1 = yield this.fn(1)
const num2 = yield this.fn(num1)
const num3 = yield this.fn(num2)
return num3
}
const g = this.gen()
const next1 = g.next()
// 这里的next1.value.then取的是 yield this.fn(1)的promise数据,然后调了 promise的then
next1.value.then(res1 => {
console.log(next1) // 1秒后同时输出 { value: Promise { 2 }, done: false }
console.log(res1) // 1秒后同时输出 2
const next2 = g.next(res1) // 传入上次的res1
next2.value.then(res2 => {
console.log(next2) // 2秒后同时输出 { value: Promise { 4 }, done: false }
console.log(res2) // 2秒后同时输出 4
const next3 = g.next(res2) // 传入上次的res2
next3.value.then(res3 => {
console.log(next3) // 3秒后同时输出 { value: Promise { 8 }, done: false }
console.log(res3) // 3秒后同时输出 8
// 传入上次的res3
console.log(g.next(res3)) // 3秒后同时输出 { value: 8, done: true }
})
})
})
宏任务和微任务
js分为同步任务和异步任务,而异步任务分为2种,宏任务和微任务,
- 常见的宏任务有:
- 宏任务:setTimeout,setInterval,Ajax,DOM事件
- 微任务:Promise.then,async/await
因为Promise定义之后便会立即执行,其后的.then()是异步里面的微任务。
而setTimeout()是异步的宏任务。
微任务要比宏任务执行的要早
微任务 》 DOM渲染 》 宏任务
setTimeout的回调不一定在指定时间后能执行。而是在指定时间后,将回调函数放入事件循环的队列中。
如果时间到了,JS引擎还在执行同步任务,这个回调函数需要等待;如果当前事件循环的队列里还有其他回调,需要等其他回调执行完。
另外,setTimeout 0ms 也不是立刻执行,它有一个默认最小时间,为4ms。
// node
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})
因为取出第一个宏任务之前在执行全局Script,如果这个时间大于 4ms,这时 setTimeout 的回调函数已经放入队列,就先执行 setTimeout;如果准备时间小于 4ms,就会先执行 setImmediate。
先执行宏任务再执行当前宏任务内的微任务(在此过程中将遇到的微宏任务依次推入到eventqueue,
先进入eventqueue的任务会被先执行),执行完当前宏任务内的所有微任务之后,再执行下一个宏任务,
以此循环,当然在微宏任务中同步任务会优先执行,而整体的scrpt标签就是第一个执行的宏任务
setImmediate
该方法用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数,
setImmediate(() => {
this.$set(this.$refs.formBox.formData[4], 'disabledDate', this.disableDate)
this.$set(this.$refs.formBox.formData[1], 'disabledDate', this.disableDate1)
})
promise原理
promise
Promise的初始状态是pending
-
1、执行了
resolve,Promise状态会变成fulfilled -
2、执行了
reject,Promise状态会变成rejected -
3、Promise只以
第一次为准,第一次成功就永久为fulfilled,第一次失败就永远状态为rejected -
4、Promise中有
throw的话,就相当于执行了reject
class MyPromise {
// eslint-disable-next-line space-before-function-paren
constructor(executor) {
// executor是个方法
// 初始化值
this.initValue()
// 初始化this指向
this.initBind()
try {
// 执行传进来的函数,而this.resolve, this.reject这两个函数作为了传参
executor(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
initBind () {
// 初始化this
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
}
initValue () {
// 初始化值
this.PromiseResult = null // 初始化值
this.PromiseState = 'pending' // 状态
// then中传的参,可能多次调用then,所以用数据存
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
}
resolve
resolve (value) {
// state是不可变的
if (this.PromiseState !== 'pending') return
// 如果执行resolve,状态变为fulfilled
this.PromiseState = 'fulfilled'
// 终值为传进来的值
this.PromiseResult = value
// 执行保存的成功回调
// onFulfilledCallbacks是各个函数组成的数组,所以shift()把数组第一个元素删除并返回,则是把数组的第一个函数删除返回了
// (this.PromiseResult)则是传给函数的参
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(this.PromiseResult)
}
}
reject
// then接收两个回调,一个是成功回调,一个是失败回调
// 当Promise状态为fulfilled执行成功回调,为rejected执行失败回调
// 如resolve或reject在定时器里,则定时器结束后再执行then
// then支持链式调用,下一次then执行受上一次then返回值的影响
reject (reason) {
// state是不可变的
if (this.PromiseState !== 'pending') return
// 如果执行reject,状态变为rejected
this.PromiseState = 'rejected'
// 终值为传进来的reason
this.PromiseResult = reason
// 执行保存的失败回调
// onFulfilledCallbacks是各个函数组成的数组,所以shift()把数组第一个元素删除并返回,则是把数组的第一个函数删除返回了
// (this.PromiseResult)则是传给函数的参
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(this.PromiseResult)
}
}
then
-
then接收两个回调,一个是
成功回调,一个是失败回调 -
当Promise状态为
fulfilled执行成功回调,为rejected执行失败回调 -
如resolve或reject在定时器里,
则定时器结束后再执行then -
then支持
链式调用,下一次then执行受上一次then返回值的影响
then中加定时器的情况
也就是在这定时器时间内,我们可以先把then里的两个回调保存起来,然后等到定时器时间过后,执行了resolve或者reject,咱们再去判断状态,并且判断要去执行刚刚保存的两个回调中的哪一个回调。我们怎么知道当前时间还没走完甚至还没开始走呢?其实很好判断,只要状态是pending,那就证明定时器还没跑完,因为如果定时器跑完的话,那状态肯定就不是pending,而是fulfilled或者rejected
那是用什么来保存这些回调呢?建议使用数组,因为一个promise实例可能会多次then,用数组就一个一个保存了
then (onFulfilled, onRejected) {
// 参数校验,确保一定是函数,如果不是函数,onFulfilled会变成val => {return val },onRejected会变成reason => { throw reason }抛错
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
// new MyPromise的话,里面的语句会立即执行
var thenPromise = new MyPromise((resolve, reject) => {
// 这里是定义的一个方法,cb是传参
const resolvePromise = cb => {
try {
const x = cb(this.PromiseResult)
if (x === thenPromise) {
// 不能返回自身哦
throw new Error('不能返回自身。。。')
}
if (x instanceof MyPromise) {
// 如果返回值是Promise
// 如果返回值是promise对象,返回值为成功,新promise就是成功
// 谁知道返回的promise是失败成功?只有then知道
x.then(resolve, reject)
} else {
// 非Promise就直接成功
resolve(x)
}
} catch (err) {
// 处理报错
reject(err)
throw new Error(err)
}
}
// 这里的状态是继承的上一次resolve或者reject的状态
if (this.PromiseState === 'fulfilled') {
// 如果onFulfilled或者onRejected返回的是自己,那上面的cb(this.PromiseResult)
// 如果当前为成功状态,执行第一个回调,this.PromiseResult为resolve或者reject执行完的值
resolvePromise(onFulfilled)
} else if (this.PromiseState === 'rejected') {
// 如果当前为失败状态,执行第二哥回调
resolvePromise(onRejected)
// 因为定时器这时候还没执行resolve,或者reject,所以状态为pending
} else if (this.PromiseState === 'pending') {
// promise定时器的处理
// 如果为待定则同时保存两个回调,这里的回调是then传进来的
this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled))
this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected))
}
})
// 返回这个包装的Promise
return thenPromise
}
all
// 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
// 如果所有Promise都成功,则返回成功结果数组
// 如果有一个Promise失败,则返回这个失败结果
static all (promises) {
// 接受成功数组
const result = []
let count = 0
// 返回一整个新promise
return new MyPromise((resolve, reject) => {
// 定义一个函数
const addData = (index, value) => {
// 把传入的参,加到成功数组里面
result[index] = value
count++
// 判断传入的promise数组是否都传入了,都传了就返回成功结果数组
if (count === promises.length) resolve(result)
}
// 对传进来的值进行循环
promises.forEach((promise, index) => {
// 判断是否为promist,如果不是直接当成成功项
if (promise instanceof MyPromise) {
promise.then(res => {
addData(index, res)
}, err => reject(err))
} else {
addData(index, promise)
}
})
})
}
race
// 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
// 哪个Promise最快得到结果,就返回那个结果,无论成功失败
static race (promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
if (promise instanceof MyPromise) {
promise.then(res => {
resolve(res)
}, err => {
reject(err)
})
} else {
resolve(promise)
}
})
})
}
any
// 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
// 如果有一个Promise成功,则返回这个成功结果
// 如果所有Promise都失败,则报错
static any (promises) {
return new Promise((resolve, reject) => {
let count = 0
promises.forEach((promise) => {
promise.then(val => {
resolve(val)
// eslint-disable-next-line handle-callback-err
}, err => {
count++
if (count === promises.length) {
reject(new Error('All promises were rejected'))
}
})
})
})
}
allSettled
// 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
// 把每一个Promise的结果,集合成数组,返回
static allSettled (promises) {
return new Promise((resolve, reject) => {
const res = []
let count = 0
const addData = (status, value, i) => {
res[i] = {
status,
value
}
count++
if (count === promises.length) {
resolve(res)
}
}
promises.forEach((promise, i) => {
if (promise instanceof MyPromise) {
promise.then(res => {
addData('fulfilled', res, i)
}, err => {
addData('rejected', err, i)
})
} else {
addData('fulfilled', promise, i)
}
})
})
}
}
超时报错
就是给定一个时间,如果接口请求超过这个时间的话就报错
/**
* 模拟延时
* @param {number} delay 延迟时间
* @returns {Promise<any>}
*/
function sleep (delay) {
// eslint-disable-next-line promise/param-names
return new Promise((_, reject) => {
// eslint-disable-next-line prefer-promise-reject-errors
setTimeout(() => reject('超时喽'), delay)
})
}
/**
* 模拟请求
*/
function request () {
// 假设请求需要 1s
return new Promise(resolve => {
setTimeout(() => resolve('成功喽'), 1000)
})
}
function timeoutPromise (requestFn, delay) {
// 如果先返回的是延迟Promise则说明超时了
return Promise.race([requestFn(), sleep(delay)])
}
timeoutPromise(request, 500)
并发控制
同时只执行几个接口请求,等前面的接口执行完毕再执行后面的接口
class Scheduler {
constructor(limit) {
// 所有请求接口保存数组
this.queue = []
// 同时执行个数
this.limit = limit
this.count = 0
}
add (time, order) {
const promiseCreator = () => {
return new Promise((resolve, reject) => {
// 因为是模拟的接口,所以加了定时器,正常是接口请求成功后resolve()
setTimeout(() => {
console.log(order)
resolve()
}, time)
})
}
this.queue.push(promiseCreator)
}
taskStart () {
// 能同时执行多少个函数,就同时执行多少个递归
for (let i = 0; i < this.limit; i++) {
this.request()
}
}
// 递归函数
request () {
if (!this.queue.length || this.count >= this.limit) return
// count是正在执行接口数量,用来限制一次只发2个接口请求的
this.count++
// shift()()把数组第一条数组摘出,并且执行
this.queue.shift()().then(() => {
// 执行完毕后在执行接口数量-1,并且回调自己
this.count--
this.request()
})
}
}
const scheduler = new Scheduler(2)
const addTask = (time, order) => {
scheduler.add(time, order)
}
addTask(100, '1')
addTask(100, '2')
addTask(300, '3')
addTask(400, '4')
scheduler.taskStart()
await async
async/await的用处就是:用同步方式,执行异步操作
async async函数返回的是一个Promise对象,但是值却是undefined,那要怎么才能使值不是undefined呢?很简单,函数有return返回值就行了,就有值了,并且还能使用then方法进行输出
原理也是利用的generator的next传参
function generatorToAsync (generatorFn) {
return function () {
const gen = generatorFn.apply(this, arguments) // gen有可能传参
// 返回一个Promise
return new Promise((resolve, reject) => {
function go (key, arg) {
let res
try {
res = gen[key](arg) // 这里有可能会执行返回reject状态的Promise
} catch (error) {
return reject(error) // 报错的话会走catch,直接reject
}
// 解构获得value和done
const { value, done } = res
if (done) {
// 如果done为true,说明走完了,进行resolve(value)
return resolve(value)
} else {
// 如果done为false,说明没走完,还得继续走
// value有可能是:常量,Promise,Promise有可能是成功或者失败
return Promise.resolve(value).then(val => go('next', val),err => go('throw', err))
}
}
go('next') // 第一次执行
})
}
}
const asyncFn = generatorToAsync(gen)
asyncFn().then(res => console.log(res))
function* gen () {
const num1 = yield fn(1)
console.log(num1) // 2
const num2 = yield fn(num1)
console.log(num2) // 4
const num3 = yield fn(num2)
console.log(num3) // 8
return num3
}
const genToAsync = generatorToAsync(gen)
const asyncRes = genToAsync()
console.log(asyncRes) // Promise
asyncRes.then(res => console.log(res)) // 8
generator其实就是JS在语法层面对协程的支持,真正支持与否看运行时环境,比如高版本的node就是支持的。协程就是主程序和子协程直接控制权的切换,并伴随通信的过程,那么,从generator语法的角度来讲,yield,next就是通信接口,next是主协程向子协程通信,而yield就是子协程向主协程通信
原型,原型链
1、引用类型,都具有对象特性,即可自由扩展属性。
2、引用类型,都有一个隐式原型 __proto__ 属性,属性值是一个普通的对象。
3、引用类型,隐式原型 __proto__ 的属性值指向它的构造函数的显式原型 prototype 属性值。
4、当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型 __proto__(也就是它的构造函数的显式原型 prototype)中寻找。
首先, obj 对象并没有 toString 属性,之所以能获取到 toString 属性,是遵循了第四条规则,从它的构造函数 Object 的 prototype 里去获取。
最后一个 null,设计上是为了避免死循环而设置的, Object.prototype 的隐式原型指向 null。
原型链就是一个过程,原型是原型链这个过程中的一个单位,贯穿整个原型链
原型链好比继承+迭代,他可以让一个对象,继承多种多样的属性,方法。
而__proto__和prototype,就好比人的dna。__proto__指向父母,prototype代表父母自己。
两者的区别就在于此。
数据判断区别
一共4个判断类型的
typeof
缺点typeof不能将Object、Array和Null区分,都返回object
instanceof可以正确判断对象的类型,
其内部运行机制是判断在其原型链中能否找到该类型的原型。
缺点instanceof, Number,Boolean,String基本数据类型不能判断
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
constructor
缺点 需要注意,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了:
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
constructor有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数。
function Fn(){};
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor===Fn); // false
console.log(f.constructor===Array); // true
(4)Object.prototype.toString.call()
Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型:
var a = Object.prototype.toString;
console.log(a.call(2)); // [object Number]
console.log(a.call(true)); // [object Boolean]
console.log(a.call('str')); // [object String]
console.log(a.call([])); // [object Array]
console.log(a.call(function(){})); // [object Function]
console.log(a.call({})); // [object Object]
console.log(a.call(undefined)); // [object Object]
console.log(a.call(null)); // [object Object]
垃圾回收机制
程序工作过程中会产生很多
垃圾,这些垃圾是程序不用的内存或者是之前用过了,以后不会再用的内存空间,而GC就是负责回收垃圾的,可达性,那些以某种方式可访问,或者说可用的值,被保存在内存中,反之不可访问,则需要回收, 垃圾清除方法1、标记清除,把所有对象标记成0,然后从根对象开始遍历,把不是垃圾的节点改成1,清理所有标记为0的垃圾,销毁并回收他们所占用的内存空间,然后把内存中对象标记修改为0,等待下一轮垃圾回收,缺点是会有内存碎片
新建内容需要找到合适的内存大小放进去,就需要额外的计算,而新的标记整理算法就可以有效的解决,会把活着的对象,向内存一端移动,没有内存碎片
内存泄漏
内存泄漏,不再用到的对象没有被及时回收时,就是内存泄漏,内存泄漏一般有4种,闭包,DOM元素引用,定时器,全局变量
闭包
上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放。
复制功能
1、clipboard 插件
2、# Clipboard法 还有read,readText,wrtieText方法,详细:www.ruanyifeng.com/blog/2021/0…
navigator.clipboard.writeText('123123').then(() => {
this.$Message.success('复制成功')
})
作用域
作用域的定义:作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。
内层作用域可以访问外层作用域,但是外层作用域不能访问内层
块级作用域 --函数声明:function 函数名(){}
--函数表达式: var 函数名=function(){}
--自执行函数: (function 函数名(){} )();自执行函数前面的语句必须有分号,通常用于隐藏作用域。 (1.独立作用域 2.运行后即销毁)
function foo (sex) {
console.log(sex)
}
var f = function () {
console.log('hello')
}
var height = 180;
(
function fn () {
console.log(height)
}
)()
foo('female')
// 依次打印:
// 180
// female
// hello
作用域链
作用域链是从里到外找,不能从外到里找,如果是一个块级作用域,用var定义变量,会有变量提升需要注意,块级作用域内,函数的提升,比变量提升优先,let,const会先存在暂时死区里面,在全区作用域声明的变量,var会挂载到window对象上面,而let,const不会
线程与进程
一个进程由一个或者多个线程组成,进程相互独立,浏览器是多进程的,简单理解,比如浏览器每打开一个tab页,就相当于创建了一个独立的浏览器进程
判断对象上是否存在某个属性
一般用in 或者 hasOwnProperty ,in的缺点是如果属性在原型链中也会返回true,hasOwnProperty如果接到Object.create后面会报错,
Object.create(null).hasOwnProperty('name'),hasOwn则不会,
let object3 = Object.create(null)
Object.hasOwn(object3, 'age') // false
栈,堆内存
栈内存,先进后出,后进先出,基本类型保存在栈内存中,因为基本类型占用空间小,大小固定。引用类型在堆内存,堆内存存取随意,因为引用类型大小不确定,占据空间大。对象和数组的指针是放在栈内存的,指针的指向是指向堆内存的,要访问堆内存中的引用数据类型时,实际上我们首先是从变量中获取了该对象的地址指针, 然后再从堆内存中取得我们需要的数据
垃圾销毁的区别 栈内存当前环境执行完就会被垃圾回收销毁,而堆内存不会,堆内存只有等引用都结束才会销毁
JSON.stringify 的几个作用
如果第二个参数是数组,则只转换数组里的属性,如果是方法,则把每个属性都方法处理一下再返回,接受2个参数,key,value,可以在这里处理数据,比如过滤
第三个参数,空白缩进格
在对象中underfined,任意函数,symbol会被忽略,数组中会返回null,### NaN Infinity null会返回null,BigInt转换会报错,处理不了循环引用,
const obj = { name: 'zcxiaobao', age: 18, }
const loopObj = { obj }
// 形成循环引用 obj.loopObj = loopObj;
JSON.stringify(obj)
/* Uncaught TypeError: Converting circular structure to JSON --> starting at object with constructor 'Object' | property 'loopObj' -> object with constructor 'Object' --- property 'obj' closes the circle at JSON.stringify (<anonymous>) at <anonymous>:10:6 */
`
妙用,对象是否相等的判断,利用对象序列化为字符串,来进行简单判断
const obj = {
age: 13
} //{"age":13}
const obj1 = {
age: 13
} //{"age":13}
console.log(JSON.stringify(obj) === JSON.stringify(obj1)) // true
//判断数组是否包含某对象
const names = [ {name:'zcxiaobao'}, {name:'txtx'}, {name:'mymy'}, ];
const zcxiaobao = {name:'zcxiaobao'};
// true JSON.stringify(names).includes(JSON.stringify(zcxiaobao))
深拷贝浅拷贝
- 浅拷贝和深拷贝都复制了值和地址,都是为了解决引用类型赋值后互相影响的问题。
- 但是浅拷贝只进行一层复制,深层次的引用类型还是共享内存地址,原对象和拷贝对象还是会互相影响。
- 深拷贝就是无限层级拷贝,深拷贝后的原对象不会和拷贝对象互相影响。 浅拷贝方法 Object.assign,扩展运算符
浅拷贝
数组的 slice
const arr = ['lin', 'is', 'handsome']
const newArr = arr.slice(0)
arr[2] = 'rich' // 改变原来的数组
console.log(newArr) // ['lin', 'is', 'handsome']
console.log(arr == newArr) // false 两者指向不同地址
concat 方法
const arr = ['lin', 'is', 'handsome']
const newArr = [].concat(arr)
arr[2] = 'rich' // 改变原来的数组
console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变
console.log(arr == newArr) // false 两者指向不同地址
深拷贝
JSON.parse(JSON.stringify(obj)),会忽略undefined、symbol和函数:
NaN、Infinity、-Infinity 会被序列化为 null:而且还不能解决循环引用的问题
简单版本深拷贝
function clone(target) {
if (typeof target === 'object')
{
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
}
else {
return target;
} };
let const var的区别
在全局作用域,或者块级作用域,var都会变量提升到最顶端,let,const会先放入暂时死区中。let,const变量只在当前作用域有效,const必须要给初始值,声明的是常量,const不能修改指针,但是可以修改值
let,const也有变量的提升,不同的是,var提升之后的变量会被初始化成undefined,而let,const没有被初始化,形成了暂时性死区,在这个区间中对变量的访问都会报错。let在这个区间之外没有初始化则默认为undefined,而const则必须进行初始化,否则报错。