夯实JavaScript功底,前端要会的手写方法

call: 简单来说就是改变执行方法当前的this,可以传入不定参数

Function.prototype.myCall = function(context, ...args) {
  context.fn = this;
  const result = context.fn(...args);
  delete context.fn;
  return result;
}

getMessage.myCall(obj, 'name'); 

立即执行getMessage方法,不过是以obj.getMessage的方式,
所以这个时候getMessage内的this是obj,传入参数'name'。
(obj可能压根就没有getMessage方法)

apply: 和call作用一致,传递参数格式不同,需是数组

Function.prototype.myApply = function(context, args) {
  context.fn = this;
  const result = context.fn(args);
  delete context.fn;
  return result;
}

getMessage.myApply(obj, ['name']); 

bind: 改变指定方法的this后执行,以函数的形式返回执行结果

Function.prototype.myBind = function(context, ...args) {
  return () => {
    return this.myApply(context, args);
  }
}

const result = getMessage.myBind(obj) 

result是一个函数,再执行一次才是getMessage方法的执行结果

new:将构造函数实例化,将参数创建为对象以及赋值原型方法

function createNew(Ctor, ...args) {
  const obj = Object.create(Ctor.prototype);
  const ret = Ctur.apply(obj, args);
  return ret instanceof Object ? ret : obj;
}

1. 将构造函数的原型赋值给新建的obj的隐式原型__proto__。
2. 在obj下执行构造函数,并传入参数,
   这个时候构造函数内的this就是obj。
3. 如果这个'构造函数'没有return对象格式的结果,
   返回新创建的obj。

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.getName = function() {
  console.log(this.name);
}

const xm = createNew(Person, 'xiaoming', 22);

instanceof: 判断一个变量是否是某个类型

function myInstanceOf(left, right) {
  while(1) {
    if(left.__proto__ === null) {
      return false;
    }
    if(left.__proto__ === right.prototype) {
      return true;
    }
    left = left.__proto__;
  }
}

instanceof的原理就是通过原型链查找,
所以一直向上查找左侧的隐式原型__ptoto__是否等于右侧显式原型,
原型链的尽头是null,没找到就返回false。

myInstanceOf([1,2], Array);  // true

forEach: 遍历数组,这个大家经常用,想必不说都懂

Array.prototype.myForEach = function(fn) {
  const arr = this;
  for(let i = 0; i < arr.length; i++) {
    fn(arr[i], i, arr);
  }
}

接受一个fn回调函数,传递给回调函数三个参数:
每项的值,下标,自身。第二个参数有人用么?

const arr = ['a','b','c'];
arr.myForEach(item => {
  console.log(item);  // a   b   c
})

map: 返回经过处理的数组

Array.prototype.myMap = function(fn) {
  const arr = this;
  const ret = [];
  for(let i = 0; i < arr.length; i++) {
    ret.push(fn(arr[i], i, arr));
  }
  return ret;
}

和forEach类似也是接受一个fn回调,
不过会将回调处理的结果放入一个新的数组,
所以map回调内的每一项需要return,
因为要组成新的数组结果。

const arr = ['a', 'b', 'c'];
const newArr = arr.myMap(item => {  // a1   b1   c1
  return item + 1; // 要return结果
})

filter: 返回回调处理结果为true的新数组

Array.prototype.myFilter = function(fn) {
  const arr = this;
  const ret = [];
  for(let i = 0; i < arr.length; i++) {
    if(fn(arr[i], i, arr)) {
      ret.push(arr[i]);
    }
  }
  return ret;
}

大同小异,过滤出处理条件为true的值。

返回数组中不重复的值:
function repeat(arr) {
  return arr.myFilter((v, i, a) => {
    return a.indexOf(v) === a.lastIndexOf(v);
  })
}

const arr = [1,2,3,4,1,2,3,5,6,8,3];
repeat(arr); // [4,5,6,8]

find:返回处理条件第一个为true的数组项

Array.prototype.myFind = function(fn) {
  const arr =this;
  for(let i = 0; i < arr.length; i++) {
    if(fn(arr[i], i, arr)) {
      return arr[i];
    }
  }
}

否则返回undefined

findIndex: 返回处理条件第一个为true的数组下标

大家自己写下咯~

every:如果数组每一项都符合处理条件。返回true,否则返回false

Array.prototype.myEvery = function(fn) {
  const arr = this;
  for(let i = 0; i < arr.length; i++) {
    if(!fn(arr[i], i, arr)) {
      return false;
    }
  }
  return true;
}

some:只要数组有一项符合处理条件。返回true,都不满足返回false。

这个相信大家都知道怎么写了~

reduce: 一般为数组做累计结果使用。

