js高級用法----手寫js原生方法


1、call 方法

/** * _call * * @param { context } context * @param { arguments } arguments */ Function.prototype._call = function(context) { // 如果沒有傳或傳的值為空對象 context指向window
  context = context || window let fn = Symbol(context) context[fn] = this //給context添加一個方法 指向this
  // 處理參數 去除第一個參數this 其它傳入fn函數
  let args = [...arguments].slice(1) //[...xxx]把類數組變成數組,arguments為啥不是數組自行搜索 slice返回一個新數組
  context[fn](...args) //執行fn
  delete context[fn] //刪除方法
} var obj = { name: 'Bob', age: '18', fn() { console.log(`My name is ${this.name}, my age is ${this.age}`) } } var dog = { name: 'Snoopy', age: 3 } obj.fn.call(dog,'daddata','ttt','yuyuyuuy') // My name is Snoby, my age is 3
obj.fn._call(dog,'daddata','ttt','yuyuyuuy') // My name is Snoby, my age is 3

2、 apply 方法

Function.prototype._apply = function(context) { // 如果沒有傳或傳的值為空對象 context指向window
    context = context || window let fn = Symbol(context) context[fn] = this let arg = [...arguments].slice(1) context[fn](arg) //執行fn
    delete context[fn] //刪除方法
}

3、bind方法

/** * _bind * * @param {*} context */ Function.prototype._bind = function (context) { //返回一個綁定this的函數,我們需要在此保存this
  let self = this
  // 可以支持柯里化傳參,保存參數
  let arg = [...arguments].slice(1) // 返回一個函數
  return function () { //同樣因為支持柯里化形式傳參我們需要再次獲取存儲參數
    let newArg = [...arguments] // 返回函數綁定this,傳入兩次保存的參數
    //考慮返回函數有返回值做了return
    return self.apply(context, arg.concat(newArg)) } }

4、promise方法

function MyPromise(exceptor) { let _this = this; _this.reason = ''; _this.value = ''; _this.resolveFnArr = []; _this.rejectFnArr = []; _this.status = 'pending'; function resolve(val) { if (_this.status === 'pending') { _this.value = val; _this.status = 'fulfiled'; _this.resolveFnArr.forEach(fn => fn()) } } function reject(reason) { if (_this.status === 'pending') { _this.reason = reason; _this.status = 'reject'; _this.rejectFnArr.forEach(fn => fn()) } } try { exceptor(resolve, reject); } catch (error) { reject(); } } MyPromise.prototype.then = function(resolve, reject) { let _this = this; if (_this.status === 'fulfiled') { resolve(_this.value); } if (_this.status === 'reject') { reject(_this.reason); } if (_this.status === 'pending') { _this.resolveFnArr.push(() => {resolve(_this.value)}) _this.rejectFnArr.push(() => {reject(_this.reason)}) } }

5、全面的promise寫法

