this

  1. const user = {
  2. name: 'guanqingchao',
  3. age: 18,
  4. sayHi() {
  5. console.log('SayHi~!', this.name);
  6. },
  7. };
  8. user.sayHi() ;// this指向user
  9. function sayHi(){
  10. console.log(this.hello)
  11. }
  12. sayHi();//this指向window或者undefined

方法:对象中的函数通常称为方法

函数或者方法中的this在调用的时候,才可以确定上下文。
this指向被调用的对象,也就是点号之前的User.sayHi(),如果函数直接执行,没有被调用,this指向undefined或者window。

  1. var num = 1;
  2. var myObject = {
  3. num: 2,
  4. add: function () {
  5. this.num = 3;
  6. // 闭包 立即执行 词法作用域:在函数定义的上下文中找
  7. (function () {
  8. console.log(this.num); //1 this指向window
  9. this.num = 4; // 更新window的变量
  10. })();
  11. console.log(this.num); // 3 this指向myObject
  12. },
  13. sub: function () {
  14. console.log(this.num);
  15. },
  16. };
  17. myObject.add();
  18. console.log(myObject.num); // 3
  19. console.log(num); // 4
  20. var sub = myObject.sub;
  21. sub(); //4 调用的时候前面没有.,this指向window
  1. var scope = 123;
  2. var obj = {
  3. scope: 456,
  4. getScope: function () {
  5. var scope = 789;
  6. console.log(scope); //789
  7. console.log(this.scope); //456
  8. var f = function () {
  9. console.log(scope); // 789 作用域链 函数f定义的上下文中获取 这里指 var scope = 789
  10. console.log(this.scope); //123 this指向window
  11. };
  12. f();
  13. },
  14. };
  15. obj.getScope();

扩展:链式调用

  1. let ladder = {
  2. step: 0,
  3. up() {
  4. this.step++;
  5. return this;
  6. },
  7. down() {
  8. this.step--;
  9. return this;
  10. },
  11. showStep: function () {
  12. console.log(this.step);
  13. return this;
  14. },
  15. };
  16. ladder.up().up().down().showStep(); // 1

call

先看一个例子,实现一个缓存函数执行结果的装饰器

  1. function cachingDecorator(func) {
  2. let cache = new Map();
  3. const cacheKey = JSON.stringify(...arguments);
  4. return function (cacheKey) {
  5. if (cache.has(cacheKey)) {
  6. return cache.get(cacheKey);
  7. }
  8. let result = func(...arguments); // (**)直接这样调用 导致this 为undefined
  9. // let result = func.call(this, ...arguments);
  10. cache.set(cacheKey, result);
  11. return result;
  12. };
  13. }
  14. function slow(x) {
  15. console.log(`Called with ${x}`);
  16. return x;
  17. }
  18. const slowEnhance = cachingDecorator(slow);
  19. slowEnhance(1);
  20. slowEnhance(2);
  21. slowEnhance(1);

这样调用好像没有什么问题,但是对worker中的slow进行缓存 报错了!

  1. // 我们将对 worker.slow 的结果进行缓存
  2. let worker = {
  3. someMethod() {
  4. return 1;
  5. },
  6. slow(x) {
  7. // 可怕的 CPU 过载任务
  8. console.log('Called with Again ' + x);
  9. return x * this.someMethod();
  10. },
  11. };
  12. worker.slow = cachingDecorator(worker.slow);
  13. worker.slow(4);
  14. worker.slow(5);
  15. worker.slow(4);

原因是因为cachingDecorator 中的第八行 直接执行 func(),导致func也就是 worker中的slow的this为undefined。

怎么解?call登场!

  1. func.call(thisArg, arg1, arg2, ...)

执行func,func中的this指向第一个参数thisArg,后面的参数作为func的入参。话不多说,上例子:

  1. //call 基本用法示例
  2. function sayHello(age) {
  3. console.log(this.name + age);
  4. }
  5. const User1 = {
  6. name: 'GQC',
  7. };
  8. const User2 = {
  9. name: 'RJQ',
  10. };
  11. sayHello.call(User1, 18); // 将sayHello中的this指向User1
  12. sayHello.call(User2, 100); // 将sayHello中的this指向User2

解决示例中的问题,很简单了

  1. let result = func.call(this, ...arguments);

apply

  1. func.apply(thisArg, [argsArray])

