JavaScript 高级应用(第二弹)

x33g5p2x  于2022-02-07 转载在 JavaScript  
字(4.4k)|赞(0)|评价(0)|浏览(363)

Author: Gorit

Date:2022年1月6日

一、Fuction

这三个玩意是干啥的,apply, call, bind?

用来修改 this 指向的,如果默认值为 null 或者 undefined 的,那么 this 的值就会指向 window(游览器环境下)

调用对象的方法,将另一个对象替换为当前对象。

1.1 call

最实用的 call 的用法,简单来说,我们有个函数,一般都是通过函数名直接调用执行,另一种方式就是通过函数名.call() 来调用

这样做就是改变了函数的上下文,即改变了 this 的指向

一、改变上下文

function add(a, b) {
    console.log(this);	// Window
    return a + b;
}

add(1,2);

// 使用 call

function add1(a, b) {
    console.log(this);	// obj
    return a + b + this.c;
}

const obj = {
    c: 20,
};

add1.call(obj, 10, 20);	// 50,但是在实际的开发当中,我们会传 undefined 的

二、实现一个 call 函数

需求:

  1. 处理指定的函数
  2. 能够改变 this 的上下文
  3. 传参
function add1(a, b) {
    console.log(this);	// obj
    return a + b + this.c;
}

const obj = {
    c: 20,
};

/**
 * 如何实现 call 函数?
 * 对象.函数() this 指向这个对象
 * @param {*} fn 接收的函数,实际上为回调函数
 * @param {*} obj 改变 this 为 obj
 * @param  {...any} args 传参
 */
function call1(fn, obj, ...args) {
    // 可能存在为 null 或 undefined 的 this
    if (obj === undefined || obj === null) {
        // 绑定 Node.js 的全局对象
        obj = globalThis;
    }

    // console.log(obj);
    // 临时绑定方法 为 obj
    obj.temp = fn;
    const result = obj.temp(...args);
    // 删除对象的属性,方法
    delete obj.temp;
    return result;
}

globalThis.c = 10;

let result = call1(add1, obj, 20, 20);
console.log(result);    // 60

let result1 = call1(add1, undefined, 20, 20);
console.log(result1);   // 50

1.2 apply

apply 方法和 call 方法类似,唯一不同的点就是传参的方式

apply 一次性接收一个数组,而 call 是可以接收多个参数

一、 场景

let arr = ['a', 'b'];
let elements = [1,2,3];

arr.push.apply(arr, elements);
console.log(arr);   // ['a', 'b', 1, 2, 3]

减少循环操作

const test = [10,20,30,40];

console.log("test max val is =>", Math.max.apply(undefined, test));
console.log("test min val is =>", Math.min.apply(undefined, test));

二、实现一个 apply 函数

/**
 * 
 * @param {*} fn 
 * @param {*} obj this 指向
 * @param {*} args 数组[]
 * @returns 
 */
function apply(fn, obj, args) {
    if (obj === undefined || obj === null) {
        obj = globalThis;
    }
    // 为 obj 添加临时方法
    obj.temp = fn;
    // 执行临时方法,传参
    const result = obj.temp(...args);
    // 删除临时方法
    delete obj.temp;
    
    return result;
}

function add(a , b) {
    return a + b + this.c;
}

const obj = {
    c: 10
}

// 如果没有绑定这个,则会打印 NaN
globalThis.c = 5;

console.log(apply(add, undefined, [10, 20]));   //  35
console.log(apply(add, obj, [10, 20]));   // 40

1.3 bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 指定为 bind() 的第一个参数,而其他参数则作为新函数的参数,供调用使用

一、基本使用

globalThis.x = 9; // Node.js 环境 globalThis

var module = {
    x: 81,
    getX: function() {
        return this.x;
    }
}

console.log(module.getX()); // 81

let retireve = module.getX;
console.log(retireve());    // 9 拿到的是全局的

/**
 * 绑定函数
 * 创建一个新函数,把 'this' 绑定到 module 对象上
 */

let boundGetX = retireve.bind(module);
console.log(boundGetX());   // 81

1.4 callback

学习 callback 之前,我们先提出如下几个问题

什么是 callback?从名称上来看,它在 JavaScript 中叫做 “回调函数”?那么什 么又是“回调函数” 呢?“回调函数” 又要怎么触发呢?它有返回值吗?
不绕圈子了,不然就进入回调陷阱了

一、来看一个生活中的例子