(function(window,undefined){ // resolve 和 reject 最終都會調用該函數
  var final = function(status,value){ var _this = this, fn, status; if(_this._status !== 'PENDING') return; // 所以的執行都是異步調用,保證then是先執行的
      setTimeout(function(){ _this._status = status; status = _this._status === 'FULFILLED' queue = _this[status ? '_resolves' : '_rejects']; while(fn = queue.shift()) { value = fn.call(_this, value) || value; } _this[status ? '_value' : '_reason'] = value; _this['_resolves'] = _this['_rejects'] = undefined; }); } //參數是一個函數,內部提供兩個函數作為該函數的參數,分別是resolve 和 reject
  var MyPromise = function(resolver){ if (!(typeof resolver === 'function' )) throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); //如果不是promise實例,就new一個
      if(!(this instanceof MyPromise)) return new MyPromise(resolver); var _this = this; _this._value; _this._reason; _this._status = 'PENDING'; //存儲狀態
      _this._resolves = []; _this._rejects = []; //       var resolve = function(value) { //由於apply參數是數組
          final.apply(_this,['FULFILLED'].concat([value])); } var reject = function(reason){ final.apply(_this,['REJECTED'].concat([reason])); } resolver(resolve,reject); } MyPromise.prototype.then = function(onFulfilled,onRejected){ var _this = this; // 每次返回一個promise,保證是可thenable的
      return new MyPromise(function(resolve,reject){ function handle(value) { // 這一步很關鍵,只有這樣才可以將值傳遞給下一個resolve
              var ret = typeof onFulfilled === 'function' && onFulfilled(value) || value; //判斷是不是promise 對象
              if (ret && typeof ret ['then'] == 'function') { ret.then(function(value) { resolve(value); }, function(reason) { reject(reason); }); } else { resolve(ret); } } function errback(reason){ reason = typeof onRejected === 'function' && onRejected(reason) || reason; reject(reason); } if(_this._status === 'PENDING'){ _this._resolves.push(handle); _this._rejects.push(errback); }else if(_this._status === FULFILLED){ // 狀態改變后的then操作,立刻執行
 callback(_this._value); }else if(_this._status === REJECTED){ errback(_this._reason); } }); } MyPromise.prototype.catch = function(onRejected){ return this.then(undefined, onRejected) } MyPromise.prototype.delay = function(ms,value){ return this.then(function(ori){ return MyPromise.delay(ms,value || ori); }) } MyPromise.delay = function(ms,value){ return new MyPromise(function(resolve,reject){ setTimeout(function(){ resolve(value); console.log('1'); },ms); }) } MyPromise.resolve = function(arg){ return new MyPromise(function(resolve,reject){ resolve(arg) }) } MyPromise.reject = function(arg){ return MyPromise(function(resolve,reject){ reject(arg) }) } MyPromise.all = function(promises){ if (!Array.isArray(promises)) { throw new TypeError('You must pass an array to all.'); } return MyPromise(function(resolve,reject){ var i = 0, result = [], len = promises.length, count = len //這里與race中的函數相比,多了一層嵌套,要傳入index
          function resolver(index) { return function(value) { resolveAll(index, value); }; } function rejecter(reason){ reject(reason); } function resolveAll(index,value){ result[index] = value; if( --count == 0){ resolve(result) } } for (; i < len; i++) { promises[i].then(resolver(i),rejecter); } }); } MyPromise.race = function(promises){ if (!Array.isArray(promises)) { throw new TypeError('You must pass an array to race.'); } return MyPromise(function(resolve,reject){ var i = 0, len = promises.length; function resolver(value) { resolve(value); } function rejecter(reason){ reject(reason); } for (; i < len; i++) { promises[i].then(resolver,rejecter); } }); } window.MyPromise = MyPromise; })(window);

 6、filter

Array.prototype.filter = function(callback, thisArg) { if (this == undefined) { throw new TypeError('this is null or not undefined'); } if (typeof callback !== 'function') { throw new TypeError(callback + 'is not a function'); } const res = []; // 讓O成為回調函數的對象傳遞(強制轉換對象)
  const O = Object(this); // >>>0 保證len為number,且為正整數
  const len = O.length >>> 0; for (let i = 0; i < len; i++) { // 檢查i是否在O的屬性(會檢查原型鏈)
    if (i in O) { // 回調函數調用傳參
      if (callback.call(thisArg, O[i], i, O)) { res.push(O[i]); } } } return res; }

7、map方法

Array.prototype.map = function(callback, thisArg) { if (this == undefined) { throw new TypeError('this is null or not defined'); } if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } const res = []; // 同理
  const O = Object(this); const len = O.length >>> 0; for (let i = 0; i < len; i++) { if (i in O) { // 調用回調函數並傳入新數組
      res[i] = callback.call(thisArg, O[i], i, this); } } return res; }

8、forEach方法