Array.prototype.myReduce = function(fn, second) {
  const arr = this;
  let index = 0;
  if(typeof second === undefined) { // 没传第二个参数
    index = 1;
    second = arr[0];
  }
  for(let i = index; i < arr.length; i++) {
    const invoked = fn(second, arr[i], i, arr);
    second = invoked;
  }
  return second;
}

一般会传入第二个参数作为初始值,如果没有传入,
初始值就是数组的第一项,将处理的结果进行累计,
最后返回累计的结果。

返回数组中指定参数重复的次数:
function count(arr, value) {
  return arr.myReduce((f, s) => {
    return Object.is(s, value) ? f + 1 : f + 0;
  }, 0)
}

const arr = [1,2,3,4,1,2,3,2,1];
count(arr, 2); // 3

debounce: 函数防抖

function debounce(fn, delay = 1000) {
  let timer;
  return () => {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, delay)
  }
}

函数防抖指的是一定时间内没有再次触发函数,就执行该函数,否则重新计时。
wow为例:
2.5s施法的寒冰箭,再读条的过程中,
你身子抖动打断了施法,想再次触发技能时麻烦您重新读条。

throttle:函数节流

function throttle(fn, delay = 100) {
  let timer;
  return () => {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, arguments);
        timer = null;
      }, delay)
    }
  }
}

函数节流指的是规定某个时间内只能执行一次函数。
wow为例:
火冲为瞬发技能,不过你规定cd为8s,
所以即使8s内按了10次,也只能来1发,节省点体力吧。

deepClone:深拷贝

一般够用型
function deepClone(source) {
  if (typeof source !== 'object' || source == null) {
    return source;
  }
  const target = Array.isArray(source) ? [] : {};
  for (const key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      if (typeof source[key] === 'object' && source[key] !== null) {
        target[key] = deepClone(source[key]);
      } else {
        target[key] = source[key];
      }
    }
  }
  return target;
}

解决循环引用和symblo类型
function cloneDeep(source, hash = new WeakMap()) {
  if (typeof source !== 'object' || source === null) {
    return source;
  }
  if (hash.has(source)) {
    return hash.get(source);
  }
  const ret = Array.isArray(source) ? [] : {};
  Reflect.ownKeys(source).forEach(key => {
    const val = source[key];
    if (typeof val === 'object' && val != null) {
      ret[key] = cloneDeep(val, hash);
    } else {
      ret[key] = val;
    }
  })
  return ret;
}

Promise:手写简易版

class MyPromise {
  constructor(fn) {
    this.state = 'PENDING';
    this.value = null
    this.resolvedCallbacks = []
    this.rejectedCallbacks = []
    const resolve = value => {
      if (this.state === 'PENDING') {
        this.state = 'RESOLVED'
        this.value = value
        this.resolvedCallbacks.map(cb => cb())
      }
    }
    const reject = value => {
      if (this.state === 'PENDING') {
        this.state = 'REJECTED'
        this.value = value
        this.rejectedCallbacks.map(cb => cb())
      }
    }
    try {
      fn(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r }
    if (this.state === 'PENDING') {
      this.resolvedCallbacks.push(onFulfilled)
      this.rejectedCallbacks.push(onRejected)
    }
    if (this.state === 'RESOLVED') {
      onFulfilled(this.value)
    }
    if (this.state === 'REJECTED') {
      onRejected(this.value)
    }
  }
  
  catch(fn) {
    return this.then(null, fn);
  }
}

const promise = new MyPromise((resolve, reject) => {
  resolve('hello world~');
})
promise.then(res => {
  console.log(res);
})

iterator:不使用Generator函数创建迭代器。

function myIterator(items) {
  let i = 0;
  return {
    next() {
      const done = i >= items.length;
      const value = !done ? items[i++] : undefined;
      return {
        done,  // 是否全部迭代完成
        value  // 返回迭代的值
      }
    }
  }
}

const interator = myIterator([1, 2, 3]);
interator.next();

JSON.stringify: 将对象转为json字符串

function jsonStringify(obj) {
  const type = typeof obj;
  if (type !== 'object') {
    if (type === 'string') {
      obj = '"' + obj + '"';
    }
    return String(obj);
  } else {
    const json = [];
    const arr = Array.isArray(obj);
    for (const k in obj) {
      let v = obj[k];
      const type = typeof v;
      if (type === 'string') {
        v = '"' + v + '"';
      } else if (v === null) { // 处理null情况
        v = null
      } else if (/function|undefined/.test(type)) { 
        // 原生方法会移除function和undefined,其实我们可以不移除
        delete obj[k];
      } else {
        v = jsonStringify(v); // 递归
      }
      json.push((arr ? "" : '"' + k + '":') + String(v));
    }
    return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
  }
}

const obj = {
  a: 'a1',
  b: [1, 2, 3],
  c: 22,
  d: function () {},
  e: Date.now(),
  f: null,
  g: /str/ig,
  h: undefined
}

const str = jsonStringify(obj); 
// {"a":"a1","b":[1,2,3],"c":22,"e":1562815128952,"f":null,"g":{}}

JSON.parse: 将字符串格式的对象转为对象。

function jsonParse(str) {
  return new Function('return ' + str)(); // return后有一个空格
}

很神奇有木有,直接在字符串前面加上'return '关键字就可以转为对象。
在组件精讲小册里有一个实例,在线vue单文件编辑器。
原理就是将编辑器内的vue单文件字符串使用正则分割,
js部分将‘export default’替换为'return '。
通过new Function转为js对象使用。

const sum = new Function('a','b','return a + b');
sum(1, 2); // 3

const str = '{"a":"a1","b":[1,2,3],"c":22,"e":1562815128952,"f":null,"g":{}}';
jsonParse(str); //
a: "a1",
b: [1, 2, 3],
c: 22,
e: 1562815128952,
f: null,
g: {}

Events:事件中心管理

class Events {
  constructor() {
    this._evnets = Object.create(null);
  }
  
