果預覽:http://ncurobot.club/
主頁
比賽報名網站,采用HTML、JavaScript、css、php完成編寫,開源供大家參考。
在報名頁可以填寫報名信息:
報名頁
在資料頁可以查看和下載規則等文件:
資料頁
部分源碼預覽:
可私聊小編,發送“報名網站源碼”獲取源碼信息,也可進入github獲取:https://github.com/1061700625/Competition_Registration_Web
圖1
圖2
圖2
圖3
能夠手撕各種JavaScript原生函數,可以說是進大廠必備!同時對JavaScript源碼的學習和實現也能幫助我們快速扎實地提升自己的前端編程能力。
最近很多人和我一樣在積極地準備前端面試筆試,所以就整理了一些前端面試筆試中非常容易被問到的原生函數實現和各種前端原理實現,其中部分源碼戳這里。
我們首先知道new做了什么:
知道new做了什么,接下來我們就來實現它
function create(Con, ...args){
// 創建一個空的對象
this.obj={};
// 將空對象指向構造函數的原型鏈
Object.setPrototypeOf(this.obj, Con.prototype);
// obj綁定到構造函數上,便可以訪問構造函數中的屬性,即this.obj.Con(args)
let result=Con.apply(this.obj, args);
// 如果返回的result是一個對象則返回
// new方法失效,否則返回obj
return result instanceof Object ? result : this.obj;
}
思路很簡單,就是利用Object.prototype.toString
Array.myIsArray=function(o) {
return Object.prototype.toString.call(Object(o))==='[object Array]';
};
function create=function (o) {
var F=function () {};
F.prototype=o;
return new F();
};
真實經歷,最近在字節跳動的面試中就被面試官問到了,要求手寫實現一個簡單的Event類。
class Event {
constructor () {
// 儲存事件的數據結構
// 為查找迅速, 使用對象(字典)
this._cache={}
}
// 綁定
on(type, callback) {
// 為了按類查找方便和節省空間
// 將同一類型事件放到一個數組中
// 這里的數組是隊列, 遵循先進先出
// 即新綁定的事件先觸發
let fns=(this._cache[type]=this._cache[type] || [])
if(fns.indexOf(callback)===-1) {
fns.push(callback)
}
return this
}
// 解綁
off (type, callback) {
let fns=this._cache[type]
if(Array.isArray(fns)) {
if(callback) {
let index=fns.indexOf(callback)
if(index !==-1) {
fns.splice(index, 1)
}
} else {
// 全部清空
fns.length=0
}
}
return this
}
// 觸發emit
trigger(type, data) {
let fns=this._cache[type]
if(Array.isArray(fns)) {
fns.forEach((fn)=> {
fn(data)
})
}
return this
}
// 一次性綁定
once(type, callback) {
let wrapFun=()=> {
callback.call(this);
this.off(type, wrapFun); // 執行完以后立即解綁
};
this.on(type, wrapFun); // 綁定
return this;
}
}
let e=new Event()
e.on('click',function(){
console.log('on')
})
// e.trigger('click', '666')
console.log(e)
先回憶一下Array.prototype.reduce語法:
Array.prototype.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
然后就可以動手實現了:
Array.prototype.myReduce=function(callback, initialValue) {
let accumulator=initialValue ? initialValue : this[0];
for (let i=initialValue ? 0 : 1; i < this.length; i++) {
let _this=this;
accumulator=callback(accumulator, this[i], i, _this);
}
return accumulator;
};
// 使用
let arr=[1, 2, 3, 4];
let sum=arr.myReduce((acc, val)=> {
acc +=val;
return acc;
}, 5);
console.log(sum); // 15
先來看一個call實例,看看call到底做了什么:
let foo={
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
從代碼的執行結果,我們可以看到,call首先改變了this的指向,使函數的this指向了foo,然后使bar函數執行了。總結一下:
思考一下:我們如何實現上面的效果呢?代碼改造如下:
Function.prototype.myCall=function(context) {
context=context || window;
//將函數掛載到對象的fn屬性上
context.fn=this;
//處理傳入的參數
const args=[...arguments].slice(1);
//通過對象的屬性調用該方法
const result=context.fn(...args);
//刪除該屬性
delete context.fn;
return result
};
我們看一下上面的代碼:
以此類推,我們順便實現一下apply,唯一不同的是參數的處理,代碼如下:
Function.prototype.myApply=function(context) {
context=context || window
context.fn=this
let result
// myApply的參數形式為(obj,[arg1,arg2,arg3]);
// 所以myApply的第二個參數為[arg1,arg2,arg3]
// 這里我們用擴展運算符來處理一下參數的傳入方式
if (arguments[1]) {
result=context.fn(…arguments[1])
} else {
result=context.fn()
}
delete context.fn;
return result
};
以上便是call和apply的模擬實現,唯一不同的是對參數的處理方式。
function Person(){
this.name="zs";
this.age=18;
this.gender="男"
}
let obj={
hobby:"看書"
}
// 將構造函數的this綁定為obj
let changePerson=Person.bind(obj);
// 直接調用構造函數,函數會操作obj對象,給其添加三個屬性;
changePerson();
// 1、輸出obj
console.log(obj);
// 用改變了this指向的構造函數,new一個實例出來
let p=new changePerson();
// 2、輸出obj
console.log(p);
仔細觀察上面的代碼,再看輸出結果。
我們對Person類使用了bind將其this指向obj,得到了changeperson函數,此處如果我們直接調用changeperson會改變obj,若用new調用changeperson會得到實例 p,并且其__proto__指向Person,我們發現bind失效了。
我們得到結論:用bind改變了this指向的函數,如果用new操作符來調用,bind將會失效。
這個對象就是這個構造函數的實例,那么只要在函數內部執行 this instanceof 構造函數 來判斷其結果是否為true,就能判斷函數是否是通過new操作符來調用了,若結果為true則是用new操作符調用的,代碼修正如下:
// bind實現
Function.prototype.mybind=function(){
// 1、保存函數
let _this=this;
// 2、保存目標對象
let context=arguments[0]||window;
// 3、保存目標對象之外的參數,將其轉化為數組;
let rest=Array.prototype.slice.call(arguments,1);
// 4、返回一個待執行的函數
return function F(){
// 5、將二次傳遞的參數轉化為數組;
let rest2=Array.prototype.slice.call(arguments)
if(this instanceof F){
// 6、若是用new操作符調用,則直接用new 調用原函數,并用擴展運算符傳遞參數
return new _this(...rest2)
}else{
//7、用apply調用第一步保存的函數,并綁定this,傳遞合并的參數數組,即context._this(rest.concat(rest2))
_this.apply(context,rest.concat(rest2));
}
}
};
Currying的概念其實并不復雜,用通俗易懂的話說:只傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數。
function progressCurrying(fn, args) {
let _this=this
let len=fn.length;
let args=args || [];
return function() {
let _args=Array.prototype.slice.call(arguments);
Array.prototype.push.apply(args, _args);
// 如果參數個數小于最初的fn.length,則遞歸調用,繼續收集參數
if (_args.length < len) {
return progressCurrying.call(_this, fn, _args);
}
// 參數收集完畢,則執行fn
return fn.apply(this, _args);
}
}
防抖函數 onscroll 結束時觸發一次,延遲執行
function debounce(func, wait) {
let timeout;
return function() {
let context=this; // 指向全局
let args=arguments;
if (timeout) {
clearTimeout(timeout);
}
timeout=setTimeout(()=> {
func.apply(context, args); // context.func(args)
}, wait);
};
}
// 使用
window.onscroll=debounce(function() {
console.log('debounce');
}, 1000);
節流函數 onscroll 時,每隔一段時間觸發一次,像水滴一樣
function throttle(fn, delay) {
let prevTime=Date.now();
return function() {
let curTime=Date.now();
if (curTime - prevTime > delay) {
fn.apply(this, arguments);
prevTime=curTime;
}
};
}
// 使用
var throtteScroll=throttle(function() {
console.log('throtte');
}, 1000);
window.onscroll=throtteScroll;
乞丐版
JSON.parse(JSON.stringfy));
非常簡單,但缺陷也很明顯,比如拷貝其他引用類型、拷貝函數、循環引用等情況。
基礎版
function clone(target){
if(typeof target==='object'){
let cloneTarget={};
for(const key in target){
cloneTarget[key]=clone(target[key])
}
return cloneTarget;
} else {
return target
}
}
寫到這里已經可以幫助你應付一些面試官考察你的遞歸解決問題的能力。但是顯然,這個深拷貝函數還是有一些問題。
一個比較完整的深拷貝函數,需要同時考慮對象和數組,考慮循環引用:
function clone(target, map=new WeakMap()) {
if(typeof target==='object'){
let cloneTarget=Array.isArray(target) ? [] : {};
if(map.get(target)) {
return target;
}
map.set(target, cloneTarget);
for(const key in target) {
cloneTarget[key]=clone(target[key], map)
}
return cloneTarget;
} else {
return target;
}
}
原理: L 的 proto 是不是等于 R.prototype,不等于再找 L.__proto__.__proto__ 直到 proto 為 null
// L 表示左表達式,R 表示右表達式
function instance_of(L, R) {
var O=R.prototype;
L=L.__proto__;
while (true) {
if (L===null){
return false;
}
// 這里重點:當 O 嚴格等于 L 時,返回 true
if (O===L) {
return true;
}
L=L.__proto__;
}
}
function myExtend(C, P) {
var F=function(){};
F.prototype=P.prototype;
C.prototype=new F();
C.prototype.constructor=C;
C.super=P.prototype;
}
就是利用 generator(生成器)分割代碼片段。然后我們使用一個函數讓其自迭代,每一個yield 用 promise 包裹起來。執行下一步的時機由 promise 來控制
function _asyncToGenerator(fn) {
return function() {
var self=this,
args=arguments;
// 將返回值promise化
return new Promise(function(resolve, reject) {
// 獲取迭代器實例
var gen=fn.apply(self, args);
// 執行下一步
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
}
// 拋出異常
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
}
// 第一次觸發
_next(undefined);
});
};
}
最近字節跳動的前端面試中也被面試官問到,要求手寫實現。
Array.prototype.myFlat=function(num=1) {
if (Array.isArray(this)) {
let arr=[];
if (!Number(num) || Number(num) < 0) {
return this;
}
this.forEach(item=> {
if(Array.isArray(item)){
let count=num
arr=arr.concat(item.myFlat(--count))
} else {
arr.push(item)
}
});
return arr;
} else {
throw tihs + ".flat is not a function";
}
};
這個問題一般還會讓你講一講事件冒泡和事件捕獲機制
<ul id="color-list">
<li>red</li>
<li>yellow</li>
<li>blue</li>
<li>green</li>
<li>black</li>
<li>white</li>
</ul>
<script>
(function () {
var color_list=document.getElementById('color-list');
color_list.addEventListener('click', showColor, true);
function showColor(e) {
var x=e.target;
if (x.nodeName.toLowerCase()==='li') {
alert(x.innerHTML);
}
}
})();
</script>
Vue 2.x的Object.defineProperty版本
// 數據
const data={
text: 'default'
};
const input=document.getElementById('input');
const span=document.getElementById('span');
// 數據劫持
Object.defineProperty(data, 'text', {
// 數據變化 —> 修改視圖
set(newVal) {
input.value=newVal;
span.innerHTML=newVal;
}
});
// 視圖更改 --> 數據變化
input.addEventListener('keyup', function(e) {
data.text=e.target.value;
});
Vue 3.x的proxy 版本
// 數據
const data={
text: 'default'
};
const input=document.getElementById('input');
const span=document.getElementById('span');
// 數據劫持
const handler={
set(target, key, value) {
target[key]=value;
// 數據變化 —> 修改視圖
input.value=value;
span.innerHTML=value;
return value;
}
};
const proxy=new Proxy(data, handler);
// 視圖更改 --> 數據變化
input.addEventListener('keyup', function(e) {
proxy.text=e.target.value;
});
思考:Vue雙向綁定的實現,使用 ES6 的 Proxy 相比 Object.defineProperty 有什么優勢?
先看看reduce和map的使用方法
let new_array=arr.map(function callback(currentValue[, index[,array) {}[, thisArg])
let result=arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
最常見的方式我們可以用一個for循環來實現:
Array.prototype.myMap=function(callback, thisArg) {
let arr=[];
for (let i=0; i < this.length; i++) {
arr.push(callback.call(thisArg, this[i], i, this));
}
return arr;
};
同樣的我們也可以用數組的reduce方法實現
Array.prototype.myMap2=function(callback, thisArg) {
let result=this.reduce((accumulator, currentValue, index, array)=> {
accumulator.push(callback.call(thisArg, currentValue, index, array));
return accumulator;
}, []);
return result;
};
看完如果覺得對你有幫助,勞煩點個贊哈,你的鼓勵就是我更新最大的動力!
學習使我快樂!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。