apply和call一样,区别是:第二参数是数组

手写call、apply

  1. // const User = {
  2. // name: 'JOY',
  3. // sayHi() {
  4. // console.log(this.name);
  5. // },
  6. // };
  7. let User = {
  8. name: 'Joy',
  9. };
  10. function sayHi(age) {
  11. console.log(this.name, age);
  12. }
  13. sayHi.call(User, 18);
  14. Function.prototype.myCall = function () {
  15. // ctx=> {name: "Joy", fn: sayHi}
  16. const ctx = arguments[0]; //User
  17. const args = [...arguments].slice(1);
  18. ctx.fn = this; //this:调用call的函数=>sayHi
  19. const result = ctx.fn(...args);
  20. delete ctx.fn;
  21. return result;
  22. };
  23. sayHi.myCall(User, '18'); //Joy 18
  1. // const User = {
  2. // name: 'JOY',
  3. // sayHi() {
  4. // console.log(this.name);
  5. // },
  6. // };
  7. let User = {
  8. name: 'Joy',
  9. };
  10. function sayHi(age) {
  11. console.log(this.name, age);
  12. }
  13. Function.prototype.myApply = function () {
  14. const ctx = arguments[0];
  15. const args = arguments[1];
  16. ctx.fn = this;
  17. //bind第二个参数是数组
  18. if (arguments[1]) {
  19. result = context.fn(...arguments[1]);
  20. } else {
  21. result = context.fn();
  22. }
  23. delete ctx.fn;
  24. return result;
  25. };
  26. sayHi.myApply(User, [18]); //Joy 18

防抖

debounce(f, ms) 装饰器的结果是一个包装器,该包装器将暂停对 f 的调用,直到经过 ms 毫秒的非活动状态,然后使用最新的参数调用 f 一次。(最后一次调用的ms之后 才执行)。

举个例子,我们有一个函数 f,并将其替换为 f = debounce(f, 1000)
然后,如果包装函数非别在 0ms、200ms 和 500ms 时被调用了,之后没有其他调用,那么实际的 f 只会在 1500ms 时被调用一次。也就是说:从最后一次调用开始经过 1000ms 的冷却期之后。
image.png

它将获得最后一个调用的所有参数,其他调用的参数将被忽略。
它被调用时,它会安排一个在给定的 ms 之后对原始函数的调用,并取消之前的此类超时。

  1. function debounce(func, ms) {
  2. let timeout;
  3. return function () {
  4. clearTimeout(timeout);
  5. timeout = setTimeout(() => func.apply(this, arguments), ms);
  6. };
  7. }
  8. function search(name) {
  9. console.log('search...', name);
  10. }
  11. const seacherDebounce = debounce(search, 2000);
  12. setTimeout(() => seacherDebounce('b'), 1000);
  13. setTimeout(() => seacherDebounce('c'), 1500);

使用场景

  • 用户输入的时候,请求后端接口(期望用户最后输入 的之后 ms才请求接口)
  • 鼠标移动滚动
  • 多次请求,只用最后一次请求的结果?
  • search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
  • 页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况),window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

节流

规定时间内,最多只执行一次触发动作。

当被多次调用时,它会在每 ms 毫秒最多将调用传递给 f 一次。
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效

与去抖的不同是,它是个完全不同的装饰器:

  • debounce 会在“冷却”期后运行函数一次。适用于处理最终结果。
  • throttle 运行函数的频率不会大于所给定的时间 ms 毫秒。适用于不应该经常进行的定期更新。
  1. function throttle(fn, interval) {
  2. // last为上一次触发回调的时间 初始化为0 保证第一个符合条件的函数被调用
  3. let last = 0;
  4. return function () {
  5. let now = +new Date();
  6. // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
  7. if (now - last >= interval) {
  8. // 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
  9. last = now;
  10. fn.apply(this, arguments);
  11. }
  12. };
  13. }
  14. const dFN = throttle(test, 3000);
  15. setTimeout(() => dFN('b'), 1000);
  16. setTimeout(() => dFN('c'), 2000);
  17. // 调用的是:哪一个delay最少就执行哪一个

image.png
image.png

  • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
  • 搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求
  • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

加强优化

优化:debounce 的问题在于它“太有耐心了”。试想,如果用户的操作十分频繁——他每次都不等 debounce 设置的 delay 时间结束就进行下一次操作,于是每次 debounce 都为该用户重新生成定时器,回调函数被延迟了不计其数次。频繁的延迟会导致用户迟迟得不到响应,用户同样会产生“这个页面卡死了”的观感。