  on(event, fn) {  // 往事件中心添加事件
    if (Array.isArray(event)) {
      for (let i = 0; i < event.length; i++) {
        this.on(evnet[i], fn);
      }
    } else {
      (this._evnets[event] || (this._evnets[event] = [])).push(fn);
    }
  }
  
  emit(event, ...args) {  // 触发事件中心对应事件
    const cbs = this._evnets[event];
    if (cbs) {
      for (let i = 0; i < cbs.length; i++) {
        cbs[i].apply(this, args);
      }
    }
  }
  
  off(event, fn) {  // 移除事件
    if (!arguments) {
      this._evnets = Object.create(null);
      return this;
    }
    if (Array.isArray(event)) {
      for (let i = 0; i < event.length; i++) {
        this.off(event[i], fn);
      }
      return this;
    }
    if (!fn) {
      this._evnets[event] = null;
      return this;
    }
    const cbs = this._evnets[event];
    let i = cbs.length;
    while (i--) {
      const cb = cbs[i];
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1);
        break;
      }
    }
    return this;
  }
  
  once(evnet, fn) {  // 只执行一次
    function on() {
      this.off(evnet, on);
      fn.apply(this, arguments);
    }
    on.fn = fn;
    this.on(evnet, on);
    return this;
  }
}

const event = new Event();
event.on('test', (name, sex) => { // 添加事件
  console.log(`${name}:${sex}`);
})
event.emit('test', 'cc', 'man');  // 传参并触发事件
evnet.off();  // 清空所有事件

setInterval: 使用setTimeout模拟,并可以取消

function mySetInterval(fn, delay) {
  let timer;
  const loop = (fn, delay) => {
    timer = setTimeout(() => {
      loop(fn, delay);
      fn.call(this, timer);
    }, delay);
  };
  loop(fn, delay);
}

mySetInterval(timer => {
  console.log('test');
  // clearTimeout(timer);  取消定时器
}, 200);

setInterval: 使用requestAnimationFrame模拟

function mySetInterval(fn, interval) {
  const now = Date.now;
  let startTime = now();
  const loop = () => {
    const timer = requestAnimationFrame(loop);
    if (now() - startTime >= interval) {
      startTime = now();
      fn.call(this, timer);
    }
  }
  loop();
}

一般来说是不建议使用setInterval的,
如内部函数复杂就不能保证一定在规定时间内自动执行。
一般是通过setTimeout模仿setInterval。
那为什么要实现setInterval?
因为它内部的实现是使用requestAnimationFrame实现的,
该方法自带函数节流。
如有持续的动画需要执行,
基本会保证在16.6毫秒内执行一次,
提高动画性能并延时也是精确的。

mySetInterval(timer => {
  console.log('a');
  // cancelAnimationFram(timer); 取消当前定时器
})

setTimeout: 使用requestAnimationFrame模拟

定时执行一次回调就取消掉,自己实现下吧~

分享一个笔者自己写的组件库,哪天可能会用的上了 ~ ↓

你可能会用的上的一个vue功能组件库,持续完善中...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,521评论 0 5
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,093评论 0 3
  • 参考基础教程的整理,方便记忆 一、Object对象 (O大写) 所有其他对象都继承自这个对象。Object本身也是...
    Viaphlyn阅读 2,302评论 0 0
  • JS基础 页面由三部分组成:html:超文本标记语言,负责页面结构css:层叠样式表,负责页面样式js:轻量级的脚...
    小贤笔记阅读 593评论 0 5
  •   引用类型的值(对象)是引用类型的一个实例。   在 ECMAscript 中,引用类型是一种数据结构,用于将数...
    霜天晓阅读 1,035评论 0 1