原生js手写

通过基础的代码,实现原生js的一些方法。更有利于对JavaScript加深理解。

部分内容具体实现可参考冴羽的深入系列

数据类型判断

1
2
3
function typeOf(){
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()
}

数组去重

1
2
3
4
5
6
7
8
9
10
// 类似的还有indexOf,includes,some等等
function unique(arr) {
var res = arr.filter(function(item, index, array) {
return array.indexOf(item) === index
})
return res
}

// es6
var unique = arr => [...new Set(arr)]

数组扁平化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
} else {
result.push(arr[i])
}
}
return result;
}

// es6,参数为处理几层,默认1
Array.prototype.flat(infinity)

浅拷贝

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
// 它不会拷贝对象的继承属性;
// 它不会拷贝对象的不可枚举的属性;
// 可以拷贝 Symbol 类型的属性。
Object.assign(target, ...sources)

// 对象扩展符,和上面的类似
let cloneObj = { ...obj };

// concat 拷贝数组
let newArr = arr.concat();

// slice 拷贝数组
let newArr = arr.slice();

// 手动实现
const shallowClone = (target) => {
if (typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? []: {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = target[prop];
}
}
return cloneTarget;
} else {
return target;
}
}

深拷贝

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
// 简单版,忽略undefund,symbol,无法转换正则日期等对象,不能循环引用对象
JSON.parse(JSON.stringify(Obj))

// 手写版
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)

const deepClone = function (obj, hash = new WeakMap()) {
if (obj.constructor === Date) {
return new Date(obj) // 日期对象直接返回一个新的日期对象
}

if (obj.constructor === RegExp){
return new RegExp(obj) //正则对象直接返回一个新的正则对象
}

//如果循环引用了就用 weakMap 来解决
if (hash.has(obj)) {
return hash.get(obj)
}
let allDesc = Object.getOwnPropertyDescriptors(obj)

//遍历传入参数所有键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)

// 把cloneObj原型复制到obj上
hash.set(obj, cloneObj)

for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
}
return cloneObj
}

发布订阅模式

同观察者模式区别在于,没有中间消息处理中心,可参考观察者模式 vs 发布订阅模式

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
class EventEmitter {
constructor() {
this.cache = {}
}
on(name, fn) {
if (this.cache[name]) {
this.cache[name].push(fn)
} else {
this.cache[name] = [fn]
}
}
off(name, fn) {
let tasks = this.cache[name]
if (tasks) {
const index = tasks.findIndex(f => f === fn || f.callback === fn)
if (index >= 0) {
tasks.splice(index, 1)
}
}
}
emit(name, once = false, ...args) {
if (this.cache[name]) {
// 创建副本,如果回调函数内继续注册相同事件,会造成死循环
let tasks = this.cache[name].slice()
for (let fn of tasks) {
fn(...args)
}
if (once) {
delete this.cache[name]
}
}
}
}

字符串模版

1
2
3
4
5
6
7
8
9
function render(template, data) {
const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
if (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的结构
}
return template; // 如果模板没有模板字符串直接返回
}

防抖

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
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}

// 最终版
function debounce(func, wait, immediate) {
var timeout, result;

var debounced = function () {
var context = this;
var args = arguments;

if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
} else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
return result;
};

debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};

return debounced;
}

节流

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
function throttle(func, wait) {
var context, args;
var previous = 0;

return function() {
var now = +new Date();
context = this;
args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}

// 最终版
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};

var later = function() {
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
func.apply(context, args);
if (!timeout) context = args = null;
};

var throttled = function() {
var now = new Date().getTime();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
};

throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = null;
}
return throttled;
}

柯里化

1
2
3
4
5
6
7
function curry(fn) {
let judge = (...args) => {
if (args.length == fn.length) return fn(...args)
return (...arg) => judge(...args, ...arg)
}
return judge
}

偏函数

1
2
3
4
5
6
function partial(fn, ...args) {
return (...arg) => {
args[index] =
return fn(...args, ...arg)
}
}

JSONP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const jsonp = ({ url, params, callbackName }) => {
const generateUrl = () => {
let dataSrc = ''
for (let key in params) {
if (params.hasOwnProperty(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)
}
})
}

Ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
})
}

call、apply、bind

利用obj.fn的this指向obj。可参考JavaScript深入之call和apply的模拟实现相关内容。

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
Function.prototype.call2 = function (context) {
var context = context || window;
context.fn = this;

var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}

var result = eval('context.fn(' + args +')');

delete context.fn
return result;
}
Function.prototype.apply2 = function (context, arr) {
var context = context || window;
context.fn = this;

var result;
if (!arr) {
result = context.fn();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}

delete context.fn
return result;
}
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);

var fNOP = function () {};

var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
// 处理new时
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}

fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}

new

1
2
3
4
5
6
7
8
9
function objectFactory() {
var obj = new Object()
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);

// 存在值为对象的情况应返回对象,否则返回生成的对象
return typeof ret === 'object' ? ret || obj : obj;
};

instanceof

1
2
3
4
5
6
7
8
9
10
function instanceOf(left, right) {
let proto = left.__proto__
while (true) {
if (proto === null) return false
if (proto === right.prototype) {
return true
}
proto = proto.__proto__
}
}

Object.create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Object.create2 = function(proto, propertyObject = undefined) {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object or null.')
if (propertyObject == null) {
new TypeError('Cannot convert undefined or null to object')
}
function F() {}
F.prototype = proto
const obj = new F()
if (propertyObject != undefined) {
Object.defineProperties(obj, propertyObject)
}
if (proto === null) {
// 创建一个没有原型对象的对象,Object.create(null)
obj.__proto__ = null
}
return obj
}

Object.assign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Object.assign2 = function(target, ...source) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object')
}
let ret = Object(target)
source.forEach(function(obj) {
if (obj != null) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
ret[key] = obj[key]
}
}
}
})
return ret
}

Promise

从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节
要就来45道Promise面试题一次爽到底

------本文结束 感谢阅读------