为了避免弄巧成拙,我们需要借力 throttle 的思想,打造一个“有底线”的 debounce——等你可以,但我有我的原则:delay 时间内,我可以为你重新生成定时器;但只要delay的时间到了,我必须要给用户一个响应。这个 throttle 与 debounce “合体”思路,已经被很多成熟的前端库应用到了它们的加强版 throttle 函数的实现中:

  1. // fn是我们需要包装的事件回调, delay是时间间隔的阈值
  2. function throttle(fn, delay) {
  3. // last为上一次触发回调的时间, timer是定时器
  4. let last = 0, timer = null
  5. // 将throttle处理结果当作函数返回
  6. return function () {
  7. let now = +new Date()
  8. // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
  9. if (now - last < delay) {
  10. // 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
  11. clearTimeout(timer)
  12. timer = setTimeout(function () {
  13. last = now
  14. fn.apply(this, arguments)
  15. }, delay)
  16. } else {
  17. // 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
  18. last = now
  19. fn.apply(this, arguments)
  20. }
  21. }
  22. }

bind

基本使用

场景1:
方法和对象被分开

  1. let User = {
  2. englishName: 'Joy',
  3. sayHi() {
  4. console.log('Hi:', this.englishName);
  5. },
  6. };
  7. const SayHi2 = User.sayHi;
  8. SayHi2();
  9. const SayHiBF = SayHi2.bind(User);
  10. SayHiBF();

场景2:
示例中在window中调用, 导致this丢失,指向的不是User

  1. const User = {
  2. englishName: 'Joy',
  3. sayHi() {
  4. console.log('Hi:', this.englishName);
  5. },
  6. };
  7. setTimeout(User.sayHi, 1000); // Hi: undefined
  8. //===========================================
  9. setTimeout(function () {
  10. console.log(this); // window
  11. User.sayHi(); //Hi: Joy 这里的sayHi被User调用执行 this指向了User
  12. }, 1000);
  13. setTimeout(() => User.sayHi(), 1000);//Hi Joy

浏览器中的setTimeout为函数调用设定了 this = window(node中this指向计时器timer)

所以第8行视图从window中找englishName,没有找到.
可以添加包装器来解决。

但是如果在setTimeout延迟执行前,user中的方法改变了,就不是我们预期的那样了!

  1. let User = {
  2. englishName: 'Joy',
  3. sayHi() {
  4. console.log('Hi:', this.englishName);
  5. },
  6. };
  7. setTimeout(() => User.sayHi(), 1000);
  8. User = {
  9. sayHi() {
  10. console.log('Another user in setTimeout!');
  11. },
  12. };
  13. // Another user in setTimeout!

并没有按照预期打印 Hi Joy
bind登场了!

  1. let bound = func.bind(context, [arg1], [arg2], ...);

先绑定this,延迟执行
它允许将上下文绑定为 this,以及绑定函数的起始参数。

  1. let User = {
  2. englishName: 'Joy',
  3. sayHi() {
  4. console.log('Hi:', this.englishName);
  5. },
  6. };
  7. const SayHiBF = User.sayHi.bind(User);
  8. setTimeout(() => SayHiBF(), 1000);
  9. User = {
  10. sayHi() {
  11. console.log('Another user in setTimeout!');
  12. },
  13. };
  14. //Hi Joy

bindAll

  1. for (let key in user) {
  2. if (typeof user[key] == 'function') {
  3. user[key] = user[key].bind(user);
  4. }
  5. }

偏函数

给函数绑定一些参数
可以理解为多个参数的时候,固定一些参数,每次调用的时候,保证有些参数是固定的,比如:

  1. function mul(a, b) {
  2. return a * b;
  3. }
  4. const double = mul.bind(null, 2);
  5. console.log(double(3));
  6. console.log(double(4));

2传递给参数a,使得后续的新函数double的调用的第一个参数一直是2,这里this没有指定 null

为什么我们通常会创建一个偏函数?
好处是我们可以创建一个具有可读性高的名字(double,triple)的独立函数。我们可以使用它,并且不必每次都提供一个参数,因为参数是被绑定了的。
另一方面,当我们有一个非常通用的函数,并希望有一个通用型更低的该函数的变体时,偏函数会非常有用。

