this
const user = {
name: 'guanqingchao',
age: 18,
sayHi() {
console.log('SayHi~!', this.name);
},
};
user.sayHi() ;// this指向user
function sayHi(){
console.log(this.hello)
}
sayHi();//this指向window或者undefined
方法:对象中的函数通常称为方法
函数或者方法中的this在调用的时候,才可以确定上下文。
this指向被调用的对象,也就是点号之前的User.sayHi(),如果函数直接执行,没有被调用,this指向undefined或者window。
var num = 1;
var myObject = {
num: 2,
add: function () {
this.num = 3;
// 闭包 立即执行 词法作用域:在函数定义的上下文中找
(function () {
console.log(this.num); //1 this指向window
this.num = 4; // 更新window的变量
})();
console.log(this.num); // 3 this指向myObject
},
sub: function () {
console.log(this.num);
},
};
myObject.add();
console.log(myObject.num); // 3
console.log(num); // 4
var sub = myObject.sub;
sub(); //4 调用的时候前面没有.,this指向window
var scope = 123;
var obj = {
scope: 456,
getScope: function () {
var scope = 789;
console.log(scope); //789
console.log(this.scope); //456
var f = function () {
console.log(scope); // 789 作用域链 函数f定义的上下文中获取 这里指 var scope = 789
console.log(this.scope); //123 this指向window
};
f();
},
};
obj.getScope();
扩展:链式调用
let ladder = {
step: 0,
up() {
this.step++;
return this;
},
down() {
this.step--;
return this;
},
showStep: function () {
console.log(this.step);
return this;
},
};
ladder.up().up().down().showStep(); // 1
call
先看一个例子,实现一个缓存函数执行结果的装饰器
function cachingDecorator(func) {
let cache = new Map();
const cacheKey = JSON.stringify(...arguments);
return function (cacheKey) {
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
let result = func(...arguments); // (**)直接这样调用 导致this 为undefined
// let result = func.call(this, ...arguments);
cache.set(cacheKey, result);
return result;
};
}
function slow(x) {
console.log(`Called with ${x}`);
return x;
}
const slowEnhance = cachingDecorator(slow);
slowEnhance(1);
slowEnhance(2);
slowEnhance(1);
这样调用好像没有什么问题,但是对worker中的slow进行缓存 报错了!
// 我们将对 worker.slow 的结果进行缓存
let worker = {
someMethod() {
return 1;
},
slow(x) {
// 可怕的 CPU 过载任务
console.log('Called with Again ' + x);
return x * this.someMethod();
},
};
worker.slow = cachingDecorator(worker.slow);
worker.slow(4);
worker.slow(5);
worker.slow(4);
原因是因为cachingDecorator 中的第八行 直接执行 func(),导致func也就是 worker中的slow的this为undefined。
怎么解?call登场!
func.call(thisArg, arg1, arg2, ...)
执行func,func中的this指向第一个参数thisArg,后面的参数作为func的入参。话不多说,上例子:
//call 基本用法示例
function sayHello(age) {
console.log(this.name + age);
}
const User1 = {
name: 'GQC',
};
const User2 = {
name: 'RJQ',
};
sayHello.call(User1, 18); // 将sayHello中的this指向User1
sayHello.call(User2, 100); // 将sayHello中的this指向User2
解决示例中的问题,很简单了
let result = func.call(this, ...arguments);
apply
func.apply(thisArg, [argsArray])
apply和call一样,区别是:第二参数是数组
手写call、apply
// const User = {
// name: 'JOY',
// sayHi() {
// console.log(this.name);
// },
// };
let User = {
name: 'Joy',
};
function sayHi(age) {
console.log(this.name, age);
}
sayHi.call(User, 18);
Function.prototype.myCall = function () {
// ctx=> {name: "Joy", fn: sayHi}
const ctx = arguments[0]; //User
const args = [...arguments].slice(1);
ctx.fn = this; //this:调用call的函数=>sayHi
const result = ctx.fn(...args);
delete ctx.fn;
return result;
};
sayHi.myCall(User, '18'); //Joy 18
// const User = {
// name: 'JOY',
// sayHi() {
// console.log(this.name);
// },
// };
let User = {
name: 'Joy',
};
function sayHi(age) {
console.log(this.name, age);
}
Function.prototype.myApply = function () {
const ctx = arguments[0];
const args = arguments[1];
ctx.fn = this;
//bind第二个参数是数组
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
delete ctx.fn;
return result;
};
sayHi.myApply(User, [18]); //Joy 18
防抖
debounce(f, ms) 装饰器的结果是一个包装器,该包装器将暂停对 f 的调用,直到经过 ms 毫秒的非活动状态,然后使用最新的参数调用 f 一次。(最后一次调用的ms之后 才执行)。
举个例子,我们有一个函数 f
,并将其替换为 f = debounce(f, 1000)
。
然后,如果包装函数非别在 0ms、200ms 和 500ms 时被调用了,之后没有其他调用,那么实际的 f
只会在 1500ms 时被调用一次。也就是说:从最后一次调用开始经过 1000ms 的冷却期之后。
它将获得最后一个调用的所有参数,其他调用的参数将被忽略。
它被调用时,它会安排一个在给定的 ms 之后对原始函数的调用,并取消之前的此类超时。
function debounce(func, ms) {
let timeout;
return function () {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, arguments), ms);
};
}
function search(name) {
console.log('search...', name);
}
const seacherDebounce = debounce(search, 2000);
setTimeout(() => seacherDebounce('b'), 1000);
setTimeout(() => seacherDebounce('c'), 1500);
使用场景
- 用户输入的时候,请求后端接口(期望用户最后输入 的之后 ms才请求接口)
- 鼠标移动滚动
- 多次请求,只用最后一次请求的结果?
- search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
- 页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况),window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
节流
规定时间内,最多只执行一次触发动作。
当被多次调用时,它会在每 ms
毫秒最多将调用传递给 f
一次。
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效
与去抖的不同是,它是个完全不同的装饰器:
debounce
会在“冷却”期后运行函数一次。适用于处理最终结果。throttle
运行函数的频率不会大于所给定的时间ms
毫秒。适用于不应该经常进行的定期更新。
function throttle(fn, interval) {
// last为上一次触发回调的时间 初始化为0 保证第一个符合条件的函数被调用
let last = 0;
return function () {
let now = +new Date();
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if (now - last >= interval) {
// 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
last = now;
fn.apply(this, arguments);
}
};
}
const dFN = throttle(test, 3000);
setTimeout(() => dFN('b'), 1000);
setTimeout(() => dFN('c'), 2000);
// 调用的是:哪一个delay最少就执行哪一个
- 鼠标不断点击触发,mousedown(单位时间内只触发一次)
- 搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求
- 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
加强优化
优化:debounce 的问题在于它“太有耐心了”。试想,如果用户的操作十分频繁——他每次都不等 debounce 设置的 delay 时间结束就进行下一次操作,于是每次 debounce 都为该用户重新生成定时器,回调函数被延迟了不计其数次。频繁的延迟会导致用户迟迟得不到响应,用户同样会产生“这个页面卡死了”的观感。
为了避免弄巧成拙,我们需要借力 throttle 的思想,打造一个“有底线”的 debounce——等你可以,但我有我的原则:delay 时间内,我可以为你重新生成定时器;但只要delay的时间到了,我必须要给用户一个响应。这个 throttle 与 debounce “合体”思路,已经被很多成熟的前端库应用到了它们的加强版 throttle 函数的实现中:
// fn是我们需要包装的事件回调, delay是时间间隔的阈值
function throttle(fn, delay) {
// last为上一次触发回调的时间, timer是定时器
let last = 0, timer = null
// 将throttle处理结果当作函数返回
return function () {
let now = +new Date()
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if (now - last < delay) {
// 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
clearTimeout(timer)
timer = setTimeout(function () {
last = now
fn.apply(this, arguments)
}, delay)
} else {
// 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
last = now
fn.apply(this, arguments)
}
}
}
bind
基本使用
场景1:
方法和对象被分开
let User = {
englishName: 'Joy',
sayHi() {
console.log('Hi:', this.englishName);
},
};
const SayHi2 = User.sayHi;
SayHi2();
const SayHiBF = SayHi2.bind(User);
SayHiBF();
场景2:
示例中在window中调用, 导致this丢失,指向的不是User
const User = {
englishName: 'Joy',
sayHi() {
console.log('Hi:', this.englishName);
},
};
setTimeout(User.sayHi, 1000); // Hi: undefined
//===========================================
setTimeout(function () {
console.log(this); // window
User.sayHi(); //Hi: Joy 这里的sayHi被User调用执行 this指向了User
}, 1000);
setTimeout(() => User.sayHi(), 1000);//Hi Joy
浏览器中的setTimeout为函数调用设定了 this = window(node中this指向计时器timer)
所以第8行视图从window中找englishName,没有找到.
可以添加包装器来解决。
但是如果在setTimeout延迟执行前,user中的方法改变了,就不是我们预期的那样了!
let User = {
englishName: 'Joy',
sayHi() {
console.log('Hi:', this.englishName);
},
};
setTimeout(() => User.sayHi(), 1000);
User = {
sayHi() {
console.log('Another user in setTimeout!');
},
};
// Another user in setTimeout!
并没有按照预期打印 Hi Joy
bind登场了!
let bound = func.bind(context, [arg1], [arg2], ...);
先绑定this,延迟执行
它允许将上下文绑定为 this,以及绑定函数的起始参数。
let User = {
englishName: 'Joy',
sayHi() {
console.log('Hi:', this.englishName);
},
};
const SayHiBF = User.sayHi.bind(User);
setTimeout(() => SayHiBF(), 1000);
User = {
sayHi() {
console.log('Another user in setTimeout!');
},
};
//Hi Joy
bindAll
for (let key in user) {
if (typeof user[key] == 'function') {
user[key] = user[key].bind(user);
}
}
偏函数
给函数绑定一些参数
可以理解为多个参数的时候,固定一些参数,每次调用的时候,保证有些参数是固定的,比如:
function mul(a, b) {
return a * b;
}
const double = mul.bind(null, 2);
console.log(double(3));
console.log(double(4));
2传递给参数a,使得后续的新函数double的调用的第一个参数一直是2,这里this没有指定 null
为什么我们通常会创建一个偏函数?
好处是我们可以创建一个具有可读性高的名字(double,triple)的独立函数。我们可以使用它,并且不必每次都提供一个参数,因为参数是被绑定了的。
另一方面,当我们有一个非常通用的函数,并希望有一个通用型更低的该函数的变体时,偏函数会非常有用。
手写bind
const User = {
name: 'JOY',
sayHi(age, color) {
console.log(this.name, age, color);
},
};
Function.prototype.myBind = function () {
const ctx = arguments[0];
const fn = this;
const args = [...arguments].slice(1);
return function () {
return fn.call(ctx, ...args, ...arguments);
};
};
// const SayHello = User.sayHi;
const SayHello = User.sayHi.myBind(User, 18);
SayHello('red');
构造函数情况
let foo = function() {
this.content = "hello";
}
let boo = foo.bind({content:"bye"});
let res = new boo();
console.log(res); // {content:"hello"}
***************************************
let foo = function() {
this.content = "hello";
}
let boo = new foo.bind({content:"bye"}); // Uncaught TypeError: foo.bind is not a constructor
因此必须考虑把函数的原型保留下来
通过上面的例子,可以知道在使用bind对一个函数绑定了上下文之后,得到的函数使用new操作符进行操作之后,这个结果的上下文并不受传递给bind的上下文影响,也就是使用new操作符的时候,传递给bind的第一个参数是会被忽略掉的。
- 判断是否对bind之后的结果使用了new操作符
- 保留原型
Function.prototype.bind2 = function() {
const args = [...arguments];
const ctx = [...arguments][0];
const fn = this;
const noop = function() {}
const res = function() {
let restArgs = [...arguments];
// this只和运行的时候有关系,所以这里的this和上面的fn不是一码事,new res()和res()在调用的时候,res中的this是不同的东西
console.log('this ====>',this,this instanceof noop,this.prototype)
return fn.apply(this instanceof noop ? this : ctx, [...args,...restArgs]);
}
if(this.prototype) {
noop.prototype = this.prototype;
}
res.prototype = new noop();
return res;
};
*******************************
let foo = function(){
console.log(this);
}
let boo = foo.bind2({content: "hello"});
boo(); // {content: "hello"}
************************************
let foo = function() {
this.content = "hello";
}
let boo = foo.bind2({content:"bye"});
let res = new boo();
console.log(res); // {content:"hello"}
柯里化
柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)。
柯里化不会调用函数。它只是对函数进行转换。
函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function (...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
function multiFn(a, b, c, d) {
return a + b + c + d;
}
var multi = curry(multiFn);
multi(2, 3, 4);
multi(2)(3, 4);
multi(2, 3)(4);
multi(2)(3)(4)(5);
function curry(fn, args=[]) {
const len = fn.length; // 获取函数的参数个数
return function(){
let newArgs =[...args,...arguments];
if (newArgs.length < len) {
return curry.call(this,fn,newArgs);
}else{
return fn.apply(this,newArgs);
}
}
}
function multiFn(a, b, c,d) {
return a + b + c+d;
}
var multi = curry(multiFn);
multi(2)(3)(4)(5);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);
const curry = (fn, arr = []) => (...args) => (
arg => arg.length === fn.length
? fn(...arg)
: curry(fn, arg)
)([...arr, ...args])
let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4) //返回10
curryTest(1,2)(4)(3) //返回10
curryTest(1,2)(3,4) //返回10