1 浅复制 Object.assign()
/**
* Object.assign(obj,obj1,obj2...)
* 浅复制
*/
Object.myAssign = (target, ...source) =>{
let res = Object(target); //转换为对象类型的构造函数调用
source.forEach((each) => {
if(each!==null){
for(let key of each){
if(each.hasOwnProperty(key)){
res[key] = each[key]
}
}
}
})
return res;
}
2 Object.create()
/**
* Object.create(proto, [propertyObject])
* 创建新对象并指定对象的原型
*/
/**
*
* @param {*} proto 新对象的原型
* @param {*} propertyObject 属性描述对象{属性,属性描述符}
* @returns
*/
Object.myCreate = (proto, propertyObject = undefined) => {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an object or null')
}
if (proto == null) {
throw TypeError('Cannot convert undefined or null to object')
}
const obj = {};
Object.setPrototypeOf(obj, proto);
if (propertyObject !== undefined) {
Object.defineProperties(obj, propertyObject);
}
return obj;
}
3 类型判断
1 instanceof
A instanceof Binstanceof运算符用来检测constructor.prototype是否存在于参数object的原型链上- 判断A是否是B的实例
- A实例 只存了一个引用
obj.__proto__ - B 构造函数的原型存储在
prototype中 Object.getPrototypeOf(obj)等价于obj.__proto__right.prototype获取构造函数的prototype对象- 只有构造函数有
prototype这个属性
const myInstanceof = (left ,right) => {
let proto = Object.getPrototypeOf(left); // 获取对象的原型
while(proto!==null){
if(proto == right.prototype){
return true;
}
proto = Object.getPrototypeOf(proto); // 获取原型的原型
}
return false;
}
测试
class People {
constructor(){
}
}
const p = new People()
console.log('myInstanceof',myInstanceof(p, People)) // true
2 typeof
const typeOf = function (item) {
return Object.prototype.toString.call(item)
.slice(8,-1).toLowerCase()
}
/**
如果不切割的话得到的是
[object Array]
[object Object]
[object Date]
*/
总汇
typeof- 判断基本类型,无法细分
Object引用类型(Array,Object,Date)
- 判断基本类型,无法细分
instanceof- 前提是要有构造函数 不能判断基本类型和
nullundefined(没有构造函数)
- 前提是要有构造函数 不能判断基本类型和
Object.prototype.toString.call(this)- 官方给出的最准确方法
4 promise(resolve, reject, then, all, race)
另一篇文章详解: 手写promise - 掘金 (juejin.cn)
class myPromise {
static PENDING = 'pending';
static FULFILLED = 'fulfiled';
static REJECTED = 'rejected';
constructor(func) {
// 实例的属性直接用this.xx定义
this.promiseStatus = myPromise.PENDING;
this.promiseResult = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
try {
/**
* reslove是在实例外部调用,会使this丢失,
* 用bind绑定this为实例中的this
*/
func(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve(result) {
setTimeout(() => {
if (this.promiseStatus === myPromise.PENDING) {
this.promiseStatus = myPromise.FULFILLED;
this.promiseResult = result;
/**
* 执行then中收集的回调函数
*/
this.onFulfilledCallbacks.forEach(callback => callback(result));
}
});
}
reject(reason) {
setTimeout(() => {
if (this.promiseStatus === myPromise.PENDING) {
this.promiseStatus = myPromise.REJECTED;
this.promiseResult = reason;
this.onRejectedCallbacks.forEach(callback => callback(reason));
}
});
}
/**
* 返回promise 可以链式调用
* 根据状态来执行
* 回调函数执行返回的值需要resolvePromise来处理
* @param {*} onResolved
* @param {*} onRejected
* @returns
*/
then(onResolved, onRejected) {
let promise = new myPromise((resolve, reject) => {
if (this.promiseStatus === myPromise.PENDING) {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onResolved !== 'function') {
resolve(this.promiseResult);
} else {
let value = onResolved(this.promiseResult);
resolvePromise(promise, value, resolve, reject);
}
} catch (error) {
reject(error);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.promiseResult);
} else {
let value = onRejected(this.promiseResult);
resolvePromise(promise, value, resolve, reject);
}
} catch (error) {
reject(error);
}
});
});
}
if (this.promiseStatus === myPromise.FULFILLED) {
setTimeout(() => {
try {
if (typeof onResolved !== 'function') {
resolve(this.promiseResult);
} else {
let value = onResolved(this.promiseResult);
resolvePromise(promise, value, resolve, reject);
}
} catch (error) {
reject(error);
}
});
}
if (this.promiseStatus === myPromise.REJECTED) {
setTimeout(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.promiseResult);
} else {
let value = onRejected(this.promiseResult);
resolvePromise(promise, value, resolve, reject);
}
} catch (error) {
reject(error);
}
});
}
});
return promise;
}
all(paramsArr) {
const result = [];
let count = 0;
return new myPromise((resolve, reject) => {
paramsArr.forEach((item, index) => {
/**
* 注意处理 参数不是promise的情况
*/
if (item instanceof myPromise) {
item.then(
res => {
count++;
result[index] = res;
count === paramsArr.length && resolve(result);
},
err => {
reject(err);
}
);
} else {
count++;
result[index] = item;
count === paramsArr.length && resolve(result);
}
});
});
}
race(paramsArr) {
return new myPromise((resolve, reject) => {
paramsArr.forEach(item => {
if (item instanceof myPromise) {
item.then(
res => {
resolve(res);
},
err => reject(err)
);
} else {
resolve(item);
}
});
});
}
}
//处理promise链式
/**
* x值的处理
* (1)x是基本类型
* (2)是自定义的promise对象
* (3)是promise实例
*/
/**
* then结果返回值处理函数,处理回调函数是promise的情况
* 递归查找then的链
* @param {*} promise then的返回值promise
* @param {*} x then回调函数的返回值
* @param {*} resolve 对x的处理函数 then的返回值promise中的
* @param {*} reject 对x的处理函数 then的返回值promise中的
* @returns
*/
function resolvePromise(promise, x, resolve, reject) {
if (x === promise) {
throw new TypeError('chaining cycle detected for promise');
}
if (x instanceof myPromise) {
x.then(y => {
resolvePromise(promise, y, resolve, reject);
}, reject);
} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
// 有then属性的对象 或者其他规范的promise,获取then属性,
// get操作可能会有异常
var then = x.then;
} catch (error) {
return reject(error);
}
// then应该是个可执行函数
if (typeof then === 'function') {
// 添加一个锁,只执行一次
let called = false;
try {
then.call( //执行
x,
y => {
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
rej => {
if (called) return;
called = true;
reject(rej);
}
);
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
} else {
resolve(x);
}
}
5 ajax
XMLHTTPRequest对象ajax是对它的封装axios返回promisefetch返回的是promise- JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致(同源)
const ajax = (method, url, data, isAsync=true) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState != 4) return;
if (xhr.status == 200 || xhr == 304) { // 状态码是number, 304资源未被修改,客户端取缓存
resolve(xhr.responseText);
} else {
reject(new Error(xhr.responseText));
}
};
// xhr.setRequestHeader('Accept', 'application/json'); // 客户端期望接收的响应类型
xhr.open(method, url, isAsync);
xhr.send(data);
});
};
6 跨域 JSONP
- 跨域解决方案
- 创建一个
<script src='' />标签 - 把那个跨域的API数据接口地址
- 赋值给
script的src - 还要在这个地址中向服务器传递该函数名(可以通过问号传参
?callback=fn)
const jsonp = ({ url, params, callback }) => {
return new Promise(res => {
const script = document.createElement('script');
script.src = url + '?' +
+ Object.keys(params).map(key => key+'='+params[key]).join('&')
+ '&callback='+callback;
document.body.appendChild(script);
window[callback] = data => {
res(data);
document.body.removeChild(script);
};
});
};
7 偏函数
- 偏函数就是将一个
n参的函数转换成固定x参的函数, - 剩余参数
(n - x)将在下次调用全部传入 - 原理:返回包装函数
- 场景
bind(cxt, arg1)bind给出固定参数 - 易错:最后也是
return
const partial = (fn, ...args) => {
return (...rest)=>{
return fn(...args, ...rest);
}
}
8 柯里化
偏函数是柯里化的一个实例应用,柯里化更加一般化
- 柯里化的基本形态是:有多个参数的函数转换成使用一个参数的函数
- fn(a,b,c)-->fn(a)(b)(c)...
- 使用到的有:闭包+递归
- lodash工具库里有curry方法可以直接用 import curry from 'lodash/fp/curry'
- 参考: juejin.cn/post/727236…
const curry = func => {
let args = []; //使用闭包存储剩余参数和预设参数
return function curried(...innerArgs) { //收集参数
args = [...args, ...innerArgs]; //等同 args = args.concat(innerArgs);
if (args.length >= func.length) { //func.length形参的个数
return func(...args);
} else {
return curried; //继续收集剩余的参数
}
};
};
测试
const add = (a, b, c) => {
return a + b + c;
}
let addCurry = curry(add);
addCurry(1)(2)(3) // 6
9 图片懒加载
- 图片全部加载完成后移除事件监听;
- 加载完的图片,从 imgList 移除;
let imgList = [...document.querySelectorAll('img')];
let length = imgList.length;
const imgLazyLoad = (function() {
let count = 0;
return function() {
let deleteIndexList = [];
imgList.forEach((img, index) => {
let rect = img.getBoundingClientReact(); //相对于视口(viewport)信息的DOM API return {top right bottom left width height}
if(rect.top < window.innerHeight) {
//进入窗口
img.src = img.dataset.src; //获取data-src 添加到src来加载图片
deleteIndexList.push(index);
count++;
if(count==length){
document.removeEventListener('scroll', imgLazyLoad)
}
}
})
// 加载完的图片移除掉
imgList = imgList.filter((i,index) => !deleteIndexList.includes(index))
}
})() //立即执行
document.addEventListener('srcoll', imgLazyLoad)
10 字符串模板匹配
题目:
//输入 template,person
//输出:我是布兰,年龄12,性别男
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let person = {
name: '布兰',
age: 12,
sex: '男'
}
console.log(render(template, person))
代码:
const render = (template, data) => {
return template.replace(/\{\{(.+?)\}\}/g, (match, key) => {
return data[key]
})
}
string.replace(rules, replacer) 搜索所有符合rules的匹配项,用replacer函数替换这些匹配项
当replacer是个函数的时候 replacer(match, key, offset, string)
match: 匹配到的内容,题目中匹配到的是{{name}}{{age}}..key: rules中的捕获组,在圆括号 () 内的部分 题目中的key是nameage..offset:原字符串开始位置索引string:原字符串"我是{{name}},年龄{{age}},性别{{sex}}"
replace(/\{\{(.+?)\}\}/g, function (match, key){})
找到所有形如 {{key}} 的占位符,并将其内容(例如这里的 key)捕获到分组中。
.任意单个字符+至少出现一次?尽可能少地匹配字符,最短的一段非空文本
11 解析 URL 参数为对象
function parseParams(url) {
const reg = /([^?&=]+)=([^?&=]*)/g;
return url.match(reg).reduce((params, param) => {
let [key, value] = param.split('=');
value = decodeURIComponent(value); // 解码
value = /^\d+$/.test(value) ? Number(value) : value; //转成数字
if (params.hasOwnProperty(key)) {
// 属性已经有值
params[key] = [].concat(params[key],value);
} else {
params[key] = value;
}
return params;
}, {});
}
测试
let url = 'http:// w.com/index.html?name=haha&age=18&six=man&six=man&six=man';
console.log(parseParams(url)); //{name: haha, age: 18, six: [man, man, man]}
解析正则规则:
/([^?&=]+)=([^?&=]*)/g()代表一个捕获组 匹配()=()[^?&=]不包含?、&或=的字符+至少出现一次*零个或多个- 上面例子匹配得到的结果数组:
['name=haha', 'age=18', 'six=man'...] /^\d+$/^开头$结尾\d+是出现一个或多个的数字(连续)
12 发布订阅模式(事件总线)
一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新
事件总线是对发布-订阅模式的一种实现
- 订阅
on - 取消订阅
off - 发布
emit(name,once=[true|false],fn); 执行name注册的所有事件
class EventEmitter {
constructor(){
//创建一个数据源
this.cache = {}
}
on(name, fn){
if(this.cache[name]){
this.cache[name].push(fn)
}else{
this.cache[name] = [fn]
}
}
off(name, fn){
let tasks = this.cache[name];
if(tasks){
const index = tasks.findIndex(item => item == fn);
index>=0 && tasks.splice(index,1);
}
}
emit(name, once=false, ...args){
if(!this.cache[name]) return;
let task = this.cache[name];
for(let fn of task) {
fn(...args);
once && this.off(name, fn)
}
}
}
测试:
let eventBus = new EventEmitter()
let fn1 = function(name, age) {
console.log(`fn1, ${name} ${age}`)
}
let fn2 = function(name, age) {
console.log(`fn2, ${name} ${age}`)
}
eventBus.on('aaa', fn1)
eventBus.on('aaa', fn2)
// eventBus.emit('aaa', false, '布兰', 12)
eventBus.off('aaa', fn1)
// eventBus.off('aaa', fn2)
eventBus.emit('aaa', false, 'sayhai', 13)
eventBus.emit('aaa', true, 'sayhai', 13)
eventBus.emit('aaa', false,'sayhai', 13)
13 深复制/深拷贝
对象的 赋值?浅复制?深复制?
- 赋值:得到对象的引用
- 浅复制:得到一个新的对象,但属性只复制了一层,如果属性是对象的话,值是引用
- 深复制:一个新的对象
const deepClone = function (obj, visited = new WeakMap()) {
if (visited.get(obj)) {
return visited.get(obj);
}
if (typeof obj === 'object') {
const cloneTarget = Array.isArray(obj) ? [] : {};
visited.set(obj, cloneTarget); // 要拷贝的值 记录到map结构中 防止循环引用进入死循环
for (let key in obj) {
cloneTarget[key] = deepClone3(obj[key], visited); //每个属性都深拷贝
}
return cloneTarget;
} else {
return obj; //基本类型
}
};
//WeakMap键是弱引用 没有引用了会垃圾回收
其他方法:
//JSON安全 obj先转成JSON字符串,在转换成JSON Obj
//不能解决循环引用
const deepClone = function(obj){
return JSON.parse(JSON.stringify(obj))
}
//html dom自带的API structuredClone(value,{transfer:[]})
//不支持含有循环引用、未实现可迭代接口的自定义对象
const deepClone = function(obj) {
return structuredClone(obj) //原生API
}
测试:
let cloneObj = deepClone(obj);
console.log('obj',obj);
console.log('cloneObj',cloneObj);
console.log('----change----')
cloneObj.children.user = 'cloner'
console.log('obj',obj);
console.log('cloneObj',cloneObj);
14 浅复制
const shallowClone = function(obj){
const cloneTarget = Array.isArray(obj) ? [] : {};
for(let key in obj){
cloneTarget[key] = obj[key];
}
return cloneTarget;
}
简化
/**
* 扩展运算符
* */
const shallowClone3 = (obj) => {
return Array.isArray(obj)? [...obj] : {...obj}
}
原生方法
- 对象:
Object.assign() - 数组:
arr.slice()
15 继承
js中实现继承的方式
1. 原型链继承
缺点
- 1 所有实例共享原型上的属性和方法
- 2 子类实例化的时候不能向父类构造函数传参承
- 3 注意
constructor丢失的问题
function People() {
this.name = ['people'];
this.sayHi = function(){
console.log('hi')
}
}
function Women() {}
Women.prototype = new People(); //原型链继承
Women.prototype.constructor = Women
//测试
const rose = new Women();
rose.name.push('rose')
const lili = new Women();
lili.name.push('lili')
console.log(lili.name) // ['people', 'rose']
lili.sayHi() // hi
//属性共享, womem的原型是people的实例,所以people上所有的属性和方法都会被实例共享
2. 构造函数继承
优点
- 1 实例上属性和方法是单例的不共享
- 2 可以向父类构造函数传递参数
缺点
- 子类访问不到父类的方法, 只能写到构造函数中才能访问
function People(name) {
this.name = name;
this.family = ['family']
this.sayHi = function(){
console.log('hi, i am ' + name);
}
}
People.prototype.greet = function() { //父类上的方法
console.log('nice to meet you');
}
function Women(name) {
People.call(this,name) //调用构造函数,this绑定新建的实例
}
const rose = new Women('rose');
rose.family.push('rose')
const lili = new Women('lili');
console.log(lili.name, lili.family) // lili, ['family']
lili.sayHi();//hi, i am lili
lili.greet();//Uncaught TypeError: lili.greet is not a function 不能访问父类中的方法
3. 组合继承 (原型链继承方法 构造函数继承属性)
优点
- 在构造函数继承的基础上, 子类可以继承父类上的方法
缺点
- 父类被调用了两次 call+1 new+1
function People(name) {
console.log('构造函数被调用')
this.name = name;
this.family = ['family']
}
People.prototype.greet = function() {
console.log('i am '+ this.name +',nice to meet you');
}
function Women(name) {
People.call(this,name) // 构造函数被调用
}
Women.prototype = new People(); // 构造函数被调用
Women.prototype.constructor = Women
const lili = new Women('lili');
console.log(lili.name, lili.family) // lili, ['family']
lili.greet();//i am lili,nice to meet you
4. 寄生式组合继承
- 最优的方法
- 要注意重写原型会丢失
constructor要重新指定
function People(name) {
this.name = name;
this.family = ['family']
}
People.prototype.greet = function() {
console.log('i am '+ this.name +',nice to meet you');
}
function Women(name) {
People.call(this,name)
}
Women.prototype = Object.create(People.prototype) //new People(); // 组合继承中的原型链继承
Women.prototype.constructor = Women;
const lili = new Women('lili');
console.log(lili.name, lili.family) // lili, ['family']
lili.greet();//i am lili,nice to meet you
console.log(lili.constructor.name) // Women
5. class继承
class People {
constructor(name) {
//这里定义的是实例的
this.name = name;
this.family = ['family'];
}
//这里定义的原型上的,是父类People上的方法,等同于People.prototype.greet
greet() {
console.log('i am ' + this.name + ',nice to meet you');
}
}
class Women extends People {
constructor(name) {
super(name); // 调用父类的构造函数 等同于People.call(this,params)
}
}
const lili = new Women('lili');
console.log(lili.name, lili.family); // lili, ['family']
lili.greet(); //i am lili,nice to meet you
console.log(lili.constructor.name);// Women
16 new操作符
new操作符做了哪些?
- 创建一个新的空对象,并将其原型设置为构造函数的
prototype。 - 使用
apply方法调用构造函数,并传入新创建的对象作为this上下文以及提供的其他参数。 - 检查构造函数返回值,如果返回值是一个对象,则返回该对象;否则返回新创建的对象。
const myNew = (fn, ...args) => {
const obj = Object.create(fn.prototype); //创建新对象并指定原型
const res = fn.apply(obj,args); //执行构造函数
return res instanceof Object ? res : obj; //返回新对象或者构造函数返回的对象
}
测试
function People(name) {
this.name = name;
this.sayHi = function(){
console.log('hi,i am '+name)
}
}
People.prototype.walk = function() {
console.log('I am walking...');
}
const lili = myNew(People,'lili');
console.log(lili.name);//lili
lili.sayHi();//hi,i am lili
lili.walk();//I am walking...
17 【数组方法】reduce
- 基本形态
arr.reduce(fn(pre, curr, index, arr),init) - 返回一个遍历完数组后计算的值
Array.prototype.myReduce = function (fn, init) {
let res = init || this[0]; //当前数组的第一个元素
let i = init ? 0 : 1;
if (typeof fn == 'function') {
for (i; i < this.length; i++) {
res = fn(res, this[i], i, this);
}
} else {
throw new Error('parameter1 is not a function');
}
return res;
};
测试
let arr = [1, 2, 3];
console.log(arr.myReduce((pre, curr) => pre + curr));
console.log(arr.myReduce((pre, curr) => pre + curr, 6));
18 【数组方法】map
- 基本形态:
arr.map(fn(item,index,arr),thisArg)返回新数组 - 参考 juejin.cn/post/703650…
Array.prototype.myMap = function (fn, thisArg) {
const res = [];
const cxt = thisArg || window;
if (typeof fn == 'function') {
for (let i = 0; i < this.length; i++) {
res.push(fn.call(cxt, this[i], i, this));
}
} else {
return new TypeError('Parameter1 is not a function');
}
return res;
};
测试
const arr = [1, 2, 3, 4];
console.log(arr.myMap(item => item + 1));
19 【数组方法】forEach
- 基本形态
arr.forEach(function(item,index,arr),thisArg) - 两个参数
- 1-函数(每一项,下标,数组本身)
- 2-上下文
Array.prototype.myForEach = function (fn, thisArg) {
const cxt = thisArg || window;
if (typeof fn === 'function') {
for (let i = 0; i < this.length; i++) {
fn.call(cxt, this[i], i, this);
}
} else {
return new TypeError('parameter1 is not a function');
}
};
测试
const arr = [1, 2, 3, 4];
arr.myForEach(item => console.log(item * 2));
20 【数组方法】filter
- 基本形态
arr.filter(fn(item,index,arr),thisArg)返回新数组--内容是符合条件的 - 参考 juejin.cn/post/703739…
Array.prototype.myFilter = function (fn, thisArg) {
const cxt = thisArg || window;
const res = [];
if (typeof fn === 'function') {
for (let i = 0; i < this.length; i++) {
fn.call(cxt, this[i], i, this) && res.push(this[i]);
}
} else {
return new TypeError('parameter1 is not a function');
}
return res;
};
测试
const arr = [1, 2, 3, 4];
console.log('filter',arr.myFilter(i => i % 2 == 0));
21 【数组方法】some
- 基本形态
arr.some(fn(item,index,arr),thisArg)返回boolean- 1存在一个元素满足条件
true - 2都不满足返回
false
- 1存在一个元素满足条件
- 参考 juejin.cn/post/703806…
Array.prototype.mySome = function (fn, thisArg) {
const cxt = thisArg || window;
const res = false;
if (typeof fn === 'function') {
for (let i = 0; i < this.length; i++) {
if (fn.call(cxt, this[i], i, this)) {
return true;
}
}
} else {
return new TypeError('parameter1 is not a function');
}
return res;
};
测试
const arr = [1, 2, 3, 4];
console.log('some',arr.mySome(i => i % 2 == 0));
22 【数组方法】every
- 基本形态
arr.every(fn(item,index,arr),thisArg)返回boolean只要有一个不满足就false全满足才true - 参考 juejin.cn/post/703806…
Array.prototype.myEvery = function (fn, thisArg) {
const cxt = thisArg || window;
const res = true;
if (typeof fn === 'function') {
for (let i = 0; i < this.length; i++) {
if (!fn.call(cxt, this[i], i, this)) {
return false;
}
}
} else {
return new TypeError('parameter1 is not a function');
}
return res;
};
测试
const arr = [1, 2, 3, 4];
console.log('every',arr.myEvery(i => i % 2 == 0));
23 【函数原型方法】call
Function.prototype.callx = function (context, ...args) {
context = context || window;
const fn = Symbol(); //创建唯一属性键名
context[fn] = this; // 当前函数赋给上下文的属性中 来实现this的绑定
let res = context[fn](...args);
delete context[fn];
return res;
};
测试
var age = 0;
var name = 'win';
var obj = {
age: 25,
name: 'sun',
};
var say = function (a, b) {
console.log(this, this.age, this.name, a, b);
};
say(100, 101);
say.c(obj, 100, 101);
以say为例 上述代码
say调用callxthis指向sayobj.fn = this把say挂载到obj的方法上 这样调用say的this变成了obj改变了this指向- 删除掉原来的方法
- 参考 blog.csdn.net/weixin_4092…
24 【函数原型方法】apply
- apply的参数部分是接收参数数组,要判断 apply 参数可能是空数组的情况
Function.prototype.applyx = function (cxt, args) {
cxt = cxt || window;
const fn = Symbol();
cxt[fn] = this;
const res = args?.length ? cxt[fn](...args) : cxt[fn]();
delete cxt[fn];
return res;
};
测试
var age = 0;
var name = 'win';
var obj = {
age: 25,
name: 'sun',
};
var say = function (a, b) {
console.log(this, this.age, this.name, a, b);
};
say(100, 101);
say.applyx(obj, [100, 101]);
25 【函数原型方法】bind 用call或apply来实现
bind改变上下文(this指向) 不同的是返回一个新函数 新函数也可以传递参数(偏函数)- 利用
闭包的方法 能够保留上下文
Function.prototype.bindx = function (cxt, ...args) {
const func = this; //当前执行的函数
return function (...rest) {
return func.call(cxt, ...args, ...rest);
// func.apply(cxt, [...args, ...rest])
};
};
测试
var age = 0;
var name = 'win';
var obj = {
age: 25,
name: 'sun',
};
var say = function (a, b) {
console.log(this, this.age, this.name, a, b);
};
say(100, 101);
const hi = say.bindx(obj, 100, 101);
hi();
26 防抖debounce
- 在计时器的间隔内,每触发一次就重新计时,当不再有触发事件的时候执行一次
- 防抖主要适用于那些只需要最后一次操作结果的场景,如表单提交、搜索
const debounce = function (fn, delay) {
let timer = null;
return function () {
if (timer) {
clearInterval(timer);
}// 删除刷新
timer = setTimeout(() => {
fn.apply(this, arguments);
}, delay);
};
};
27 节流throttle
- 计时器间隔内出发多次,一旦时间到了,立即执行,且执行一次
- 固定了吞吐量,所以节流更适合那些需要一定时间内稳定的动作结果,例如实时反馈位置信息但不需要每秒多次更新的情况
- 拖拽事件(限制状态更新频率)、滚动事件、窗口大小变化
const throttle = function (fn, delay) {
let timer;
return function () {
if(timer) return; //执行过了 就不再执行
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
},delay)
}
}
完结🎉