js基础-各种手写的实现
如果想看基础总结,查看xmind
文件夹下的js
文件
Object.create的实现
将创建的对象a的隐式原型指向传入的显示原型
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
2
3
4
5
instanceof 的实现
右侧构造函数的prototype
是否在左侧原型链中能够找到
function myInstance (left, right) {
let proto = Object.getPrototypeOf(left)
let prototype = right.prototype
while(true) {
if(!proto) return false
if(proto === prototype) return true
proto = Object.getPrototypeOf(proto)
}
}
2
3
4
5
6
7
8
9
new 操作符实现原理
- 使用
Object.create
创建一个空对象,并且隐式原型指向传入的显示原型 - 使用
apply
执行构造函数和确定this
指向 - 返回空对象或者执行后拿到的对象
function myNew(fn, ...rest) {
let obj = Object.create(fn.prototype)
let res = fn.apply(obj, ...rest)
if(res && (typeof res ==='object'|| typeof res === 'function')) {
return res
}
return obj
}
2
3
4
5
6
7
8
防抖函数实现
在n秒内触发事件后,如果再次触发会重新计算时间
function debounce(fn, wait) {
let timer = null
return function() {
args = arguments
if(timer) {
clearTimeout(timer)
timer = null
}
timer = setTimeout(() => {
fn.apply(this, args)
}, wait);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
节流函数的实现
n秒内只会执行一次事件
function throttle(fn, delay) {
let curTime = Date.now()
return function() {
args = arguments,
nowTime = Date.now()
if(nowTime - curTime >= delay) {
curTime = Date.now()
return fn.apply(this, args)
}
}
}
2
3
4
5
6
7
8
9
10
11
call 和 apply的实现
call
和apply
实现是一样的,只是传入的参数一个是数组,一个是单个的
Function.prototype.myCall = function(context, ...args) {
if(!context || context === null) {
context = window
}
let fn = Symbol()
context[fn] = this
return context[fn](...args)
}
2
3
4
5
6
7
8
bind 实现
bind
可以被new
调用,调用后this
指向当前实例- 返回的是一个函数
Function.prototype.myBind = function(context, ...args) {
if(!context || context === null) {
context = window
}
let fn = Symbol()
context[fn] = this
let _this = this
const result = function(...innerArgs) {
// 说明是用new了 是 否早函数
if(this instanceof _this === true) {
this[fn] = _this // 重新赋值函数
this[fn](...[...args, ...innerArgs])
}else {
// 如果是普通函数调用 this指向就是传入的context
context[fn](...[...args, ...innerArgs])
}
}
result.prototype = Object.create(this.prototype)
return result
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
实现一个compose函数
用法如下
function fn1 (x) {
return x + 1
}
function fn2 (x) {
return x + 2
}
function fn3 (x) {
return x + 3
}
function fn4 (x) {
return x + 4
}
const a = compose(fn1, fn2, fn3, fn4)
console.log(a(1)) // 1+4+3+2+1=11
2
3
4
5
6
7
8
9
10
11
12
13
14
15
实现
function compose(...fn) {
if(!fn.length) return v=>v
if(fn.length === 1) return fn[0]
return fn.reduce((pre, cur) => (...args) => pre(cur(...args)))
}
2
3
4
5
用 settimeout 模拟 setinterval
为什么要这么做?
在setinterval
中,因为主线程执行时间不确定
- 可能多个定时器会连续执行
- 某些间隔可能会跳过
function mySetinterval(fn, t) {
let timer = null
function interval() {
fn()
timer = setTimeout(interval, t);
}
interval()
return {
cancel: () => clearTimeout(timer)
}
}
2
3
4
5
6
7
8
9
10
11
实现发布订阅模式
class EventEmitter {
constructor() {
this.events = {}
}
on(type, callback) {
if(!this.events[type]) {
this.events[type] = [callback]
} else {
this.events[type].push(callback)
}
}
emit(type, ...rest) {
this.events[type] && this.events[type].forEach(fn => fn.apply(this, rest))
}
off(type, callback) {
if(!this.events[type]) return
this.events[type] = this.events[type].filter(item => item !== callback)
}
once(type, callback) {
function fn() {
callback()
this.off(type, fn)
}
this.on(type, fn)
}
}
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
数组去重
function uniqueArr(arr) {
return [...new Set(arr)]
}
2
3
数组扁平化
function flatter(arr) {
if(!arr.length) return
return arr.reduce((pre, cur) => (Array.isArray(cur) ? [...pre, flatter(cur)]: [...pre, cur]), [])
}
2
3
4
迭代的方法
function flatter2(arr) {
if(!arr.length) return
if(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
}
2
3
4
5
6
7
es5 模拟 es6的 继承
function Parent (name) {
this.name = name
this.say = () => {
console.log(this.name)
}
}
Parent.prototype.play = () => {
console.log(222)
}
function Children (name) {
Parent.call(this)
this.name = name
}
Children.prototype = Object.create(Parent.prototype)
Children.prototype.constructor = Children
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
深拷贝
function isObject() {
return typeof val === 'object' && val !== null
}
function deepClone(obj, hash = new WeakMap()) {
if(!isObject(obj)) return obj
// 已存在
if(hash.hah(obj)) {
return hash.get(obj)
}
let target = Array.isArray(obj) ? [] : {}
hash.set(obj, target)
Reflect.ownKeys(obj).forEach(item => {
if (isObject(obj[item])) {
target[item] = deepClone(obj[item], hash)
} else {
target[item] = obj[item]
}
})
return target
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
柯里化
柯里化实现,部分求值,吧接收多个参数的函数转变成接收一个参数的函数 用法如下: const add = (a, b, c) => a + b + c; const a = currying(add, 1); console.log(a(2,3))
function currying(fn, ...args) {
const length = fn.length
let allArgs = [...args]
const res = (...newArgs) => {
allArgs = [...allArgs, ...newArgs]
if(allArgs.length === length) {
return fn(...allArgs)
} else {
return res
}
}
return res
}
2
3
4
5
6
7
8
9
10
11
12
13
升级版:add(1)(2)(3)...(n)
function add(...args) {
let allArgs = [...args]
function fn(...newArgs) {
allArgs = [...allArgs, ...newArgs]
return fn
}
fn.toString = function () {
if(!allArgs.length) return
return allArgs.reduce((pre, cur) => pre+cur)
}
return fn
}
2
3
4
5
6
7
8
9
10
11
12
版本号排序
有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。 现在需要对其进行排序,排序的结果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']
arr.sort((a, b) => {
let i = 0
const arr1 = a.split('.')
const arr2 = b.split('.')
while (true) {
const s1 = arr1[i]
const s2 = arr2[i]
i++
if (s1 === undefined || s2 === undefined) {
return arr2.length - arr1.length
}
if (s1 === s2) continue
return s2 - s1
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
列表转树形结构
[ { id: 1, text: '节点1', parentId: 0 //这里用0表示为顶级节点 }, { id: 2, text: '节点1_1', parentId: 1 //通过这个字段来确定子父级 } ... ]
转成 [ { id: 1, text: '节点1', parentId: 0, children: [ { id:2, text: '节点1_1', parentId:1 } ] } ]
function listToTree(data) {
let temp = {}
treeData = []
for(let i=0;i<data.length;i++) {
temp[data[i].id] = data[i]
}
for(let i in temp) {
if(+temp[i].parentId != 0) {
if(!temp[temp[i].parentId].children) {
temp[temp[i].parentId].children = []
}
temp[temp[i].parentId].children.push(temp[i])
}
treeData.push(temp[i])
}
return treeData
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function treeToList(data) {
let res = []
const dfs = (tree) => {
tree.forEach(item => {
if(item.children) {
dfs(item.children)
delete item.children
}
res.push(item)
})
}
dfs(data)
return res
}
2
3
4
5
6
7
8
9
10
11
12
13
14
ajax
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", url, true);
// 设置状态监听函数
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 当请求成功时
if (this.status === 200) {
handle(this.response);
} else {
console.error(this.statusText);
}
};
// 设置请求失败时的监听函数
xhr.onerror = function() {
console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
原型修改、重写
function Person(name) {
this.name = name
}
// 修改原型
Person.prototype.getName = function() {}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
// 重写原型
Person.prototype = {
getName: function() {}
}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false
2
3
4
5
6
7
8
9
10
11
12
13
14
15
下面p
的原型被修改了,p.constructor
不指向Person
。那么只要修改回来就好了
Person.prototype = {
getName: function() {}
}
var p = new Person('hello')
p.constructor = Person
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
2
3
4
5
6
7
原型链指向
要牢记 __proto__
是浏览器的实现,它是用来访问prototype
的。
p.__proto__ // Person.prototype
Person.prototype.__proto__ // Object.prototype
p.__proto__.__proto__ //Object.prototype
p.__proto__.constructor.prototype.__proto__ // Object.prototype
Person.prototype.constructor.prototype.__proto__ // Object.prototype
p1.__proto__.constructor // Person
Person.prototype.constructor // Person
2
3
4
5
6
7
如何获得对象非原型链上的属性?
function iterate(obj){
var res=[];
for(var key in obj){
if(obj.hasOwnProperty(key))
res.push(key+': '+obj[key]);
}
return res;
}
2
3
4
5
6
7
8
继承的几种方式
- 原型链继承
缺点: 无法实现多继承
//父类型
function Person(name, age) {
this.name = name,
this.age = age,
this.play = [1, 2, 3]
this.setName = function () { }
}
Person.prototype.setAge = function () { }
//子类型
function Student(price) {
this.price = price
this.setScore = function () { }
}
Student.prototype = new Person() // 子类型的原型为父类型的一个实例对象
var s1 = new Student(15000)
var s2 = new Student(14000)
console.log(s1,s2)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 借用构造函数继承
缺点:无法继承原型上的方法
function Person(name, age) {
this.name = name,
this.age = age,
this.setName = function () {}
}
Person.prototype.setAge = function () {}
function Student(name, age, price) {
Person.call(this, name, age) // 相当于: this.Person(name, age)
this.price = price
}
var s1 = new Student('Tom', 20, 15000)
2
3
4
5
6
7
8
9
10
11
- 组合继承
使用了es5
的语法,Object.create()
,将子类的原型的原型指向了父类的原型,并且它的构造函数重新指向自己 没有副作用的方式
function Person(name, age) {
this.name = name,
this.age = age
}
Person.prototype.setAge = function () {
console.log("111")
}
function Student(name, age, price) {
Person.call(this, name, age)
this.price = price
this.setScore = function () {}
}
Student.prototype = Object.create(Person.prototype)//核心代码
Student.prototype.constructor = Student//核心代码
var s1 = new Student('Tom', 20, 15000)
console.log(s1 instanceof Student, s1 instanceof Person) // true true
console.log(s1.constructor) //Student
console.log(s1)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18