手写bind

  1. const User = {
  2. name: 'JOY',
  3. sayHi(age, color) {
  4. console.log(this.name, age, color);
  5. },
  6. };
  7. Function.prototype.myBind = function () {
  8. const ctx = arguments[0];
  9. const fn = this;
  10. const args = [...arguments].slice(1);
  11. return function () {
  12. return fn.call(ctx, ...args, ...arguments);
  13. };
  14. };
  15. // const SayHello = User.sayHi;
  16. const SayHello = User.sayHi.myBind(User, 18);
  17. SayHello('red');

构造函数情况

  1. let foo = function() {
  2. this.content = "hello";
  3. }
  4. let boo = foo.bind({content:"bye"});
  5. let res = new boo();
  6. console.log(res); // {content:"hello"}
  7. ***************************************
  8. let foo = function() {
  9. this.content = "hello";
  10. }
  11. let boo = new foo.bind({content:"bye"}); // Uncaught TypeError: foo.bind is not a constructor
  12. 因此必须考虑把函数的原型保留下来

通过上面的例子,可以知道在使用bind对一个函数绑定了上下文之后,得到的函数使用new操作符进行操作之后,这个结果的上下文并不受传递给bind的上下文影响,也就是使用new操作符的时候,传递给bind的第一个参数是会被忽略掉的。

  1. 判断是否对bind之后的结果使用了new操作符
  2. 保留原型
  1. Function.prototype.bind2 = function() {
  2. const args = [...arguments];
  3. const ctx = [...arguments][0];
  4. const fn = this;
  5. const noop = function() {}
  6. const res = function() {
  7. let restArgs = [...arguments];
  8. // this只和运行的时候有关系,所以这里的this和上面的fn不是一码事,new res()和res()在调用的时候,res中的this是不同的东西
  9. console.log('this ====>',this,this instanceof noop,this.prototype)
  10. return fn.apply(this instanceof noop ? this : ctx, [...args,...restArgs]);
  11. }
  12. if(this.prototype) {
  13. noop.prototype = this.prototype;
  14. }
  15. res.prototype = new noop();
  16. return res;
  17. };
  18. *******************************
  19. let foo = function(){
  20. console.log(this);
  21. }
  22. let boo = foo.bind2({content: "hello"});
  23. boo(); // {content: "hello"}
  24. ************************************
  25. let foo = function() {
  26. this.content = "hello";
  27. }
  28. let boo = foo.bind2({content:"bye"});
  29. let res = new boo();
  30. console.log(res); // {content:"hello"}

image.png
image.png

柯里化

柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)。
柯里化不会调用函数。它只是对函数进行转换。

函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。

  1. function curry(func) {
  2. return function curried(...args) {
  3. if (args.length >= func.length) {
  4. return func.apply(this, args);
  5. } else {
  6. return function (...args2) {
  7. return curried.apply(this, args.concat(args2));
  8. };
  9. }
  10. };
  11. }
  12. function multiFn(a, b, c, d) {
  13. return a + b + c + d;
  14. }
  15. var multi = curry(multiFn);
  16. multi(2, 3, 4);
  17. multi(2)(3, 4);
  18. multi(2, 3)(4);
  19. multi(2)(3)(4)(5);
  20. function curry(fn, args=[]) {
  21. const len = fn.length; // 获取函数的参数个数
  22. return function(){
  23. let newArgs =[...args,...arguments];
  24. if (newArgs.length < len) {
  25. return curry.call(this,fn,newArgs);
  26. }else{
  27. return fn.apply(this,newArgs);
  28. }
  29. }
  30. }
  31. function multiFn(a, b, c,d) {
  32. return a + b + c+d;
  33. }
  34. var multi = curry(multiFn);
  35. multi(2)(3)(4)(5);
  36. multi(2,3,4);
  37. multi(2)(3,4);
  38. multi(2,3)(4);
  39. const curry = (fn, arr = []) => (...args) => (
  40. arg => arg.length === fn.length
  41. ? fn(...arg)
  42. : curry(fn, arg)
  43. )([...arr, ...args])
  44. let curryTest=curry((a,b,c,d)=>a+b+c+d)
  45. curryTest(1,2,3)(4) //返回10
  46. curryTest(1,2)(4)(3) //返回10
  47. curryTest(1,2)(3,4) //返回10