Array.prototype.forEach = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined'); } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function'); } const O = Object(this); const len = O.length >>> 0; let k = 0; while (k < len) { if (k in O) { callback.call(thisArg, O[k], k, O); } k++; } }

9、reduce方法

Array.prototype.reduce = function(callback, initialValue) { if (this == undefined) { throw new TypeError('this is null or not defined'); } if (typeof callback !== 'function') { throw new TypeError(callbackfn + ' is not a function'); } const O = Object(this); const len = this.length >>> 0; let accumulator = initialValue; let k = 0; // 如果第二個參數為undefined的情況下
  // 則數組的第一個有效值作為累加器的初始值
  if (accumulator === undefined) { while (k < len && !(k in O)) { k++; } // 如果超出數組界限還沒有找到累加器的初始值,則TypeError
    if (k >= len) { throw new TypeError('Reduce of empty array with no initial value'); } accumulator = O[k++]; } while (k < len) { if (k in O) { accumulator = callback.call(undefined, accumulator, O[k], k, O); } k++; } return accumulator; }

10、debounce(防抖)

const debounce = (fn, time) => { let timeout = null; return function() { clearTimeout(timeout) timeout = setTimeout(() => { fn.apply(this, arguments); }, time); } };

11、throttle(節流)

var throttle = function(func, delay) {            
  var prev = Date.now();            
  return function() {                
    var context = this;                
    var args = arguments;                
    var now = Date.now();                
    if (now - prev >= delay) {                    
      func.apply(context, args);                    
      prev = Date.now();                
    }            
  }        
}        
function handle() {            
  console.log(Math.random());        
}        
window.addEventListener('scroll', throttle(handle, 1000));

12、模擬new操作

function newOperator(ctor, ...args) { if (typeof ctor !== 'function') { throw new TypeError('Type Error'); } const obj = Object.create(ctor.prototype); const res = ctor.apply(obj, args); const isObject = typeof res === 'object' && res !== null; const isFunction = typeof res === 'function'; return isObject || isFunction ? res : obj; }

13、instanceof

const myInstanceof = (left, right) => { // 基本數據類型都返回false
  if (typeof left !== 'object' || left === null) return false; let proto = Object.getPrototypeOf(left); while (true) { if (proto === null) return false; if (proto === right.prototype) return true; proto = Object.getPrototypeOf(proto); } }

14、原型繼承

function Parent() { this.name = 'parent'; } function Child() { Parent.call(this); this.type = 'children'; } Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child;

15、Object.assign

