JavaScript进阶:手写代码挑战(一)

​🌈个人主页:前端青山
🔥系列专栏:JavaScript篇
🔖人终将被年少不可得之物困其一生

依旧青山,本期给大家带来JavaScript篇专栏内容:JavaScript手写代码篇

#1024程序员节|征文#

在现代Web开发中,JavaScript 是不可或缺的编程语言。掌握其核心功能和原理对于开发者至关重要。本文通过手写实现JavaScript的一些关键功能和算法,帮助读者深入理解其工作原理,提升编程技能。无论你是初学者还是有经验的开发者,都能从中受益。

目录

1、手写一个Promise

2、手写call,apply,bind实现详解

4、手写一个防抖的实现

5、手写一个节流的实现

6、手写ajax  

7、手写JSONP的原理和实现

8、手写深拷贝

9、bind

10 call

11 apply


1、手写一个Promise

/*** 自定义Promise函数模块*/
(function () {
​const PENDING = 'pending'const RESOLVED = 'resolved'const REJECTED = 'rejected'
​
​/*** Promise构造函数* @param {*} executor 执行器函数*/function Promise(executor) {const self = this// 给promise对象指定status属性,初始值为pendingself.status = PENDING// 给promise对象指定一个用于存储结果数据的属性self.data = undefined// 每个元素的结构 { onResolved(){}, onRejected() }self.callbacks = []
​
​function resolve(value) {// 此处做判断,使得promise的状态只能修改一次if (self.status === PENDING) {// 将状态改为 resolvedself.status = RESOLVED// 保存value数据self.data = value
​// 如果有待执行的callback函数,立即异步执行回调if (self.callbacks.length > 0) {setTimeout(() => { // 表示在异步队列中执行self.callbacks.forEach(callbacksObj => {callbacksObj.onResolved(value)})}, 0);}}}function reject(reason) {// 此处做判断,使得promise的状态只能修改一次if (self.status === PENDING) {// 将状态改为 resolvedself.status = REJECTED// 保存value数据self.data = reason
​// 如果有待执行的callback函数,立即异步执行回调if (self.callbacks.length > 0) {setTimeout(() => { // 表示在异步队列中执行self.callbacks.forEach(callbacksObj => {callbacksObj.onRejected(reason)})}, 0);}}}
​// 立即执行器try {executor(resolve, reject);} catch (error) { // 如果执行器抛出异常,promise状态变为rejected状态reject(error)}}
​/*** Promise原型对象的then方法*/Promise.prototype.then = function (onResolved, onRejected) {const self = thisonResolved = typeof onResolved === 'function' ? onResolved : value => value// 指定默认的失败的回调(实现错误/异常穿透的关键点)onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
​
​return new Promise((resolve, reject) => {
​/*** 处理onResolve和onRejected函数* @param {*} callback */function resolvePromise(callback) {try {const result = callback(self.data)if (result instanceof Promise) {result.then(resolve, reject)} else if (result !== null && (typeof result === 'object' || typeof result === 'function')) {// 拿到result.thenconst then = result.then;if (typeof then === 'function') {then(resolve, reject)} else {resolve(then)}} else {resolve(result)}} catch (error) {reject(error)}}
​
​if (self.status === RESOLVED) {setTimeout(() => {/*** 1. 如果抛出异常,return 的 promise就会失败,reason就是error* 2. 如果回调函数返回不是promise,return的promise就会成功,value就是返回值* 3. 如果回调函数返回的是一个promise,return的promise的结果就是这个promise的结果,value就是返回值*/resolvePromise(onResolved)}, 0);} else if (self.status === REJECTED) {setTimeout(() => {resolvePromise(onRejected)}, 0);} else {self.callbacks.push({onResolved(value) {resolvePromise(onResolved)},onRejected(reason) {resolvePromise(onRejected)}})}})}
​/*** Promise原型对象的catch方法*/Promise.prototype.catch = function (onRejected) {return this.then(undefined, onRejected)}
​/*** Promise函数对象的resolve方法* * 返回一个指定结果的成功的promise*/Promise.resolve = function (value) {return new Promise((resolve, reject) => {if (value instanceof Promise) {value.then(resolve, reject)} else {resolve(value)}})}
​/*** Promise函数对象的reject方法*/Promise.reject = function (reason) {return new Promise((resolve, reject) => {reject(reason)})}
​/*** Promise函数对象的all方法*/Promise.all = function (promises) {// 判断数组if (!(promises instanceof Array)) {return Promise.reject(new Error('params must be a Array!'))}// 用来保存所有数据成功value的数组const resultArray = new Array(promises.length)// 用来保存成功数量的计数let resultCount = 0return new Promise((resolve, reject) => {// 遍历获取每个promise的结果promises.forEach((p, index) => {Promise.resolve(p).then(value => {resultCount++resultArray[index] = value// 如果全部都成功了,将return的promise变为成功if (resultCount === promises.length) {// 表示成功resolve(resultArray)}}, reason => {reject(reason)})})})}
​/*** Promise函数对象的race方法*/Promise.race = function (promises) {// 判断数组if (!(promises instanceof Array)) {return Promise.reject(new Error('params must be a Array!'))}
​return new Promise((resolve, reject) => {// 遍历数组promises.forEach((p, index) => {Promise.resolve(p).then(value => {resolve(value)}, reason => {reject(reason)})})})}
​// ---------------------------------------实现自己的方法---------------------------------------/*** 返回一个promise对象,它在指定的时间后才确定成功结果* @param {*} value * @param {*} time */Promise.resolveDelay = function (value, time) {return new Promise((resolve, reject) => {setTimeout(() => {if (value instanceof Promise) {value.then(resolve, reject)} else {resolve(value)}}, time);})}
​
​/*** 返回一个promise对象,它在指定的时间后才确定失败结果* @param {*} reason * @param {*} time */Promise.rejectDelay = function (reason, time) {return new Promise((resolve, reject) => {setTimeout(() => {reject(reason)}, time);})}
​
​
​// 向外暴露函数window.Promise = Promise
})(window)

2、手写call,apply,bind实现详解

call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数。
​let obj = {name: "一个"}function allName(firstName, lastName) {console.log(this)console.log(`我的全名是“${firstName}${this.name}${lastName}”`)}// 很明显此时allName函数是没有name属性的allName('我是', '前端') //我的全名是“我是前端”  this指向windowallName.call(obj, '我是', '前端') //我的全名是“我是一个前端” this指向obj
复制代码
apply
apply接收两个参数,第一个参数为函数上下文this,第二个参数为函数参数只不过是通过一个数组的形式传入的。
​
allName.apply(obj, ['我是', '前端'])//我的全名是“我是一个前端” this指向obj
复制代码
bind
bind 接收多个参数,第一个是bind返回值返回值是一个函数上下文的this,不会立即执行。
​let obj = {name: "一个"}function allName(firstName, lastName, flag) {console.log(this)console.log(`我的全名是"${firstName}${this.name}${lastName}"我的座右铭是"${flag}"`)}allName.bind(obj) //不会执行let fn = allName.bind(obj)fn('我是', '前端', '好好学习天天向上')// 也可以这样用,参数可以分开传。bind后的函数参数默认排列在原函数参数后边fn = allName.bind(obj, "你是")fn('前端', '好好学习天天向上')
复制代码
接下来搓搓手实现call、apply和bind
​
实现calllet Person = {name: 'Tom',say() {console.log(this)console.log(`我叫${this.name}`)}}// 先看代码执行效果Person.say() //我叫Tom Person1 = {name: 'Tom1'}// 我们尝试用原生方法call来实现this指向Person1Person.say.call(Person1) //我叫Tom1复制代码
通过第一次打印执行和第二次打印执行我发现,如果Person1有say方法那么Person1直接执行Person1.say() 结果就是我是Tom1,是的call就是这么实现的。 再看代码
​Function.prototype.MyCall = function(context) {//context就是demo中的Person1// 必须此时调用MyCall的函数是say方法,那么我们只需要在context上扩展一个say方法指向调用MyCall的say方法这样thisconsole.log(this)context.say = this //Mycall里边的this就是我们虚拟的say方法context.say()}// 测试Person.say.MyCall(Person1)//我叫Tom1
复制代码perfect!爆棚的满足感!不过拿脚趾头想想也不会这么简单,继续完善 我们自己找茬 1、call支持多个参数,有可能一个也不没有 2、考虑多参数时要把参数传给扩展方法。 3、给上下文定义的函数要保持唯一不能是say 4、扩展完我们需要吧自定义函数删除 接下来针对找茬问题一一解决let Person = {name: 'Tom',say() {console.log(this)console.log(`我叫${this.name}`)}}Person1 = {name: 'Tom1'}//如果没有参数Person.say.call()
复制代码
没有指定this,this指向window
我们也要这样
​Function.prototype.MyCall = function(context) {// 如果没有参数我们参考call的处理方式context = context || window//context就是demo中的Person1// 必须此时调用MyCall的函数是say方法,那么我们只需要在context上扩展一个say方法指向调用MyCall的say方法这样thiscontext.say = this //Mycall里边的this就是我们虚拟的say方法context.say()}Person.say.MyCall()
复制代码没毛病! 继续解决
// 找茬2:我们默认定义context.say = this  fn如果已经被占用 嘎嘎 sb了。 不怕 搞定它// say需要是一个唯一值 是不是突然想到es6的新类型 Symbol   fn = Symbol() 不过我们装逼不嫌事大 都说自己实现了function mySymbol(obj) {// 不要问我为什么这么写,我也不知道就感觉这样nblet unique = (Math.random() + new Date().getTime()).toString(32).slice(0, 8)// 牛逼也要严谨if (obj.hasOwnProperty(unique)) {return mySymbol(obj) //递归调用} else {return unique}}
//接下来我们一并把多参数和执行完删除自定义方法删除掉一块搞定Function.prototype.myCall1 = function(context) {// 如果没有传或传的值为空对象 context指向windowcontext = context || windowlet fn = mySymbol(context)context.fn = this //给context添加一个方法 指向this// 处理参数 去除第一个参数this 其它传入fn函数let arg = [...arguments].slice(1) //[...xxx]把类数组变成数组,arguments为啥不是数组自行搜索 slice返回一个新数组context.fn(...arg) //执行fndelete context.fn //删除方法}let Person = {name: 'Tom',say(age) {console.log(this)console.log(`我叫${this.name}我今年${age}`)}}Person1 = {name: 'Tom1'}Person.say.call(Person1,18)//我叫Tom1我今年18
复制代码
测试结果相当完美!
​
实现apply
接下来apply就简单多了,只有多参数时第二个参数是数组,就不一步步细说了。
​Function.prototype.myApply = function(context) {// 如果没有传或传的值为空对象 context指向windowif (typeof context === "undefined" || context === null) {context = window}let fn = mySymbol(context)context.fn = this //给context添加一个方法 指向this// 处理参数 去除第一个参数this 其它传入fn函数let arg = [...arguments].slice(1) //[...xxx]把类数组变成数组,arguments为啥不是数组自行搜索 slice返回一个新数组context.fn(arg) //执行fndelete context.fn //删除方法}
复制代码
实现bind
这个和call、apply区别还是很大的,容我去抽根烟回来收拾它 还是老套路先分析bind都能干些什么,有什么特点 1、函数调用,改变this 2、返回一个绑定this的函数 3、接收多个参数 4、支持柯里化形式传参 fn(1)(2)
​Function.prototype.bind = function(context) {//返回一个绑定this的函数,我们需要在此保存thislet self = this// 可以支持柯里化传参,保存参数let arg = [...arguments].slice(1)// 返回一个函数return function() {//同样因为支持柯里化形式传参我们需要再次获取存储参数let newArg = [...arguments]console.log(newArg)// 返回函数绑定this,传入两次保存的参数//考虑返回函数有返回值做了returnreturn self.apply(context, arg.concat(newArg))}}// 搞定测试let fn = Person.say.bind(Person1)fn()fn(18)

3、手写一个instanceOf

实现思路:
首先 instanceof 左侧必须是对象, 才能找到它的原型链
​
instanceof 右侧必须是函数, 函数才会prototype属性
​
迭代 , 左侧对象的原型不等于右侧的 prototype时, 沿着原型链重新赋值左侧
​​
复制代码
// [1,2,3] instanceof Array ---- true
​
// L instanceof R
// 变量R的原型 存在于 变量L的原型链上
function instance_of(L,R){    // 验证如果为基本数据类型,就直接返回falseconst baseType = ['string', 'number','boolean','undefined','symbol']if(baseType.includes(typeof(L))) { return false }let RP  = R.prototype;  //取 R 的显示原型L = L.__proto__;       //取 L 的隐式原型while(true){           // 无线循环的写法(也可以使 for(;;) )if(L === null){    //找到最顶层return false;}if(L === RP){       //严格相等return true;}L = L.__proto__;  //没找到继续向上一层原型链查找}
}

4、手写一个防抖的实现

!DOCTYPE html><html>
<!-- 防抖 -->
<!-- 防抖就是在n秒内 防止连续触发,在n秒内触发了下一次,那就重新计算 --><body><div id="content"style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div><script>let num = 1;let content = document.getElementById('content');/***非立即执行版**/function debounceNoAtOnce(func, wait) {let timeout;return function () {let context = this;let args = arguments;if (timeout) clearTimeout(timeout);timeout = setTimeout(() => {func.apply(context, args)}, wait);}};/***立即执行版**/function debounceAtOnce(func, wait) {let timeout;return function () {let context = this;let args = arguments;debuggerif (timeout) clearTimeout(timeout);let callNow = !timeout;timeout = setTimeout(() => {timeout = null;}, wait)if (callNow) func.apply(context, args)}}/***聚合版**/function debounce(func, wait, immediate) {let timeout;return function () {let context = this;let args = arguments;if (timeout) clearTimeout(timeout);if (immediate) {var callNow = !timeout;timeout = setTimeout(() => {timeout = null;}, wait)if (callNow) func.apply(context, args)} else {timeout = setTimeout(function () {func.apply(context, args)}, wait);}}}function count() {content.innerHTML = num++;};content.onmousemove = debounce(count, 1000, true);</script>
</body>
<script>
</script></html>
防抖的目的在于:n秒内点击多少次都算一次+每次点击都重新计算时间

5、手写一个节流的实现

<!DOCTYPE html><html>
<!-- 节流 -->
<!-- 节流是为了固定的时间段内只能点击一次 --><body><div id="content"style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div><script>let num = 1;let content = document.getElementById('content');/***throttleTime 时间戳版**/throttleTime = function (func, wait) {let previde = 0;return function () {let nowDate = Date.now();if (nowDate - previde > wait) {func();previde = nowDate;}}}/***throttleSet 定时器版**/throttleSet = function (func, wait) {let timeout;return function () {if (!timeout) {timeout = setTimeout(() => {timeout = falsefunc()}, wait)}}}function count() {content.innerHTML = num++;};content.onmousemove = throttleSet(count, 1000);</script>
</body>
<script>
</script></html>

节流的目的在于:固定时间段内只能点击一次

应用场景:输入框输入+提交/确定

6、手写ajax  

function createXMLHTTPRequest() {     //1.创建XMLHttpRequest对象     //这是XMLHttpReuquest对象无部使用中最复杂的一步     //需要针对IE和其他类型的浏览器建立这个对象的不同方式写不同的代码     var xmlHttpRequest;  if (window.XMLHttpRequest) {     //针对FireFox,Mozillar,Opera,Safari,IE7,IE8     xmlHttpRequest = new XMLHttpRequest();     //针对某些特定版本的mozillar浏览器的BUG进行修正     if (xmlHttpRequest.overrideMimeType) {     xmlHttpRequest.overrideMimeType("text/xml");     }     } else if (window.ActiveXObject) {     //针对IE6,IE5.5,IE5     //两个可以用于创建XMLHTTPRequest对象的控件名称,保存在一个js的数组中     //排在前面的版本较新     var activexName = [ "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" ];     for ( var i = 0; i < activexName.length; i++) {     try {     //取出一个控件名进行创建,如果创建成功就终止循环     //如果创建失败,回抛出异常,然后可以继续循环,继续尝试创建     xmlHttpRequest = new ActiveXObject(activexName[i]);   if(xmlHttpRequest){  break;  }  } catch (e) {     }     }     }     return xmlHttpRequest;  }  

7、手写JSONP的原理和实现

JSONP实现原理

jsonp,其实就是单纯为了实现跨域请求而创造的一个欺骗(trick)。

虽然,因为同源策略的影响,不能通过XMLHttpRequest请求不同域上的数据(Cross -origin reads)。但是,在页面上引入不同域上的js脚本文件却是可以的(Cross -origin embedding)。因此,在js文件载入完毕之后,触发回调,可以将需要的data作为参数传入 注意,实现方式(需前后端配合)

优点

兼容性好(兼容低版本IE)

缺点

JSONP只支持GET请求,XMLHttpRequest相对于JSONP有着更好的错误处理机制。

实现

1.服务端采用的是Node,服务端处理请求方法如下

router.get('/', function(req, res, next) {
​
​
​console.log('收到客户端的请求:', req.query);
​
​
​// 传回到客户端的数据
​
​
​let data = JSON.stringify({
​
​
​'status':200,
​
​
​'result':{
​
​
​'name':'柳成荫',
​
​
​'site':'123456'
​
​
​}
​
​
​});
​
​
​// 获取方法名称 - 这是客户端传过来的方法名参数
​
​
​// 因为这个方法名必须是客户端有的,必须要客户端告诉服务端是哪个方法
​
​
​let methodName = req.query.callback;
​
​
​let methodStr = methodName + '(' + data + ')';
​
​
​// 快速结束没有任何数据的响应,客户端会执行这个方法,从而获取到服务端返回的数据
​
​
​res.end(methodStr)
​
​
​
});

2.封装JSONP

(function (w) {
​
​
​/**
​
​
​* jsonp的实现
​
​
​* @param {Object}option
​
​
​*/
​
​
​function jsonp(option) {
​
​
​// 把success函数挂载在全局的getDate函数上
​
​
​w.getData = option.success;
​
​
​// 处理url,拼接参数 - 回调方法是getData
​
​
​option.url = option.url + '?callback=getData';
​
​
​// 创建script标签,并插入body
​
​
​let scriptEle = document.createElement('script');
​
​
​scriptEle.src = option.url;
​
​
​document.body.appendChild(scriptEle);
​
​
​}
​
​
​// 全局挂载一个jsonp函数
​
​
​w.jsonp = jsonp;
​
​
​})(window);
​
​
​​
​
​/**
​
​
​* 把对象转换成拼接字符串
​
​
​* 把形如
​
​
​data:{
​
​
​"sex":"男",
​
​
​"name":"九月"
​
​
​}
​
​
​转换成sex=男&name=九月
​
​
​* @param paramObj 对象参数
​
​
​* @param words
​
​
​* @returns {string} 字符串
​
​
​*/
​
​
​function getStrWithObject(paramObj,words){
​
​
​let resArr = [];
​
​
​// 1.转换对象
​
​
​for(let key in paramObj){
​
​
​let str = key + '=' + paramObj[key];
​
​
​resArr.push(str);
​
​
​}
​
​
​resArr.push(words);
​
​
​// 3.数组转换成字符串
​
​
​return resArr.join("&");
​
​
​}

看似代码没有任何问题。但是,我们测试一下,多次调用jsonp方法。

jsonp({
​
​
​url:'http://localhost:3000/',
​
​
​data:{
​
​
​"sex":"男",
​
​
​"name":"九月"
​
​
​},
​
​
​success:function (data) {
​
​
​console.log(data);
​
​
​alert(1);
​
​
​}
​
​
​});
​
​
​​
​
​jsonp({
​
​
​url:'http://localhost:3000/',
​
​
​data:{
​
​
​"sex":"男",
​
​
​"name":"九月"
​
​
​},
​
​
​success:function (data) {
​
​
​console.log(data);
​
​
​alert(2);
​
​
​}
​
​
​});

结果会怎么样?的确,还是会执行2次,但是每次执行的都是第二个jsonp方法。这是为什么?

问题出现在这里,多次调用,会发生函数覆盖。

img

解决方案就是让每一次调用函数名不一致,在JS里有很多方法,比如通过随机数、时间戳之类的。这里采用的是随机数,修改如下。

// 0.产生不同的函数名 - 解决了调用多次请求,造成覆盖的问题
​
​
​
// 如果方法名相同,调用多次,都会执行最后一个,出现覆盖现象
​
​
​
let callBackName = 'lcy' + Math.random().toString().substr(2) 
​
​
​
+ Math.random().toString().substr(2);
​
​
​
// 把success函数挂载在全局的getDate函数上
​
​
​
w[callBackName] = option.success;
​
​
​
// 处理url,拼接参数
option.url = option.url + '?' + getStrWithObject(option.data,'callback='+callBackName);

好,现在看似完美解决。然而,我们发现,每次调用jsonp方法,都会在body里插入一个script标签。我们并不希望在调用jsonp之后,添加这样一个标签,我们需要在调用完之后,将其移除。

怎么做呢?我们在将success函数挂载在全局时,我们在success外层再套个函数。在这个函数里调用success方法之后,即已经请求到数据之后,我们就去把这个script标签给它移除就行了。修改如下

// 1.函数挂载在全局
​
​
​
w[callBackName] = function(data){
​
​
​option.success(data);
​
​
​// 删除script标签
​
​
​document.body.removeChild(scriptEle);
​
​
​
};
整个过程就是这样,以下是完整代码。(function (w) {
​
​
​/**
​
​
​* jsonp的实现
​
​
​* @param {Object}option
​
​
​*/
​
​
​function jsonp(option) {
​
​
​// 0.产生不同的函数名 - 解决了调用多次请求,造成覆盖的问题
​
​
​// 如果方法名相同,调用多次,都会执行最后一个,出现覆盖现象
​
​
​let callBackName = 'lcy' + Math.random().toString().substr(2) + Math.random().toString().substr(2);
​
​
​// 1.函数挂载在全局
​
​
​w[callBackName] = function(data){
​
​
​option.success(data);
​
​
​// 删除script标签
​
​
​document.body.removeChild(scriptEle);
​
​
​};
​
​
​// 2.处理url
​
​
​option.url = option.url + '?' + getStrWithObject(option.data,'callback='+callBackName);
​
​
​// 3.创建script标签插入body
​
​
​let scriptEle = document.createElement('script');
​
​
​scriptEle.src = option.url;
​
​
​document.body.appendChild(scriptEle);
​
​
​}
​
​
​w.jsonp = jsonp;
​
​
​})(window);
​
​
​​
​
​/**
​
​
​* 把对象转换成拼接字符串
​
​
​* @param paramObj 对象参数
​
​
​* @returns {string} 字符串
​
​
​*/
​
​
​function getStrWithObject(paramObj,words){
​
​
​let resArr = [];
​
​
​// 1.转换对象
​
​
​for(let key in paramObj){
​
​
​let str = key + '=' + paramObj[key];
​
​
​resArr.push(str);
​
​
​}
​
​
​resArr.push(words);
​
​
​// 3.数组转换成字符串
​
​
​return resArr.join("&");
​
​
​}

8、手写深拷贝

ar a = {name: "muyiy",book: {title: "You Don't Know JS",price: "45"},a1: undefined,a2: null,a3: 123
}
// hasOwnProperty表示是否有自己的属性。这个方法会查找一个对象是否有某个属性,但是不会去查找它的原型链。
function cloneDeep1(source) {var target = {};for (var key in source) {if (source.hasOwnProperty(key)) {if (typeof source[key] === 'object') {target[key] = cloneDeep1(source[key]); // 注意这里} else {target[key] = source[key];}}}return target;
}
​
// 使用上面测试用例测试一下
console.log(a)
var b = cloneDeep1(a);
a.name = "前端进阶";
a.book.price = "55";
console.log(b);
​
考虑再全一些的深拷贝
​
​
function deepClone(obj,hash = new WeakMap()){ //递归实现if(obj instanceof RegExp) return new RegExp(obj);if(obj instanceof Date) return new Date(obj);if(obj === null || typeof obj != "object"){// 普通数据类型return obj;}if(hash.has(obj)){return hash.get(obj);}// 下面是数组和对象的判断let t = new obj.constructor();hash.set(obj,t);for(let key in obj){//  递归if(obj.hasOwnProperty(key)){ //是否是自身的属性t[key] = deepClone(obj[key],hash)}}return t;
}

9、bind

function print() {console.log(this.name, ...arguments);
}
​
const obj = {name: 'mxin',
};
​
// Function.prototype.__bind()
console.log('Function.prototype.__bind()');
​
// 直接调用,返回原函数拷贝,this 指向 obj
const F = print.__bind(obj, 26);
F(178); // mxin, 26, 178
​
// new 情况
const _obj = new F(145); // undefined, 26, 145
console.log(_obj); // print {}
​
// Function.prototype.bind()
console.log('Function.prototype.bind()');
​
const Fn = print.bind(obj, 26);
Fn(178); // mxin, 26, 178
​
const __obj = new Fn(145); // undefined, 26, 145
console.log(__obj); // print {}

10 call

/*** 模拟 call* 使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数* @param {object} ctx* @param  {...any} args* @returns {any} 调用 this 的返回值,若无有返回值,则返回 undefined*/
Function.prototype.__call = function (ctx, ...args) {if (typeof this !== 'function') throw new TypeError('Error');
​// 考虑 null 情况,参数默认赋值会无效if (!ctx) ctx = window;
​// 将 this 函数保存在 ctx 上ctx.fn = this;
​// 传参执行并保存返回值const res = ctx.fn(...args);
​// 删除 ctx 上的 fndelete ctx.fn;return res;
};
​
// ------------------------------ 测试 ------------------------------
function Product(name, price) {this.name = name;this.price = price;
}
​
// Function.prototype.__call()
console.log('Function.prototype.__call()');
​
function Food(name, price) {Product.__call(this, name, price);this.category = 'food';
}
const food = new Food('cheese', 5);
console.log(food);
// Food {name: "cheese", price: 5, category: "food"}
//   category: "food"
//   name: "cheese"
//   price: 5
//   __proto__:
//     constructor: ƒ Food(name, price)
//     __proto__: Object
​
// Function.prototype.call()
console.log('Function.prototype.call()');
​
function Toy(name, price) {Product.call(this, name, price);this.category = 'toy';
}
const toy = new Toy('car', 10);
console.log(toy);
// Toy {name: "car", price: 10, category: "toy"}
//   category: "toy"
//   name: "car"
//   price: 10
//   __proto__:
//     constructor: ƒ Toy(name, price)
//     __proto__: Object

11 apply

/*** 模拟 apply* 调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数* @param {object} ctx* @param {} args*/
Function.prototype.__apply = function (ctx, args) {if (typeof this !== 'function') throw new TypeError('Error');
​// 考虑 null 情况,参数默认赋值会无效if (!ctx) ctx = window;
​// 将 this 函数保存在 ctx 上ctx.fn = this;
​// 传参执行并保存返回值const result = ctx.fn(...args);
​// 删除 ctx 上的 fndelete ctx.fn;return result;
};
​
// ------------------------------ 测试 ------------------------------
​
const numbers = [5, 6, 2, 3, 7];
​
// Function.prototype.__apply()
console.log('Function.prototype.__apply()');
​
const max = Math.max.__apply(null, numbers);
console.log(max); // 7
​
// Function.prototype.apply()
console.log('Function.prototype.apply()');
const min = Math.min.apply(null, numbers);
console.log(min); // 2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/57602.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中国移动机器人将投入养老场景;华为与APUS共筑AI医疗多场景应用

AgeTech News 一周行业大事件 华为与APUS合作&#xff0c;共筑AI医疗多场景应用 中国移动展出人形机器人&#xff0c;预计投入养老等场景 作为科技与奥富能签约&#xff0c;共拓智能适老化改造领域 天与养老与香港科技园&#xff0c;共探智慧养老新模式 中山大学合作中国…

[Python学习日记-53] Python 中的正则表达式模块 —— re

[Python学习日记-53] Python 中的正则表达式模块 —— re 简介 re 模块 练习 简介 我们在编程的时候经常会遇到想在一段文字当中找出电话号码、身份证号、身高、年龄之类的信息&#xff0c;就像下面的数据一样 # 文件名&#xff1a;美丽学姐联系方式.txt 姓名 地区 …

微信小程序美团点餐

引言&#xff1a;外卖已经成为了都市人的必备&#xff0c;在无数个来不及&#xff08;懒得&#xff09;做饭的时刻拯救孤单寂寞的胃。美团外卖无疑是外卖届的领头羊&#xff0c;它的很多功能与设计都值得我们学习。本文将从五个方面&#xff0c;对美团外卖展开产品分析&#xf…

【ArcGIS Pro实操第4期】绘制三维地图

【ArcGIS Pro实操第4期】绘制三维地图 ArcGIS Pro绘制三维地图-以DEM高程为例参考 如何使用ArcGIS Pro将栅格数据用三维的形式进行表达&#xff1f;在ArcGIS里可以使用ArcScene来实现&#xff0c;ArcGIS Pro实现原理跟ArcScene一致。由于Esri未来将不再对ArcGIS更新&#xff0c…

深入浅出神经网络:从基础原理到高级应用

第5章 神经网络 更加详细内容可以看这篇文章 5.1 神经元模型 神经网络的基本单元是神经元模型。神经元模拟了生物神经元的行为&#xff0c;通过接收输入信号&#xff0c;进行加权求和&#xff0c;然后经过激活函数输出结果。 数学上&#xff0c;一个简单的神经元可以表示为&…

pipeline开发笔记

pipeline开发笔记 jenkins常用插件Build Authorization Token Root配置GitLab的webhooks(钩子)配置构建触发器--示例 piblish over sshBlue OceanWorkspace Cleanup PluginGit插件PipelineLocalization: Chinese (Simplified) --中文显示Build Environment Plugin 显示构建过程…

ArcGIS 10.8 安装教程

目录 一、ArcGIS10.8二、安装链接三、安装教程四、ArcGIS实战 &#xff08;一&#xff09;ArcGIS10.8 1. 概述 ArcGIS 10.8是由美国Esri公司开发的GIS平台&#xff0c;用于处理、分析、显示和管理地理数据&#xff0c;并实现数据共享。它具有新特性和功能&#xff0c;性能更…

iOS MPNowPlayingInfoCenter 通知栏、锁屏 显示当前播放的媒体信息

前言 MPNowPlayingInfoCenter 是 iOS 框架 MediaPlayer 中的一个类&#xff0c;主要用于管理锁屏界面、控制中心、通知中心中显示的“当前播放”媒体信息。它允许开发者向用户展示正在播放的音乐或媒体信息&#xff0c;并控制媒体播放。 通过 MPNowPlayingInfoCenter&#xf…

新电脑Win11家庭中文版跳过联网激活方法(教程)

预装Win11家庭中文版的新电脑&#xff0c;如何跳过联网激活&#xff1b;由于微软限制必须要联网激活&#xff0c;需要使用已有的微软账户登入或者注册新的微软账户后才可以继续开机使用&#xff0c;Win11联网后系统会自动激活。下面介绍一下初次开机初始化电脑时如何跳过联网激…

猫咪掉毛还容易应激,哪款宠物空气净化器可以吸毛且低噪?

今年的双十一第一波优惠我没有抢&#xff0c;因为我在犹豫我真的必须要买宠物空气净化器&#xff0c;但是会不会有很多副作用等等问题&#xff0c;让我一直不敢下手。 一直犹豫买不买是因为我家养了一只爱掉毛的小猫咪&#xff0c;家里每天都是想着要清理猫咪掉下来的猫毛&…

又是一年 1024

今天是 1024 程序员节&#xff0c;现在是一名大数据讲师&#xff0c;我和往常一样&#xff0c;依旧在讲课中度过。对于很多程序员来说&#xff0c;这一天也许是属于代码、调试和无数行 SQL 查询的&#xff0c;而对于我来说&#xff0c;虽然工作内容不同&#xff0c;却也和数据、…

软考算法——线性表、栈和队列、串、数组、矩阵和广义表

软考算法&#xff08;一&#xff09; 线性表定义顺序表单链表循环链表双向链表 性能分析线性表插入删除操作 栈和队列栈队列循环队列 串、数组、矩阵和广义表串串的基本操作串的存储结构 数组数组的存储地址计算 矩阵——稀疏矩阵上三角矩阵下三角矩阵 广义表 线性表 定义 线性…

设置K8s管理节点异常容忍时间

说明 每个节点上的 kubelet 需要定时向 apiserver 上报当前节点状态&#xff0c;如果两者间网络异常导致心跳终端&#xff0c;kube-controller-manager 中的 NodeController 会将该节点标记为 Unknown 或 Unhealthy&#xff0c;持续一段时间异常状态后 kube-controller-manage…

软考——计算机网络概论

文章目录 &#x1f550;计算机网络分类1️⃣通信子网和资源子网2️⃣网络拓扑结构3️⃣ 计算机网络分类3&#xff1a;LAN MAN WAN4️⃣其他分类方式 &#x1f551;OSI 和 TCP/IP 参考模型1️⃣OSI2️⃣TCP/IP&#x1f534;TCP/IP 参考模型对应协议 3️⃣OSI 和 TCP/IP 模型对应…

AUTOSAR_EXP_ARAComAPI的6章笔记(4)

☞返回总目录 相关总结&#xff1a;《AUTOSAR 自适应应用中原始数据流传输的使用方法》总结 6.4 原始数据流传输的使用方法 本章描述了原始数据流&#xff08;RawDataStreams&#xff09;在 AUTOSAR 自适应应用程序中的使用方法。 目前&#xff0c;原始数据流传输在单播 / …

WSL2-轻量级AI训练场景最佳生产环境

WSL2 只适用于 Win 10 、Win11 在运行 AI 软件、AI 模型训练&#xff0c;Linux 是最佳的操作系统。 在运行各种软件&#xff0c;如&#xff1a;Stable Diffusion Web UI 等&#xff0c;使用 Docker 容器运行也更方便后期的快速复用&#xff0c;同样的 Docker 容器在 Linux 中…

基于springboot的网上服装商城推荐系统的设计与实现

基于springboot的网上服装商城推荐系统的设计与实现 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;idea 源码获取&#xf…

安灯系统助力汽车零部件工厂快速解决生产异常

在汽车零部件制造领域&#xff0c;高效的生产管理和快速解决异常情况是确保产品质量和生产进度的关键。而安灯系统的应用&#xff0c;正为汽车零部件工厂带来了全新的变革&#xff0c;助力其快速解决生产异常。 汽车零部件工厂的生产报工产线看板直观地反映出生产的各项关键数据…

基于SpringBoot的“心灵治愈交流平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“心灵治愈交流平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能界面图 登录、用户注册界面图 心灵专…

【有啥问啥】智能座舱中的儿童遗留检测(CPD,Child Presence Detection)技术详解

智能座舱中的儿童遗留检测&#xff08;CPD&#xff0c;Child Presence Detection&#xff09;技术详解 引言 儿童遗留检测&#xff08;CPD&#xff0c;Child Presence Detection&#xff09;系统是一项旨在保护儿童免受因被遗忘在车内而导致的热中暑危险的重要安全技术。近年…