ALEX CODE PARK

vuePress-theme-reco ALEX CODE PARK    2020
ALEX CODE PARK

Choose mode

  • dark
  • auto
  • light
阅读笔记
  • 《深入浅出Vue.js》
  • 《JavaScript高级程序设计》
  • 《你不知道的JavaScript》
  • 《CSS世界》
前端面试
  • CSS
  • HTML
  • JavaScript
  • 常见算法
  • 网络相关
  • 剑指Offer
Github
Tag
Category
  • 前端面试
  • 算法题目
  • 前端书籍
  • 个人笔记
author-avatar

ALEX CODE PARK

19

文章

10

标签

阅读笔记
  • 《深入浅出Vue.js》
  • 《JavaScript高级程序设计》
  • 《你不知道的JavaScript》
  • 《CSS世界》
前端面试
  • CSS
  • HTML
  • JavaScript
  • 常见算法
  • 网络相关
  • 剑指Offer
Github
Tag
Category
  • 前端面试
  • 算法题目
  • 前端书籍
  • 个人笔记
  • 前端面试相关

    • CSS面试题
    • HTML面试题
    • JavaScript面试题
    • JavaScript面试题 Q&A
    • JavaScript手写实现汇总
      • 手写函数防抖
      • 手写函数节流
      • 手写简易深拷贝
      • 手写 new
      • 手写实现 instanceof
      • 手写 call
      • 手写 apply
      • 手写 bind
      • 手写简易 Promise
      • 手写 getQueryString
      • 手写 JSONP
    • 常见算法题
    • 网络面试题
  • 剑指Offer

JS 手写实现汇总

vuePress-theme-reco ALEX CODE PARK    2020

JS 手写实现汇总


ALEX CODE PARK 2020-02-17 JavaScript ES6 算法

# 手写函数防抖

# 原理

防抖:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。

  1. 创建一个标记用来存放定时器返回值
  2. 每次点击清楚上一个定时器 clearTimeout()
  3. 清楚后创建一个新的定时器

# 应用

  • 有个输入框,输入之后会调用接口,获取联想词。但是,因为频繁调用接口不太好,所以我们在代码中使用防抖功能,只有在用户输入完毕的一段时间后,才会调用接口,出现联想词。

# 代码

mybtn = document.getElementById("myBtn");

function debounce(fn, delay) {
  var timer = null;
  return function() {
    var context = this;
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.call(context, ...arguments);
    }, delay || 500);
  };
}

mybtn.onclick = debounce(() => {
  console.log("debouncing!");
}, 1000); // 一秒内连续点击也只算最后那一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 手写函数节流

# 原理

节流:指定时间间隔内只会执行一次任务。

  1. 保存一个标记 canRun
  2. 每次进入闭包函数检测 canRun 状态
  3. 第一次设置为 true -> 闭包函数中 canRun 变 false 且启动定时器
  4. 在整个定时器阶段内 canRun 皆为 false 所以每次点击进入闭包都直接被中止
  5. 直至定时器完成同时设置 canRun 为 true

# 应用

  1. 懒加载要监听计算滚动条的位置,使用节流按一定时间的频率获取。
  2. 用户点击提交按钮,假设我们知道接口大致的返回时间的情况下,我们使用节流,只允许一定时间内点击一次。

# 代码

mybtn = document.getElementById("myBtn");

function throttle(fn, interval) {
  let canRun = true;
  return function() {
    var context = this;
    if (!canRun) {
      return;
    }
    canRun = false;
    setTimeout(() => {
      fn.call(context, arguments);
      canRun = true;
    }, interval || 500);
  };
}

mybtn.onclick = throttle(() => {
  console.log("throttling!");
}, 1000); // 如一直点按 固定1s出现一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 手写简易深拷贝

# 原理

  • 先扫描第一层
  • 如有对象则进入迭代

# 代码

