语法:call(Fn,obj,…args)
功能: 执行Fn,使this为obj,并将后面的参数传给fn,等同于函数对象的call方法
实现:
1.给obj添加临时方法Fn
2.调用Fn,传入参数,获取返回值
3.删除临时方法
其他说明
1.如果这个函数处于非严格模式下,指定null或undefined时会自定替换为指向全局对象
function call(Fn,obj,...arg) {
if(obj === undefined || obj ===null)obj=globalThis;
obj.tmp = Fn;
let result = obj.tmp(...arg);
delete obj.tmp;
return result;
}
与call的差别是,传入的数据参数用数组包起来了
function apply(Fn,obj,args) {
if(obj === undefined || obj ===null)obj=globalThis;
obj.tmp = Fn;
let result = obj.tmp(...args);
delete obj.tmp;
return result;
}
bind返回一个新函数,并不执行,其余与call函数一致
function bind(Fn,obj,...args) {
//返回新函数 args2调用返回函数时的传参
return (...args2) => {
//调用原来函数,参数列表由args和atgs2依次组成
return call(Fn,obj,...args,...args2)
}
}
目的:控制事件处理函数的执行频率
事件频繁触发可能造成的问题?
函数节流(throttle):控制事件执行的时间间隔
理解:
函数防抖(debounce)
触发事件后不会立即执行,需要等待wait时间,如果在等待的过程中再一次触发了事件,计时器重新开始计时wait时间,直到达到wait时间后执行最后一次的回调
理解:
//使用形式
window.addEventListener('scroll',throttle(()=>{},500))
/*
1.定义上一次时间,判断执行的时间和上一次时间的时间间隔
2.满足条件,执行回调函数
*/
function throttle(callback,wait){
let pre = 0;
//返回值是一个函数
return function(event){
//节流函数/真正的事件回调函数
const current = Date.now(); //当前时间
//事件触发后,节流函数也会被返回,只是满足一定的条件再调用
if (current - pre > wait) { //只有离上一次调用callback的时间差大于wait
//callback()是window调用的,所以callback函数里的this是window,这里要修改指向事件源
callback.call(this,event);
//记录此次调用的时间
pre = current;
}
}
}
//使用形式
window.addEventListener('scroll',debounce(()=>{},500))
function debounce(callback,time){
let timeId = null;
return function(event){//防抖一般用于事件绑定,所以需要接受event
if(timeId!== null){//说明已经开启了一个定时器,所以需要清空上一次的
clearTimeout(timeId);
}
/*
启动定时器,定时器执行后里面的回调是异步执行的
setTimeout返回值是立即返回的
*/
timeId = setTimeout(()=>{
console.log('执行时的timeId:'+timeId);
callback.call(this,event);
//执行成功之后,重置timeId,所以这里可以起作用
timeId = null;
},time)
}
}
/*
实现map
*/
function map(arr,callback) {
let newArr = [];
for(let i=0;i<arr.length;i++){
newArr.push(callback(arr[i],i));
}
return newArr;
}
/*
实现reduce
*/
function reduce(arr,callback,initValue) {
let result = initValue;//每次运行的结果,最开始是初始化
for(let i=0;i<arr.length;i++){
result=callback(result,arr[i],i);
}
return result;
}
/*
实现filter()
*/
function filter(array, callback) {
const arr = []
for (let index = 0; index < array.length; index++) {
if (callback(array[index], index)) {
arr.push(array[index])
}
}
return arr
}
/*
实现find()
*/
function find (array, callback) {
for (let index = 0; index < array.length; index++) {
if (callback(array[index], index)) {
return array[index]
}
}
return undefined
}
/*
实现findIndex()
*/
function findIndex (array, callback) {
for (let index = 0; index < array.length; index++) {
if (callback(array[index], index)) {
return index
}
}
return -1
}
/*
实现every()
*/
function every (array, callback) {
for (let index = 0; index < array.length; index++) {
if (!callback(array[index], index)) { // 只有一个结果为false, 直接返回false
return false
}
}
return true
}
/*
实现some()
*/
function some (array, callback) {
for (let index = 0; index < array.length; index++) {
if (callback(array[index], index)) { // 只有一个结果为true, 直接返回true
return true
}
}
return false
}
方法1: 利用forEach()和indexOf()
说明: 本质是双重遍历, 效率差些
方法2: 利用forEach() + 对象容器
说明: 只需一重遍历, 效率高些
方法3: 利用ES6语法: from + Set 或者 … + Set
说明: 编码简洁
/*
方法1: 利用forEach()和indexOf()
说明: 本质是双重遍历, 效率差些
*/
export function unique1 (array) {
const arr = []
array.forEach(item => {
if (arr.indexOf(item)===-1) {
arr.push(item)
}
})
return arr
}
/*
方法2: 利用forEach() + 对象容器
说明: 只需一重遍历, 效率高些
*/
export function unique2 (array) {
const arr = []
const obj = {}
array.forEach(item => {
if (!obj.hasOwnProperty(item)) {
obj[item] = true
arr.push(item)
}
})
return arr
}
/*
方法3: 利用ES6语法
1). from + Set
2). ... + Set
说明: 编码简洁
*/
export function unique3 (array) {
// return Array.from(new Set(array))
return [...new Set(array)]
}
concat(): 合并
语法: var new_array = concat(array, value1[, value2[, …[, valueN]]])
功能: 将n个数组或值与当前数组合并生成一个新数组, 原始数组不会被改变
slice(): 切片
语法: var new_array = slice(array, [begin[, end]])
功能: 返回一个由 begin 和 end 决定的原数组的浅拷贝, 原始数组不会被改变
/*
语法: var new_array = concat(old_array, value1[, value2[, ...[, valueN]]])
功能: 将n个数组或值与当前数组合并生成一个新数组
*/
function cancat(arr,...args) {
const result = [...arrr];
args.forEach(item=>{
if(Array.isArray(item))result.push(...item);
else result.push(item);
})
return result;
}
/*
语法: var new_array = slice(oldArr, [begin[, end]])
功能: 返回一个由 begin 和 end 决定的原数组的浅拷贝, 原始数组不会被改变
*/
function slice (array, begin, end) {
// 如果当前数组是[], 直接返回[]
if (array.length === 0) {
return []
}
// 如果begin超过最大下标, 直接返回[]
begin = begin || 0
if (begin >= array.length) {
return []
}
// 如果end不大于begin, 直接返回[]
end = end || array.length
if (end > array.length) {
end = array.length
}
if (end <= begin) {
return []
}
// 取出下标在[begin, end)区间的元素, 并保存到最终的数组中
const arr = []
for (let index = begin; index < end; index++) {
arr.push(array[index])
}
return arr
}
将多维数组转换为一维数组
核心就是有数组则继续遍历
function flatten(arr) {
while(arr.some(item=>Array.isArray(item))) {
arr = [].concat(...arr); //...arr展开后,元素直接连接,一维数组会展开之后连接
}
return arr;
}
function flatten(arr) {
let result = [];
arr.forEach(item=>{
//递归返回的是一个一维数组,所以使用concat进行连接,concat连接返回新数组
if(Array.isArray(item))result = result.concat(flatten1(item));
else result = result.concat(item);
})
return result;
}
语法:chunk(array,size)
功能:将数组拆分成多个size长度的区块,每个区块组成小数组,整体组成一个二维数组
如[1,3,5,6,7,8]调用chunk(arr,4)->[[1,3,5,6],[7,8]]
function chunk(array,size){
let result = [];
let tmp = [];
size = size||1;
if(array.length<=size ||size===1){
return array;
}
array.forEach((item) => {
//先压入,因为为满在压入,那么最后不足size的元素将不会入栈
if(tmp.length===0)result.push(tmp);
tmp.push(item);
if(tmp.length===size) tmp = [];
});
return result;
}
pull(array, …values):
删除原数组中与value相同的元素, 返回所有删除元素的数组
说明: 原数组发生了改变
如: pull([1,3,5,3,7], 2, 7, 3, 7) —> 原数组变为[1, 5], 返回值为[3,3,7]
pullAll(array, values):
功能与pull一致, 只是参数变为数组
如: pullAll([1,3,5,3,7], [2, 7, 3, 7]) —> 数组1变为[1, 5], 返回值为[3,3,7]
/**
* 删除数组元素
* @param {Array} arr
* @param {...any} args
*/
function pull(arr,...args) {
const result = [];
for(let index=0;index<arr.length;++index){
if(args.includes(arr[index])){
result.push(arr[index]);
arr.splice(index,1);
index--;//splice删除一般需要小标自减
}
}
return result;
}
function pullAll(arr,values) {
return pull(arr,...values);
}
drop(array, count)
得到当前数组过滤掉左边count个后剩余元素组成的数组
说明: 不改变当前数组, count默认是1
如: drop([1,3,5,7], 2) —> [5, 7]
dropRight(array, count)
得到当前数组过滤掉右边count个后剩余元素组成的数组
说明: 不改变当前数组, count默认是1
如: dropRight([1,3,5,7], 2) —> [1, 3]
/*
1. drop(array, count):
得到数组过滤掉左边count个后剩余元素组成的数组
说明: 不改变当前数组, count默认是1
如: drop([1,3,5,7], 2) ===> [5, 7]
2. dropRight(array, count):
得到数组过滤掉右边count个后剩余元素组成的数组
说明: 不改变数组, count默认是1
如: dropRight([1,3,5,7], 2) ===> [1, 3]
*/
export function drop (array, count=1) {
if (array.length === 0 || count >= array.length) {
return []
}
return array.filter((item, index) => index>=count)
}
export function dropRight (array, count=1) {
if (array.length === 0 || count >= array.length) {
return []
}
return array.filter((item, index) => index < array.length-count)
}
new的原理
1.立即创建一个新对象
2.对象的隐式原型指向构造函数的显式原型prototype
3.将函数中的this,设置成新建的对象,这样在构造器中用this引用新建的对象
4.执行构造函数的对象
5.构造函数有返回值且是对象?返回构造函数返回值:1创建的新对象
语法: newInstance(Fn, …args)
功能: 创建Fn构造函数的实例对象
function newInstance(Fn,...arg) {
const obj = {};
obj.__proto__ = Fn.prototype;
let result = Fn.call(obj,...arg);
return result instanceof Object ? result:obj;
}
语法: myInstanceOf(obj, Type)
功能: 判断obj是否是Type类型的实例
实现: Type的原型对象是否是obj的原型链上的某个对象, 如果是返回tru, 否则返回false
A instanceof B原理: 沿着A 的隐式原型链去找是否有B的原型
/*
判断obj是否是Type类型的实例
*/
function myInstanceOf(obj,Type) {
let protoObj = obj.__proto__;
while(protoObj){
if(protoObj === Type.prototype)return true;
protoObj = protoObj.__proto__;
}
return false;
}
语法: object mergeObject(…objs)
功能: 合并多个对象, 返回一个合并后对象(不改变原对象)
例子:
{ a: [{ x: 2 }, { y: 4 }], b: 1}
{ a: { z: 3}, b: [2, 3], c: ‘foo’}
合并后: { a: [ { x: 2 }, { y: 4 }, { z: 3 } ], b: [ 1, 2, 3 ], c: ‘foo’ }
/*
1.新建一个对象resultObj
2.先把第一个对象放进去
3.在放第二个对象,如果此对象的键在resultObj里面有,则合并。没有则直接写入
*/
function mergerObject(...objs) {
const resultObj = {};
objs.forEach(obj=>{
//获取当前对象的所有属性;
Object.keys(obj).forEach(key=>{
// 如果resultObjt还没有key值属性
if(resultObj.hasOwnproperty(key)){
//如果有合并
resultObj[key] = [].concat(resultObj[key],obj[key]);
}else{
//如果没有则直接写入
resultObj[key] = obj[key];
}
})
})
return resultObj;
}
深浅拷贝只是针对引用数据类型
复制之后的副本进行修改会不会影响到原来的
浅拷贝:修改拷贝以后的数据会影响原数据,拷贝的引用。使得原数据不安全。(只拷贝一层)
深拷贝:修改拷贝以后的数据不会影响原数据,拷贝的时候生成新数据。
function clone1(target) {
//深浅拷贝针对对象
if(typeof target ==='object' && target !==null){
if(Array.isArray(target)){
return [...target];
}else{
return {...target}
}
}else{
return target;
}
}
扩展运算符只能对一层进行深拷贝,如果拷贝的层数超过了一层的话,那么就会进行浅拷贝。
function clone2(target) {
if(typeof target ==='object' && target !==null){
//创建一个容器
const result = Array.isArray(target)?[]:{};
//for in 既可以遍历数组也可以遍历对象,但是他会遍历原型上的方法
for (let key in target) {
if (target.hasOwnProperty(key)) {
result[key] = target[key];
}
}
return result;
}
else{
return target;
}
}
let arr= [1,3,{name:'ran'}]
let arr2 = arr.slice()
arr2[1]=55
arr2[2].name = 'ke'
console.log(arr); // 输出[1,3,{name:'ke'}]
let arr= [1,3,{name:'ran'}]
let arr2 = arr.concat()
// arr2[1]=55 //输出[1,3,{name:'ran'}]
arr2[3].name = 'ke'
console.log(arr);
//输出 [1,3,{name:'ke'}]
问题1: 函数属性会丢失,不能克隆方法
问题2: 循环引用会出错
//循环引用:b中引用了c,c中又有b
obj = {
b:['a','f'],
c:{h:20}
}
obj.b.push(obj.c);
obj.c.j = obj.b;
b:['a','f',{h:20,j:[]}],
c:{h:20,j:['a','f',[]]}
JSON.stringify():将JavaScript对象转换为JSON字符串
JSON.parse():可以将JSON字符串转为一个对象。
function deepClone1(target) {
//通过数组创建JSON格式的字符串
let str = JSON.stringify(target);
//将JSON格式的字符串转换为JS数据
let data = JSON.parse(str);
return data;
}
判断是否是引用类型,引用类型循环遍历内元素进行复制
解决问题1: 函数属性还没丢失
function deepClone2(target) {
if(typeof target ==='object' && target !==null){
const result = Array.isArray(target)?[]:{};
for (let key in target) {//检测是否本身的元素
if (target.hasOwnProperty(key)) {
result[key] = deepClone2(key);
}
}
return result;
}else{
return target;
}
}
实现二不能解决问题的原因是:死循环?
解决问题2: 循环引用正常
思路:保证对象只克隆一次,就不会套娃了。使用Map存储已经克隆之后的对象。
//加入Map保证克隆过的不再克隆
function deepClone2(target,map=new Map()) {
if(typeof target ==='object' && target !==null){
//先判断是否已经克隆过了
let cloneLet = map.get(target);
if(cache)return cache;
const result = Array.isArray(target)?[]:{};
//key为原对象,result为克隆的对象,如果克隆过则直接取克隆之后的对象就可以了
map.set(target,result);
for (let key in target) {//检测是否本身的元素
if (target.hasOwnProperty(key)) {
result[key] = deepClone3(key,map);
}
}
return result;
}else{
return target;
}
}
for-in效率比较低,因为还需要遍历原型上的方法和属性
数组: while | for | forEach() 优于 for-in | keys()&forEach()
对象: for-in 与 keys()&forEach() 差不多
//map存放已经拷贝过的对象,key为需要拷贝的对象,value为拷贝后的对象
function deepClone3(target,map=new Map()){
//1.判断是否是引用类型
if(typeof target === 'object' && target !==null ){
if(map.has(target))return map.get(target); //说明已经拷贝过了
let isArr = Array.isArray(target);
let res = isArr?[]:{};
map.set(target,res)
if(isArr){//拷贝的是数组
target.forEach((item,index) => {
res[index] = deepClone(item,map);
});
}else{//拷贝的是对象
Object.keys(target).forEach(key=>{
res[key]=deepClone(target[key],map);
})
}
return res; //返回的是一个数组或对象
}else{
return target;
}
字符串倒序
语法: reverseString(str)
功能: 生成一个倒序的字符串
/*
1.先转换成数组 split
2.将数组翻转
3.转化回字符串 join
*/
function reverseString(str) {
let arr = str.split('');
//let arr = [...str]
arr.reverse();
return arr.join('');
}
字符串是否是回文
语法: palindrome(str)
功能: 如果给定的字符串是回文,则返回 true ;否则返回 false
/*
使用双指针,一个从头开始,一个从尾开始,遇见不一样的就返回false
可以先将字符串翻转,判断反转后是否等于翻转前
*/
function palindrome(str) {
return reverseString(str) === str;
}
事件冒泡的流程
事件委托/代理
语法:addEventListener(element, type, fn, selector)
参数
element:父级元素选择器
type:绑定事件的类型,如click
fn:事件处理程序
selector:需要绑定事件的元素
说明:如果selector没有,直接给element绑定事件,如果selector有,将selector对应的多个元素的事件委托绑定给父元素element
function addEventListener1(element, type, fn, selector) {
//判断el的类型,获取到el元素
if(typeof element === 'string')element=document.querySelector(element);
// 如果没有指定selector, 普通的事件绑定
if (!selector) {
element.addEventListener(type, fn)
} else {// 否则是代委托的事件绑定,点击之后先判断是不是需要触发的子元素
element.addEventListener(type, function (event) {
// 得到真正发生事件的目标元素
const target = event.target
// 如果与选择器匹配则触发,不是则不触发
console.log(target);
if (target.matches(selector)) {
// 调用处理事件的回调fn, 并指定this为目标元素, 参数为event
fn.call(target, event)
}
})
}
}
eventBus: 包含所有功能的事件总线对象
eventBus.on(eventName, listener): 绑定事件监听
eventBus.emit(eventName, data): 分发事件
eventBus.off(eventName): 解绑指定事件名的事件监听, 如果没有指定解绑所有
//为一个事件绑定多个回调
eventBus.on('add', (data) => {
console.log('add', data)
})
eventBus.on('add', (data) => {
console.log('add2', data)
})
//触发事件
eventBus.emit('add', 123)
//实现
const eventBus = {
//保存类型与回调的容器
callback:{}
};
//绑定事件
eventBus.on = function (type,callback) {
if(this.callback[type]){
this.callback[type].push(callback);
}else{
this.callback[type] = [callback];
}
}
//触发事件
eventBus.emit = function (type,data) {
if(this.callback[type] && this.callback[type].length>0){
this.callback[type].forEach(callback => {
callback(data);
});
}
}
//解绑事件
eventBus.off = function (eventName) {
if(eventName){
//清楚对应事件名
delete this.callback[eventName];
}else{//全部清除
this.callback = {};
}
}
使用描述
// 订阅pay频道,当支付成功pay的消息触发,商家和骑手接到消息
PubSub.subscribe('pay', (data) => {
console.log("商家接到了订单,准备开始制作", data)
})
PubSub.subscribe('pay', (data) => {
console.log("骑手接到了订单,准备开始去取餐", data)
})
//发布消息,上述的两个回调函数都会被触发
PubSub.publish('pay',{
title:'鱼香肉丝盖饭'
})
与事件总线的区别
可以取消单个订阅,如上图的骑手,因此单个订阅应该有唯一的编号用于取消订阅
事件总线只能取消pay整个事件
PubSub: 包含所有功能的订阅/发布消息的管理者
PubSub.subscribe(msg, subscriber): 订阅消息: 指定消息名和订阅者回调函数
PubSub.publish(msg, data):同步发布消息: 指定消息名和数据
PubSub.publishSync(msg, data): 同步发布消息: 指定消息名和数据
const PubSub = {
//订阅的唯一编号
id:0,
/*
频道于回调保存容器,
需要单个取消,同时执行,所以用对象保存pay:{token1:xx,token2:yy}
*/
callbacks:{
}
}
/*
订阅频道
*/
PubSub.subscribe = function (channel,callback) {
let token = "token_" + this.id++;
//先判断callbacks是不是已经有channel
if(this.callbacks[channel]){
this.callbacks[channel][token] = callback;
}else{//如果没有使用上面this.callbacks[channel]就会找不到了undefined[token]
this.callbacks[channel] ={
[token]:callback
}
}
return token;
}
/*
发布消息
*/
PubSub.publish = function (channel,data) {
if(this.callbacks[channel]){
Object.values(this.callbacks[channel]).forEach(callback => {
callback(data)
})
}
}
/*
取消订阅
1.没有传值,取消所有
2.传入token字符串,取消对应的token订阅
3.msgName字符串,取消对应的频道
*/
PubSub.unsubscribe = function (flag) {
if(flag===undefined){
this.PubSub = {};
}else if(typeof flag === 'string'){
if(flag.indexOf('token_'===0)){//订阅id
let callbackObj= Object.values(this.callbacks).find(obj =>obj.hasOwnProperty(flag));
if(callbackObj){
delete callbackObj[flag];
this.id--;
}
}else{//频道的名称
delete this.callback[flag];
}
}
}
语法:
功能:使用xhr发送ajax请求的工具函数,与axios库功能类似
//使用方式1 配置参数
axios({
url: 'http://localhost:3000/posts',
method: 'GET',
params: {
id: 1,
xxx: 'abc'
}
}).then(
response => {
console.log(response)
},
error => {
alert(error.message)
}
)
//使用方式2 简介语法
axios.get('http://localhost:3000/posts',{
params:{
id: 1,
xxx: 'abc'
}
}).then(
response => {
console.log(response)
},
error => {
alert(error.message)
}
)
思路
1.先处理参数,有默认值的设置默认参数
2.执行异步ajax请求,如果请求成功了调用resolve()
,如果请求失败了调用reject()
2.1 创建xhr对象new XMLHttpRequest()
2.2 初始化请求xmr.open()
2.3 发送请求xmr.send()
发送请求。参数为字符串,可以是json对象字符串,也可以是urlencoded格式字符串
function axios({
url,
method="GET", //设置默认值
parmas={},//默认空对象
data={}//设置默认值
}) {
//返回一个Promise对象
return new Promise((resolve, reject) => {
//处理query参数,拼接到url
let queryString='';
Obeject.keys(params).forEach(key=>{
queryString+=`${key}=${parmas[key]}&`
})
if (queryString){
//去掉最后的&
queryString = queryString.substring(0,queryString.length-1)
//拼接
url += "?"+queryString
}
//处理method为大写,传进来的可能是小写
method = method.toUpperCase()
//1.执行异步ajax请求
//创建xhr对象
const request = new XMLHttpRequest();
//初始化请求(异步)
request.open(method,url,true)
//绑定状态改变的监听,send是异步的,所以绑定监听写在send后面也可以
request.onreadystatechange = function () {
//如果请求没有完成,不需要做处理
if (request.readyState!==4){
return
}
//请求完成了可以获取状态码了
//如果响应状态码在[200,300)之间代表成功,否则失败
const {status,statusText} = request;
if (status>=200&&status<=299){
//2.1如果请求成功了,调用resolve()
//准备结果response对象
const response = {
//服务器返回的是JSON数据需要转换成对象
//响应json数据自动解析为js的对象/数组
data:JSON.parse(request.response),
status,
statusText,
}
resolve(response)
}else {
//2.2如果请求失败,调用reject()
reject(new Error(`request error status is ${status}`))
}
}
switch (method){
case "GET"||"DELETE" :
//get的参数通过url传
request.send();
break;
case "POST"||"PUT":
//发送JSON请求体的数据
request.setRequestHeader("Content-Type","application/json;charset=utf");
request.send(JSON.stringify(data));
break;
}
})
}
/* 发送特定请求的静态方法 */
axios.get = function (url, options) {
return axios(Object.assign(options, {url, method: 'GET'}))
}
axios.delete = function (url, options) {
return axios(Object.assign(options, {url, method: 'DELETE'}))
}
axios.post = function (url, data, options) {
return axios(Object.assign(options, {url, data, method: 'POST'}))
}
axios.put = function (url, data, options) {
return axios(Object.assign(options, {url, data, method: 'PUT'}))
}
笔记:https://editor.csdn.net/md/?articleId=124798768