我们先来看一个生活 中回调函数的一个例子(我在学习 callback,在知乎翻到的)

这里面出现了几个概念,我的理解如下

  1. 你给店员,留下电话号码,电话号码当做“回调函数”
  2. 把电话号码留在店员那里,相当于把 “回调函数注册了一个相关事件”
  3. 当货物到了,就相当于触发了 “上面回调函数所注册的相关事件”,也就是说,电源知道你要的货物到了
  4. 店员打电话通知你,说货物到了。这就触发了回调函数
    换成 JavaScript 的语言来说,我们注册了一个异步函数,但是不知道什么时候生效(收到回调)。当收到某一特定事件(货物到了),并且店员打电话告诉我们,通知我们去拿货物(通知回调)

我们日常在编程的过程中,我们的代码一般都是从上往下按顺序执行的。很少会出现程序不按照顺序执行的情况。【仅限 JavaScript 环境,因为 JavaScript 是单线程语言,是不存在并发这一说的】

但是,有时候确实需要回调函数处理一些 非同步 问题

  • 异步网络请求(Ajax)
  • 延迟处理(setTimeout)

二、同步与异步

但是在有些情况,确实需要异步执行的!!!
举个最简单的例子,网络请求,大家都熟悉吧,有时候网页内容加载不出来,我们就会按下键盘上的 F5 键,这个时候游览器就会把当前网页重新加载一般。 — 这种必须要等待内容加载完毕的,就叫 “同步处理”

我们再来看看 “异步” 的案例, 翻译都用过吧,但是我们输入完待翻译的内容之后,整个网页并没有刷新,只有翻译的框框显示了翻译结果。这种局部刷新的情况就叫做 “异步处理”

三、我们在哪里用到了回调函数?

  • web 开发中处理 DOM 事件时?
<button id="submit">提交</button>

...

// 处理 DOM 事件
let btnSubmit = document.getElementById("submit");

// 方式一:监听点击事件 (ES5 的写法,在 ES6 中我们可以使用箭头函数来简化)
btnSubmit.addEventListener('click', function() {
    // 逻辑处理
});

// 方式二:
btnSubmit.onclick(function () {
    // 业务逻辑
});
  • 处理网络请求时
// 发起 ajax 网络请求,这里我用到了自己编写的真实的网络接口
let res = fetch("http://api.xxx.xxxx/api/v1/rest/info")
.then(res => res.json())
	.then(res => {
	    console.log(res);
});

// 返回的是一个 JSON 对象
{
    "code": 20000,
    "msg": "操作成功",
    "data": {
        "method": "get",
        "remoteIpAddress": "xx.xx.xx.xx",
        "requestPath": "/api/v1/rest/info"
    }
}

这里怎么有两个 then?这里实际上是简写了,使用了 ES6 的箭头函数语法,直接把网络请求拿到的值,当成函数的参数传递给下游处理

  • 一些内置的 JavaScript API 都可以看到 callback 的影子
// 第一个参数就是要处理的函数,第二个是延迟的时间
setTimeout(() => {
    // ...
}, 2000);

let arr = [1,2,3];

// 其实 function 也可以省略掉
arr.forEach(function (item, index) => {
    console.log(item, index);
})

再说一个,我们在 JavaScript 中经常会用到的数组的方法 —— forEach

在用 vscode,输入 forEach 的时候,就会弹出如下信息。这不就告诉了我们这个是怎么用的嘛

翻译过来,简单的说 forEach 函数,会接收三个参数,并且会 告诉 callbackfn 对数组中的每一个元素执行一次回调操作

所以这个 forEach 的案例告诉了我们什么?

callback 实际上也是一个函数,它也可以接收参数,并有返回值。只不过它的使用方式有点特殊。它一般在函数中使用,写出来就是下面这个样子

// 伪代码 => 一个函数接收另一个函数
fn(callbackFn());

四、如何写自己的回调函数

通过上面,我们知道了 callback 本身就是一个函数,调用方法的时候,使用函数接收。就能拿到回调结果

// ES6
const handleSomeEvent = (param1, param2, callback) {
    let res = param1 + param2;
   	// callback 就是在函数里面调用了另一个函数
    if (callback) {
        callback(res);
    }
}

handleSomeEvent(1,2, (num) => {
    console.log(num);	// 3
})

二、更新 ing~

想看到最新内容,欢迎关注 JavaScript 高级应用 第二弹

相关文章