this
const user = {name: 'guanqingchao',age: 18,sayHi() {console.log('SayHi~!', this.name);},};user.sayHi() ;// this指向userfunction 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指向windowthis.num = 4; // 更新window的变量})();console.log(this.num); // 3 this指向myObject},sub: function () {console.log(this.num);},};myObject.add();console.log(myObject.num); // 3console.log(num); // 4var sub = myObject.sub;sub(); //4 调用的时候前面没有.,this指向window
var scope = 123;var obj = {scope: 456,getScope: function () {var scope = 789;console.log(scope); //789console.log(this.scope); //456var f = function () {console.log(scope); // 789 作用域链 函数f定义的上下文中获取 这里指 var scope = 789console.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指向User1sayHello.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]; //Userconst args = [...arguments].slice(1);ctx.fn = this; //this:调用call的函数=>sayHiconst 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 = nowfn.apply(this, arguments)}, delay)} else {// 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应last = nowfn.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); // windowUser.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) //返回10curryTest(1,2)(4)(3) //返回10curryTest(1,2)(3,4) //返回10