Object.defineProperty(Object, 'assign', { value: function(target, ...args) { if (target == null) { return new TypeError('Cannot convert undefined or null to object'); } // 目標對象需要統一是引用數據類型,若不是會自動轉換
    const to = Object(target); for (let i = 0; i < args.length; i++) { // 每一個源對象
      const nextSource = args[i]; if (nextSource !== null) { // 使用for...in和hasOwnProperty雙重判斷,確保只拿到本身的屬性、方法(不包含繼承的)
        for (const nextKey in nextSource) { if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; }, // 不可枚舉
  enumerable: false, writable: true, configurable: true, })

16、深拷貝

const cloneDeep1 = (target, hash = new WeakMap()) => { // 對於傳入參數處理
  if (typeof target !== 'object' || target === null) { return target; } // 哈希表中存在直接返回
  if (hash.has(target)) return hash.get(target); const cloneTarget = Array.isArray(target) ? [] : {}; hash.set(target, cloneTarget); // 針對Symbol屬性
  const symKeys = Object.getOwnPropertySymbols(target); if (symKeys.length) { symKeys.forEach(symKey => { if (typeof target[symKey] === 'object' && target[symKey] !== null) { cloneTarget[symKey] = cloneDeep1(target[symKey]); } else { cloneTarget[symKey] = target[symKey]; } }) } for (const i in target) { if (Object.prototype.hasOwnProperty.call(target, i)) { cloneTarget[i] =
        typeof target[i] === 'object' && target[i] !== null
        ? cloneDeep1(target[i], hash) : target[i]; } } return cloneTarget; }

17、JSONP

const jsonp = ({ url, params, callbackName }) => { const generateUrl = () => { let dataSrc = ''; for (let key in params) { if (Object.prototype.hasOwnProperty.call(params, key)) { dataSrc += `${key}=${params[key]}&`; } } dataSrc += `callback=${callbackName}`; return `${url}?${dataSrc}`; } return new Promise((resolve, reject) => { const scriptEle = document.createElement('script'); scriptEle.src = generateUrl(); document.body.appendChild(scriptEle); window[callbackName] = data => { resolve(data); document.removeChild(scriptEle); } }) }

18、AJAX

const getJSON = function(url) { return new Promise((resolve, reject) => { const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp'); xhr.open('GET', url, false); xhr.setRequestHeader('Accept', 'application/json'); xhr.onreadystatechange = function() { if (xhr.readyState !== 4) return; if (xhr.status === 200 || xhr.status === 304) { resolve(xhr.responseText); } else { reject(new Error(xhr.responseText)); } } xhr.send(); }) }

19、圖片懶加載

// 可以給img標簽統一自定義屬性data-src='default.png',當檢測到圖片出/現在窗口之后再補充src屬性,此時才會進行圖片資源加載。
function lazyload() { const imgs = document.getElementsByTagName('img'); const len = imgs.length; // 視口的高度
  const viewHeight = document.documentElement.clientHeight; // 滾動條高度
  const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop; for (let i = 0; i < len; i++) { const offsetHeight = imgs[i].offsetTop; if (offsetHeight < viewHeight + scrollHeight) { const src = imgs[i].dataset.src; imgs[i].src = src; } } }

20、滾動加載

// 原理就是監聽頁面滾動事件,分析clientHeight、scrollTop、scrollHeight三者的屬性關系。
window.addEventListener('scroll', function() { const clientHeight = document.documentElement.clientHeight; const scrollTop = document.documentElement.scrollTop; const scrollHeight = document.documentElement.scrollHeight; if (clientHeight + scrollTop >= scrollHeight) { // 檢測到滾動至頁面底部,進行后續操作
    // ...
 } }, false);

21、渲染幾萬條數據不卡住頁面

// 渲染大數據時,合理使用createDocumentFragment和requestAnimationFrame,將操作切分為一小段一小段執行。
setTimeout(() => { // 插入十萬條數據
  const total = 100000; // 一次插入的數據
  const once = 20; // 插入數據需要的次數
  const loopCount = Math.ceil(total / once); let countOfRender = 0; const ul = document.querySelector('ul'); // 添加數據的方法
  function add() { const fragment = document.createDocumentFragment(); for(let i = 0; i < once; i++) { const li = document.createElement('li'); li.innerText = Math.floor(Math.random() * total); fragment.appendChild(li); } ul.appendChild(fragment); countOfRender += 1; loop(); } function loop() { if(countOfRender < loopCount) { window.requestAnimationFrame(add); } } loop(); }, 0)

22、將VirtualDom轉化為真實DOM結構

// vnode結構: // { // tag, // attrs, // children, // }

//Virtual DOM => DOM
function render(vnode, container) { container.appendChild(_render(vnode)); } function _render(vnode) { // 如果是數字類型轉化為字符串
  if (typeof vnode === 'number') { vnode = String(vnode); } // 字符串類型直接就是文本節點
  if (typeof vnode === 'string') { return document.createTextNode(vnode); } // 普通DOM
  const dom = document.createElement(vnode.tag); if (vnode.attrs) { // 遍歷屬性
    Object.keys(vnode.attrs).forEach(key => { const value = vnode.attrs[key]; dom.setAttribute(key, value); }) } // 子數組進行遞歸操作
  vnode.children.forEach(child => render(child, dom)); return dom; }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM