Dieser Artikel behandelt verschiedene wichtige Handschrifttests, die häufig in Front-End-Interviews getestet werden.
RECORMENDE:
《2021 PHP-Interviewfragen Zusammenfassung (Sammlung) 》 《《
2021 Front-End-Interview Fragen Zusammenfassung (Sammlung)
Handschriftliches natives Ajax (Verständnis der Ajax-Prinzipien und http-Anfragen Das Verständnis der Methode konzentriert sich auf die Implementierung von Get- und Post-Anfragen.
Instanz von
Während des Suchvorgangs wird die Prototypenkette der Variablen auf der linken Seite durchlaufen, bis der Prototyp der Variablen auf der rechten Seite gefunden wird. Die Suche schlägt fehl und es wird „false“ zurückgegeben. Implementieren Sie die Map-Methode des Arrays
map des Arrays. Die Methode ()gibt ein neues Array zurück. Jedes Element in diesem neuen Array entspricht dem Rückgabewert des entsprechenden Elements im ursprünglichen Array, nachdem die bereitgestellte Funktion einmal aufgerufen wurde . Verwendung:
let myInstanceof = (target,origin) => { while(target) { if(target.__proto__===origin.prototype) { return true } target = target.__proto__ } return false } let a = [1,2,3] console.log(myInstanceof(a,Array)); // true console.log(myInstanceof(a,Object)); // true
Native Implementierung:
const a = [1, 2, 3, 4]; const b = array1.map(x => x * 2); console.log(b); // Array [2, 4, 6, 8]
3. Reduce implementiert die Map-Methode des Arrays
Verwenden Sie die integrierte Reduce-Methode des Arrays, um die Map-Methode zu implementieren und die Beherrschung zu untersuchen des Reduzierprinzips Array.prototype.myMap = function(fn, thisValue) {
let res = []
thisValue = thisValue||[]
let arr = this
for(let i in arr) {
res.push(fn(arr[i]))
}
return res
}
reduce() empfängt eine Funktion als Akkumulator (von links nach rechts) und wird schließlich zu einem Wert . Es ist ein weiteres neues Array in ES5.
Parameter:
callback (eine Funktion, die für jedes Element im Array aufgerufen wird und vier Funktionen akzeptiert :)
previousValue (der Rückgabewert des letzten Zeitpunkt, zu dem die Callback-Funktion aufgerufen wurde, oder der Anfangswert) currentValue (das Array-Element, das gerade verarbeitet wird)currentIndex (der Index des Array-Elements, das gerade verarbeitet wird)
array (das Array, das die Methode Reduce() aufruft)Array.prototype.myMap = function(fn,thisValue){ var res = []; thisValue = thisValue||[]; this.reduce(function(pre,cur,index,arr){ return res.push(fn.call(thisValue,cur,index,arr)); },[]); return res; } var arr = [2,3,1,5]; arr.myMap(function(item,index,arr){ console.log(item,index,arr); })
function reduce(arr, cb, initialValue){ var num = initValue == undefined? num = arr[0]: initValue; var i = initValue == undefined? 1: 0 for (i; i
Wenn die Curry-Funktion genügend Parameter erhält, führt sie die ursprüngliche Funktion aus. Wie kann festgestellt werden, wann genügend Parameter erreicht sind? Es gibt zwei Ideen:
Ermitteln Sie die Anzahl der formalen Parameter der Funktion über das Längenattribut der Funktion. Die Anzahl der formalen Parameter ist die Anzahl der erforderlichen Parameter.
Wenn Sie die Currying-Tool-Funktion aufrufen, Geben Sie die erforderliche Anzahl von Parametern manuell an.
Kombinieren Sie diese beiden Punkte, um eine einfache Curry-Funktion zu implementieren:
let a = [1,[2,3]]; a.flat(); // [1,2,3] a.flat(1); //[1,2,3]
Lassen Sie es uns überprüfen: let a = [1,[2,3,[4,[5]]]];
a.flat(Infinity); // [1,2,3,4,5] a是4维数组
Wenn wir beispielsweise einen Platzhalter übergeben, überspringen die in diesem Aufruf übergebenen Parameter den Platzhalter und die Position des Platzhalters wird durch die Parameter des nächsten Aufrufs gefüllt, wie zum Beispiel diesen:
Schauen Sie sich direkt an offizielle Website Beispiel:
function flatten(arr) { var res = []; for (let i = 0, length = arr.length; i Überprüfen:;<p></p><pre class="brush:php;toolbar:false">/** * 将函数柯里化 * @param fn 待柯里化的原函数 * @param len 所需的参数个数,默认为原函数的形参个数 */ function curry(fn,len = fn.length) { return _curry.call(this,fn,len) } /** * 中转函数 * @param fn 待柯里化的原函数 * @param len 所需的参数个数 * @param args 已接收的参数列表 */ function _curry(fn,len,...args) { return function (...params) { let _args = [...args,...params]; if(_args.length >= len){ return fn.apply(this,_args); }else{ return _curry.call(this,fn,len,..._args) } } }
7. Deep Copy implementieren
Der Unterschied zwischen flachem Kopieren und tiefem Kopieren :
Flache Kopie: Kopiert nur eine Ebene und kopiert nur Referenzen auf tieferen Objektebenen.
Tiefe Kopie: Kopiert mehrere Ebenen,
Daten auf jeder Ebene werden kopiert. Auf diese Weise hat die Änderung des Kopierwerts keine Auswirkungen auf andere ObjekteES6-Methode für flaches Kopieren: Object.assign(target,...sources)let _fn = curry(function(a,b,c,d,e){ console.log(a,b,c,d,e) }); _fn(1,2,3,4,5); // print: 1,2,3,4,5 _fn(1)(2)(3,4,5); // print: 1,2,3,4,5 _fn(1,2)(3,4)(5); // print: 1,2,3,4,5 _fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5
8 Handschriftlicher Aufruf, Anwenden, Binden
手写call
Function.prototype.myCall=function(context=window){ // 函数的方法,所以写在Fuction原型对象上 if(typeof this !=="function"){ // 这里if其实没必要,会自动抛出错误 throw new Error("不是函数") } const obj=context||window //这里可用ES6方法,为参数添加默认值,js严格模式全局作用域this为undefined obj.fn=this //this为调用的上下文,this此处为函数,将这个函数作为obj的方法 const arg=[...arguments].slice(1) //第一个为obj所以删除,伪数组转为数组 res=obj.fn(...arg) delete obj.fn // 不删除会导致context属性越来越多 return res }
//用法:f.call(obj,arg1) function f(a,b){ console.log(a+b) console.log(this.name) } let obj={ name:1 } f.myCall(obj,1,2) //否则this指向window obj.greet.call({name: 'Spike'}) //打出来的是 Spike
手写apply(arguments[this, [参数1,参数2.....] ])
Function.prototype.myApply=function(context){ // 箭头函数从不具有参数对象!!!!!这里不能写成箭头函数 let obj=context||window obj.fn=this const arg=arguments[1]||[] //若有参数,得到的是数组 let res=obj.fn(...arg) delete obj.fn return res } function f(a,b){ console.log(a,b) console.log(this.name) } let obj={ name:'张三' } f.myApply(obj,[1,2]) //arguments[1]
手写bind
this.value = 2 var foo = { value: 1 }; var bar = function(name, age, school){ console.log(name) // 'An' console.log(age) // 22 console.log(school) // '家里蹲大学' } var result = bar.bind(foo, 'An') //预置了部分参数'An' result(22, '家里蹲大学') //这个参数会和预置的参数合并到一起放入bar中
简单版本
Function.prototype.bind = function(context, ...outerArgs) { var fn = this; return function(...innerArgs) { //返回了一个函数,...rest为实际调用时传入的参数 return fn.apply(context,[...outerArgs, ...innerArgs]); //返回改变了this的函数, //参数合并 } }
new失败的原因:
例:
// 声明一个上下文 let thovino = { name: 'thovino' } // 声明一个构造函数 let eat = function (food) { this.food = food console.log(`${this.name} eat ${this.food}`) } eat.prototype.sayFuncName = function () { console.log('func name : eat') } // bind一下 let thovinoEat = eat.bind(thovino) let instance = new thovinoEat('orange') //实际上orange放到了thovino里面 console.log('instance:', instance) // {}
生成的实例是个空对象
在new
操作符执行时,我们的thovinoEat
函数可以看作是这样:
function thovinoEat (...innerArgs) { eat.call(thovino, ...outerArgs, ...innerArgs) }
在new操作符进行到第三步的操作thovinoEat.call(obj, ...args)
时,这里的obj
是new操作符自己创建的那个简单空对象{}
,但它其实并没有替换掉thovinoEat
函数内部的那个上下文对象thovino
。这已经超出了call
的能力范围,因为这个时候要替换的已经不是thovinoEat
函数内部的this
指向,而应该是thovino
对象。
换句话说,我们希望的是new
操作符将eat
内的this
指向操作符自己创建的那个空对象。但是实际上指向了thovino
,new
操作符的第三步动作并没有成功!
可new可继承版本
Function.prototype.bind = function (context, ...outerArgs) { let that = this; function res (...innerArgs) { if (this instanceof res) { // new操作符执行时 // 这里的this在new操作符第三步操作时,会指向new自身创建的那个简单空对象{} that.call(this, ...outerArgs, ...innerArgs) } else { // 普通bind that.call(context, ...outerArgs, ...innerArgs) } } res.prototype = this.prototype //!!! return res }
9. 手动实现new
new的过程文字描述:
创建一个空对象 obj;
将空对象的隐式原型(proto)指向构造函数的prototype。
使用 call 改变 this 的指向
如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。
function Person(name,age){ this.name=name this.age=age } Person.prototype.sayHi=function(){ console.log('Hi!我是'+this.name) } let p1=new Person('张三',18) ////手动实现new function create(){ let obj={} //获取构造函数 let fn=[].shift.call(arguments) //将arguments对象提出来转化为数组,arguments并不是数组而是对象 !!!这种方法删除了arguments数组的第一个元素,!!这里的空数组里面填不填元素都没关系,不影响arguments的结果 或者let arg = [].slice.call(arguments,1) obj.__proto__=fn.prototype let res=fn.apply(obj,arguments) //改变this指向,为实例添加方法和属性 //确保返回的是一个对象(万一fn不是构造函数) return typeof res==='object'?res:obj } let p2=create(Person,'李四',19) p2.sayHi()
细节:
[].shift.call(arguments) 也可写成: let arg=[...arguments] let fn=arg.shift() //使得arguments能调用数组方法,第一个参数为构造函数 obj.__proto__=fn.prototype //改变this指向,为实例添加方法和属性 let res=fn.apply(obj,arg)
10. 手写promise(常见promise.all, promise.race)
// Promise/A+ 规范规定的三种状态 const STATUS = { PENDING: 'pending', FULFILLED: 'fulfilled', REJECTED: 'rejected' } class MyPromise { // 构造函数接收一个执行回调 constructor(executor) { this._status = STATUS.PENDING // Promise初始状态 this._value = undefined // then回调的值 this._resolveQueue = [] // resolve时触发的成功队列 this._rejectQueue = [] // reject时触发的失败队列 // 使用箭头函数固定this(resolve函数在executor中触发,不然找不到this) const resolve = value => { const run = () => { // Promise/A+ 规范规定的Promise状态只能从pending触发,变成fulfilled if (this._status === STATUS.PENDING) { this._status = STATUS.FULFILLED // 更改状态 this._value = value // 储存当前值,用于then回调 // 执行resolve回调 while (this._resolveQueue.length) { const callback = this._resolveQueue.shift() callback(value) } } } //把resolve执行回调的操作封装成一个函数,放进setTimeout里,以实现promise异步调用的特性(规范上是微任务,这里是宏任务) setTimeout(run) } // 同 resolve const reject = value => { const run = () => { if (this._status === STATUS.PENDING) { this._status = STATUS.REJECTED this._value = value while (this._rejectQueue.length) { const callback = this._rejectQueue.shift() callback(value) } } } setTimeout(run) } // new Promise()时立即执行executor,并传入resolve和reject executor(resolve, reject) } // then方法,接收一个成功的回调和一个失败的回调 function then(onFulfilled, onRejected) { // 根据规范,如果then的参数不是function,则忽略它, 让值继续往下传递,链式调用继续往下执行 typeof onFulfilled !== 'function' ? onFulfilled = value => value : null typeof onRejected !== 'function' ? onRejected = error => error : null // then 返回一个新的promise return new MyPromise((resolve, reject) => { const resolveFn = value => { try { const x = onFulfilled(value) // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } } } const rejectFn = error => { try { const x = onRejected(error) x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } switch (this._status) { case STATUS.PENDING: this._resolveQueue.push(resolveFn) this._rejectQueue.push(rejectFn) break; case STATUS.FULFILLED: resolveFn(this._value) break; case STATUS.REJECTED: rejectFn(this._value) break; } }) } catch (rejectFn) { return this.then(undefined, rejectFn) } // promise.finally方法 finally(callback) { return this.then(value => MyPromise.resolve(callback()).then(() => value), error => { MyPromise.resolve(callback()).then(() => error) }) } // 静态resolve方法 static resolve(value) { return value instanceof MyPromise ? value : new MyPromise(resolve => resolve(value)) } // 静态reject方法 static reject(error) { return new MyPromise((resolve, reject) => reject(error)) } // 静态all方法 static all(promiseArr) { let count = 0 let result = [] return new MyPromise((resolve, reject) => { if (!promiseArr.length) { return resolve(result) } promiseArr.forEach((p, i) => { MyPromise.resolve(p).then(value => { count++ result[i] = value if (count === promiseArr.length) { resolve(result) } }, error => { reject(error) }) }) }) } // 静态race方法 static race(promiseArr) { return new MyPromise((resolve, reject) => { promiseArr.forEach(p => { MyPromise.resolve(p).then(value => { resolve(value) }, error => { reject(error) }) }) }) } }
11. 手写原生AJAX
步骤
创建 XMLHttpRequest 实例
发出 HTTP 请求
服务器返回 XML 格式的字符串
JS 解析 XML,并更新局部页面
不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON。
了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。
version 1.0:
myButton.addEventListener('click', function () { ajax() }) function ajax() { let xhr = new XMLHttpRequest() //实例化,以调用方法 xhr.open('get', 'https://www.google.com') //参数2,url。参数三:异步 xhr.onreadystatechange = () => { //每当 readyState 属性改变时,就会调用该函数。 if (xhr.readyState === 4) { //XMLHttpRequest 代理当前所处状态。 if (xhr.status >= 200 && xhr.status <p>promise实现</p><pre class="brush:php;toolbar:false">function ajax(url) { const p = new Promise((resolve, reject) => { let xhr = new XMLHttpRequest() xhr.open('get', url) xhr.onreadystatechange = () => { if (xhr.readyState == 4) { if (xhr.status >= 200 && xhr.status console.log(res)) .catch(reason => console.log(reason))
12. 手写节流防抖函数
函数节流与函数防抖都是为了限制函数的执行频次,是一种性能优化的方案,比如应用于window对象的resize、scroll事件,拖拽时的mousemove事件,文字输入、自动完成的keyup事件。
节流:连续触发事件但是在 n 秒中只执行一次函数
例:(连续不断动都需要调用时用,设一时间间隔),像dom的拖拽,如果用消抖的话,就会出现卡顿的感觉,因为只在停止的时候执行了一次,这个时候就应该用节流,在一定时间内多次执行,会流畅很多。
防抖:指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
例:(连续不断触发时不调用,触发完后过一段时间调用),像仿百度搜索,就应该用防抖,当我连续不断输入时,不会发送请求;当我一段时间内不输入了,才会发送一次请求;如果小于这段时间继续输入的话,时间会重新计算,也不会发送请求。
防抖的实现:
function debounce(fn, delay) { if(typeof fn!=='function') { throw new TypeError('fn不是函数') } let timer; // 维护一个 timer return function () { var _this = this; // 取debounce执行作用域的this(原函数挂载到的对象) var args = arguments; if (timer) { clearTimeout(timer); } timer = setTimeout(function () { fn.apply(_this, args); // 用apply指向调用debounce的对象,相当于_this.fn(args); }, delay); }; } // 调用 input1.addEventListener('keyup', debounce(() => { console.log(input1.value) }), 600)
节流的实现:
function throttle(fn, delay) { let timer; return function () { var _this = this; var args = arguments; if (timer) { return; } timer = setTimeout(function () { fn.apply(_this, args); // 这里args接收的是外边返回的函数的参数,不能用arguments // fn.apply(_this, arguments); 需要注意:Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。 timer = null; // 在delay后执行完fn之后清空timer,此时timer为假,throttle触发可以进入计时器 }, delay) } } p1.addEventListener('drag', throttle((e) => { console.log(e.offsetX, e.offsetY) }, 100))
13. 手写Promise加载图片
function getData(url) { return new Promise((resolve, reject) => { $.ajax({ url, success(data) { resolve(data) }, error(err) { reject(err) } }) }) } const url1 = './data1.json' const url2 = './data2.json' const url3 = './data3.json' getData(url1).then(data1 => { console.log(data1) return getData(url2) }).then(data2 => { console.log(data2) return getData(url3) }).then(data3 => console.log(data3) ).catch(err => console.error(err) )
14. 函数实现一秒钟输出一个数
for(let i=0;i{ console.log(i); },1000*i) }
15. 创建10个标签,点击的时候弹出来对应的序号?
var a for(let i=0;i' a.addEventListener('click',function(e){ console.log(this) //this为当前点击的<a> e.preventDefault() //如果调用这个方法,默认事件行为将不再触发。 //例如,在执行这个方法后,如果点击一个链接(a标签),浏览器不会跳转到新的 URL 去了。我们可以用 event.isDefaultPrevented() 来确定这个方法是否(在那个事件对象上)被调用过了。 alert(i) }) const d=document.querySelector('p') d.appendChild(a) //append向一个已存在的元素追加该元素。 }</a>