function deepClone(obj) {
  function isObject(obj) {
    return (
      Object.prototype.toString.call(obj) === "[object Function]" ||
      Object.prototype.toString.call(obj) === "[object Object]"
    );
  }
  function isArray(obj) {
    return Object.prototype.toString.call(obj) === "[object Array]";
  }
  if (!isObject(obj) && !isArray(obj)) {
    return "error!";
  }
  let newObj = isObject(obj) ? { ...obj } : [...obj];
  // 以上实现浅拷贝
  // 循环第一层
  Reflect.ownKeys(newObj).forEach(e => {
    newObj[e] = isObject(newObj[e]) ? deepClone(newObj[e]) : newObj[e];
  });
  return newObj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 手写 new

# 原理

  • 优雅的处理 arguments
  • 返回时确保 result 是一个 obj

切勿混淆
手写 newobj.__proto__ = con.prototype
组合继承son.prototype = new father()

# 代码

function myNew() {
  let obj = {};
  let con = [].shift.call(arguments);
  obj.__proto__ = con.prototype;
  let result = con.apply(obj, arguments);
  return result instanceof Object ? result : obj;
}

function father(name) {
  this.name = name;
}

father.prototype.sayName = function() {
  console.log(this.name);
};

let son = myNew(father, "cool");
son.sayName(); // cool
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 手写实现 instanceof

# 原理

  • 获取子原型
  • 获取父原型
  • while 循环判断是否全等 === 直到
    • 左边原型===null||undefined (原型链遍历完)->false
    • 子原型===父原型 -> true
    • 否则往下走一步原型链继续判断

# 代码

function myInstanceof(son, father) {
  let right = father.prototype;
  let left = son.__proto__;
  while (true) {
    if (left === null || left === undefined) {
      return false;
    }
    if (left === right) {
      return true;
    }
    left = left.__proto__;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 手写 call

# 原理

  • 该方法应该写在 Function 的原型方法上
  • 参数 context 为需要函数执行的作用域对象
  • 检测 this 类型 如不为 function 则抛出错误
    • 为了避免这种情况 a = foo.mycall(); a(context) 该 this 指向 window
  • 如不传入参数 context 默认为 window
  • 改变 this context.fn = this 至此 fn 的 this 便指向 context
  • 分离传入参数 (arguments 是数组 直接使用数组方法即可)
  • 执行函数并将结果赋值给 result 便于返回
  • 删除 context.fn

# 代码

Function.prototype.mycall = function(context, ...args) {
  if (typeof this != "function") {
    throw new TypeError("error");
  }
  context = context || window;
  const args = [...args];
  context.fn = this;
  const result = context.fn(args);
  delete context.fn;
  return result;
};
1
2
3
4
5
6
7
8
9
10
11

# 手写 apply

# 原理

  • 参数处理和 call 有区别 其余相同
  • 如存在第二个参数 则解构它作为变量传给函数
  • 如不存在则直接执行函数

# 代码

Function.prototype.myapply(context){
  if(typeof this!='function'){
    throw new TypeError('error')
  }
  context = context || window
  context.fn = this
  let result
  if(arguments[1]){
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 手写 bind

# 原理

  • 判断调用 bind 的是函数 否则抛出错误
  • 处理 arguments
  • 返回函数 该函数内部判断 是否将该函数当做构造函数使用 if(this instance of F)
    • 如是 则执行原函数并返回一个实例 (绑定 this 失效)
    • 如不是 使用 .apply 实现绑定
  • 因为传参可以分多次完成 注意拼接参数

# 代码

Function.prototype.mybind(context){
	if(typeof this != 'function'){
		throw new TypeError('error')
	}
	let self = this // 需要绑定的函数
	let args = [...arguments].slice(1) // 除context外的参数 返回数组
	return function F(){
		if(this instanceof F){
			return new self(...args,...arguments) //args数组拆开来调用
		}
		return self.apply(context,args.concat(...arguments))
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 手写简易 Promise

# 原理

  • 构建大体框架
    • state
    • value
    • resloved cb
    • rejected cb
  • resolve/reject 函数
    • 赋状态
    • 传入值
    • 执行回调
  • fn 函数
    • try-catch 块执行传入 fn
  • then 函数
    • 检测传入是否为函数
    • 状态判断

# 代码

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function _Promise(fn) {
  console.log("promise");
  const that = this;
  that.state = PENDING;
  that.value = null;
  that.resolvedcb = [];
  that.rejectedcb = [];
  // 设定resolve态
  function resolve(value) {
    console.log("resolve(value)");
    if (that.state === PENDING) {
      that.state = RESOLVED;
      that.value = value;
      that.resolvedcb.map(cb => cb(that.value));
    }
  }
  // 设定resolve态
  function reject(value) {
    if (that.state === PENDING) {
      that.state = REJECTED;
      that.value = value;
      that.rejectedcb.map(cb => cb(that.value));
    }
  }
  // 执行fn 控制什么时候改变状态
  try {
    fn(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

// 设置回调函数
_Promise.prototype.then = function(onResolved, onRejected) {
  console.log("then");
  const that = this;
  onResolved = typeof onResolved === "function" ? onResolved : v => v;
  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : e => {
          throw e;
        };
  if (that.state === PENDING) {
    console.log("then.pending");
    that.resolvedcb.push(onResolved);
    that.rejectedcb.push(onRejected);
  }
  if (that.state === RESOLVED) {
    console.log("then.resolved");
    onResolved(that.value);
  }
  if (that.state === REJECTED) {
    onRejected(that.value);
  }
};

new _Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(100);
  }, 3000);
}).then(
  v => {
    console.log(v);
  },
  e => {
    throw e;
  }
);
// promise
// then
// then.pending
// resolve(value)
// 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

# 手写 getQueryString

# 原理

API

  • string.prototype.substring(start,end)分割子串

# 代码

function getQueryValue(key, url) {
  let index = url.indexOf("?");
  if (index != -1) {
    for (let item of url.substring(index + 1).split("&")) {
      let obj = item.split("=");
      if (obj[0] == key) {
        return obj[1];
      }
    }
    return "none";
  }
}

console.log(
  getQueryValue(
    "id",
    "https://www.baidu.com/s?id=123&name=why&phone=13876769797"
  )
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 手写 JSONP

# 原理

通过 script 标签获取一段后端传回的 js 如callback({后端数据})自动执行完成请求

  • 拼接 querystring,如:传入 url+{a:1,b:2}+callback 需拼成 url?a=1&b:2&callback=cbname

  • 定义全局函数

  • 执行完后删除节点与函数

  • api:new Date().getTime()

# 代码

// 前端
var jsonp = function(url, data, callback) {
  // 拼接querystring
  let cbname = "cb_" + new Date().getTime();
  cbStr = "&callback=" + cbname;
  let dataStr = "";
  for (let key in data) {
    let str = key + "=" + data[key] + "&";
    dataStr += dataStr + str;
  }
  let queryStr = url + "?" + dataStr + cbStr;
  let script = document.createElement("script");
  script.src = queryStr;
  window[cbname] = function(res) {
    callback(res);
    document.body.removeChild(script);
    delete window[cbname];
  };
  document.body.appendChild(script);
};

jsonp("http://localhost:8080", { count: 1 }, function(data) {
  console.log(data.data);
});

// 服务端
var http = require("http");
var urllib = require("url");
var port = 8080;
var data = { data: "world" };
http
  .createServer(function(req, res) {
    var params = urllib.parse(req.url, true);
    console.log(params);
    if (params.query.callback) {
      //jsonp
      var str = params.query.callback + "(" + JSON.stringify(data) + ")";
      res.end(str);
    } else {
      res.end();
    }
  })
  .listen(port, function() {
    console.log("jsonp server is on");
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45