阅读 277

js代码整理(1)

js代码整理

call apply bind new 模拟实现

/**
使用一个指定的this值调用某个函数
    1. 将函数设为对象属性
    2. 执行函数
    3. 删除该对象属性
 */
Function.prototype.newCall = function(){
    if(typeof this !== 'function') throw new Error('no function')
    var context = arguments[0] || window
    context.fn = this
    var args = []
    for(var i = 1; i < arguments.length; i++){
        args.push(arguments[i])
    }
    var result = eval('context.fn(' + args + ')')
    delete context.fn
    return result
}
复制代码
/*
使用一个指定的this值调用某个函数
    1. 将函数设为对象属性
    2. 执行函数
    3. 删除该对象属性
*/
Function.prototype.newApply = function(){
    if(typeof this !== 'function') throw new Error('no function')
    var context = arguments[0] || window
    context.fn = this
    var args = arguments[1] || []
    var result = eval('context.fn(' + args + ')')
    delete context.fn
    return result
}
复制代码
/*
创建一个新函数,使用第一个参数作为运行新函数的 this  其他参数作为新函数的入参
    1. 取出入参
    2. 新建一个函数 F() 作为参数返回值
    3. 创建一个 函数 f() f.prototype = this.prototype F.prototype = new f()
    4. 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
    5. 函数调用的时候,新传入的参数跟之前提取的参数合并为一个数组
    6. self.apply( this instanceof F ? this : context, arg ) '是否 new 调用', this instanceof fBound"
*/
Function.prototype.newBind = function(){
    if(typeof this !== 'function') throw new Error('no function')
    var context = arguments[0] || window
    var fn = this
    var args = []
    for(let i = 1; i < arguments.length; i++) args.push(arguments[i])
    var bind = function(){
        for(let i = 0; i < arguments.length; i++) args.push(arguments[i])
        context = this instanceof bind ? this : context
        return fn.apply(context, args)
    }
    var F = function(){}
    F.prototype = this.prototype
    bind.prototype = new F()
    return bind
}
复制代码
/*
创建一个用户定义的对象类型实例
    1. 先从 Object.prototype 克隆一个对象 O
    2. Construtor 是外部传入的构造器
    3. O.__proto__ = Construtor.prototype
    4. ret = Construtor.apply(O, arguments) 借用构造器给obj设置属性
    5. ret || O 总是返回一个对象
*/
function New(){
    var obj = new Object();
    var Constructor = Array.prototype.shift.call(arguments)
    obj.__proto__ = Constructor.prototype
    let ret =Constructor.apply(obj, arguments)
    return typeof ret === 'object' ? ret : obj
}
复制代码

防抖节流

/*
无论触发多少次,都是 N 秒后执行
    1. 定义一个定时器 变量
    2. 函数内,获取 this 参数
    3. 函数内,设置定时器
    4. 返回一个包装后的函数
*/
function debounce(fn, wait, immediate) {
    var timeout, result
    var debounced = function(){
        var context = this
        var args = arguments
        // 如果存在,取消后 wait 秒后再调用
        if(timeout) clearTimeout(timeout)
        if(immediate){
            // 立即触发
            // 如果没有触发过,则直接触发
            var caller = !timeout
            timeout = setTimeout(function(){
                timeout = null
            }, wait)
            if(caller) result = fn.apply(context, args)
        }else{
            timeout = setTimeout(function(){
                result = fn.apply(context, args)
                timeout = null
            }, wait)
        }
        return result
    }
    // 取消
    debounced.cancel = function(){
        if(timeout) clearTimeout(timeout)
        timeout = null
    }
    return debounced
}
复制代码
/*
一段时间内无论触发多少次,只执行一次
    1. 定义一个下次触发时间的变量
    2. 通过设置当前时间戳 + 时间间隔的方式来控制是否触发事件
*/
// 时间戳实现,会立刻执行,停止触发后没有办法再执行事件
function throttle(fn, wait){
    var prev = 0
    var throttled = function(){
        var now = +new Date()
        var context = this
        var args = arguments
        if(now - prev > wait){
            fn.apply(context, args)
            prev = now
        }
    }
    return throttled
}
// 定时器实现 n 秒后第一次执行,停止触发后依然会再执行一次事件
function throttle(fn, wait){
    var timeout
    var throttled = function(){
        var context = this
        var args = arguments
        if(!timeout){
            timeout = setTimeout(function(){
                timeout = null
                fn.apply(context, args)
            }, wait)
        }
    }
    return throttled
}
/**
 * 
 * @param {function} fn 执行方法
 * @param {number} wait 等待时间
 * @param {配置} options leading:是否允许立即执行 trailing:是否允许最后一次执行
 * @returns 
 */
function throttle(fn, wait, options) {
    options = options || {}
    var result, context, args, timeout
    var prev = 0
    var later = function(){
        prev = options.leading === false ? 0 : +new Date()
        timeout = null
        result =fn.apply(context, args)
    }
    var t = function(){
        var now = +new Date()
        prev = !prev && options.leading === false ? now : prev
        var r = wait - (now - prev)
        context = this
        args = arguments
        if(r > wait || r <= 0){
            if(timeout){
                clearTimeout(timeout)
                timeout = null
            }
            prev = now
            result = fn.apply(context, args)
        }else if(!timeout && options.trailing !== false){
            timeout = setTimeout(later, r)
        }
        return result
    }
    t.cancel = function(){
        clearTimeout(timeout)
        timeout = null
        prev = 0
    }
    return t
}
复制代码

promise

// 三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
	// 状态: 初始状态为 pending
	status = PENDING
	// 值
	value = null
	// 原因
	reason = null
	// 执行 onFulfilled 的队列
	onFulfilledCallbacks = []
	// 执行 onRejected 的队列
	onRejectedCallbacks = []
	// 构造方法
	constructor(executor){
		try {
			executor(this.resolve, this.reject)
		} catch (error) {
			this.reject(error)
		}
	}
	resolve = value => {
		// 判断是否状态处于等待状态
		if(this.status === PENDING){
			// 改变状态
			this.status = FULFILLED
			// 赋值
			this.value = value
			// 循环调用
			while(this.onFulfilledCallbacks.length){
				this.onFulfilledCallbacks.shift()(this.value)
			}
		}
	}
	reject = reason => {
		// 判断是否状态处于等待状态
		if(this.status === PENDING){
			// 更改状态
			this.status = REJECTED
			// 赋值原因
			this.reason = reason
			// 循环调用
			while(this.onRejectedCallbacks.length){
				this.onRejectedCallbacks.shift()(this.reason)
			}
		}
	}
	then(onFulfilled, onRejected){
		// 可选参数
		const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
		const realOnRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
		const promise1 = new MyPromise((resolve, reject) => {
			// 创建一个微任务执行完成的函数
			const fulfilledMicrotask = () => {
				queueMicrotask(() => {
					try{
						let x = realOnFulfilled(this.value)
						resolvePromise(promise1, x, resolve, reject)
					}catch (error) {
						reject(error)
					}
				})
			}
			// 创建一个微任务执行拒绝的函数
			const rejectMicrotask = () => {
				queueMicrotask(() => {
					try{
						let x = realOnRejected(this.reason)
						resolvePromise(promise1, x, resolve, reject)
					}catch (error) {
						reject(error)
					}
				})
			}
			// 状态确定后直接执行
			if(this.status == FULFILLED){
				fulfilledMicrotask()
			}else if(this.status == REJECTED){
				rejectMicrotask()
			}else{
				// 异步,加入队列
				this.onFulfilledCallbacks.push(fulfilledMicrotask)
				this.onRejectedCallbacks.push(rejectMicrotask)
			}
		})
		// then 返回一个新的 promise
		return promise1
	}
	// catch 方法
	catch (onRejected) {
		this.then(null, onRejected)
	}
	// finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
	finally(fn) {
		return this.then(value => {
			return MyPromise.resolve(fn()).then(() => {
				return value
			})
		}, reason => {
			return MyPromise.resolve(fn()).then(() => {
				return reason
			})
		})
	}
	// 有时需要将现有对象转为 Promise 对象,状态为 fulfilled
	static resolve(parameter){
		// 如果传入一个 promise 对象,直接返回
		if(parameter instanceof MyPromise){
			return parameter
		}
		return new MyPromise(resolve => {
			resolve(parameter)
		})
	}
	// Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为 rejected
	static reject(reason){
		return new MyPromise((resolve, reject) => {
			reject(reason)
		})
	}
	// all 方法,全部成功的时候返回
	static all(promiseList){
		return new MyPromise((resolve, reject) => {
			let count = 0
			let length = promiseList.length
			let result = []
			if(length === 0) return resolve(result)
			for(let i = 0; i < length; i++){
				MyPromise.resolve(promiseList[i]).then(value => {
					count++
					results[i] = value
					// 全部成功
					if(count === length) resolve(result)
				}, reason => {
					// 一次失败
					reject(reason)
				})
			}
		})
	}
	// race 方法 只有一次成功就返回
	static race(promiseList){
		return new MyPromise((resolve, reject) => {
			let length = promiseList.length
			if(length === 0) return resolve()
			for (let i = 0; i < length; i++) {
				MyPromise.resolve(promiseList[i]).then(value => {
					return resolve(value)
				}, reason => {
					return reject(reason)
				})
			}
		})
	}
	// 接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,
	static allSettled(promiseList){
		return new MyPromise((resolve, reject) => {
			let count = 0
			let length = promiseList.length
			let result = []
			if(length === 0) return resolve(result)
			for (let i = 0; i < length; i++) {
				MyPromise.resolve(promiseList[i]).then(value => {
					count++;
					result[i] = {
						value,
						status: FULFILLED
					}
					if(count === length) resolve(result)
				}, reason => {
					count++;
					result[i] = {
						reason,
						status: REJECTED
					}
					if(count === length) resolve(result)
				})
			}
		})
	}
}

function resolvePromise(promise, x, resolve, reject){
	if(x === promise){
		// 循环调用,直接报错
		return reject(new TypeError('The promise and the return value are the same'));
	}
	if(typeof x === 'function' || typeof x === 'object'){
		// null 直接返回
		if(x === null) return resolve(x)

		let then
		try {
			then = x.then
		} catch (error) {
			// 不存在直接拒绝
			return reject(error)
		}
		// 如果对象上面存在 then 方法
		if(typeof then === 'function'){
			let called = false
			try {
				then.call(x, y => {
					// 执行多次忽略
					if(called) return
					called = true
					// 接着执行
					resolvePromise(promise, y, resolve, reject)
				}, r => {
					// 执行多次忽略
					if(called) return
					called = true
					reject(r)
				})
			} catch (error) {
				// 
				if(called) return
				called = true
				reject(error)
			}
		}else{
			// then 不是函数
			resolve(x)
		}
	}else{
		// 如果 x 不为对象或者函数,直接用 x 为参数执行 promise
		resolve(x)
	}
}
复制代码

curry compose pipe partial

// add(1, 2, 3) => add = curry(add) add(1, 2, 3) = add(1)(2)(3) = add(1)(2, 3) = ...
function curry(fn, args){
    var length = fn.length
    args = args || []
    return function(){
        var _args = args.slice().concat(Array.prototype.slice.call(arguments))
        if(_args.length < length){
            return curry.call(this, fn, _args)
        }else{
            return fn.apply(this, _args)
        }
    }
}
复制代码
// a(b(c(d))) => compose(c, b, a)(d)
function compose(){
    var args = arguments
    var start = args.length
    return function(){
        var result = args[--start].apply(this, arguments)
        while(start--) result = args[start].call(this, result)
        return result
    }
}
复制代码
// a(b(c(d))) => pipe(a, b, c)(d)
function pipe(){
    var args = arguments
    var start = 0
    return function(){
        var result = args[start++].apply(this, arguments)
        while(start < args.length){
            result = args[start++].call(this, result)
        }
        return result
    }
}
复制代码
// partial(func, _, 'b', _, 'd')('a', 'c')
var _ = {}
function partial(){
    var fn = arguments[0]
    var args = Array.prototype.slice.call(arguments, 1)
    var bind = function(){
        var position = 0
        _args = args.slice(0)
        var len = _args.length
        for(var i = 0; i < len; i++){
            _args[i] = _args[i] === _ ? arguments[position++] : _args[i]
        }
        while(position < arguments.length) _args.push(arguments[position++])
        return fn.apply(this, _args)
    }
    inherit(fn, bind)
    return bind
}

function inherit(parent, child){
    var F = function(){}
    F.prototype = parent.prototype
    child.prototype = new F()
}
复制代码

发布订阅 EventEmitter & 观察者模式 Observer

/*
    一对多
    消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。
    而是将发布的消息分为不同的类别,然后分别发送给不同的订阅者。
*/
class EventEmitter{
    constructor(){
        this.cache = {}
        return this
    }
    on(type, event){
        if(!this.cache[type]) this.cache[type] = []
        if(this.cache[type].indexOf(event) == -1){
            this.cache[type].push(event)
        }
        return this
    }
    off(type, event){
        if(!this.cache[type]) return this
        this.cache[type] = this.cache[type].filter(e => e !== event)
        return this
    }
    once(type, event){
        let _event = function(){
            event.apply(this, arguments)
            this.off(type, _event)
        }
        this.on(type, _event)
        return this
    }
    emit(){
        let type = arguments[0]
        let args = Array.prototype.slice.call(arguments, 1)
        let list = this.cache[type] || []
        for (const event of list) {
            event.apply(this, args)
        }
        return this
    }
}
复制代码
/*
观察者模式,它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,
这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
在观察者模式中有两个主要角色:Subject(主题)和 Observer(观察者)。
*/
class Observer {
    constructor(name){
        this.name = name
    }
    notify(){
        console.log(`${this.name} has been notified`)
    }
}
class Subject {
    observers = []
    addObserver(observer){
        console.log(observer.name, 'is push')
        this.observers.push(observer)
    }
    deleteObserver(observer){
        console.log('remove observer: ', observer.name)
        this.observers = this.observers.filter(o => o !== observer)
    }
    notifyObservers(){
        console.log('notify')
        this.observers.forEach(o => o.notify())
    }
}
复制代码

inherit

/**
 * 原型链继承
 * 缺点
 * 引用类型的属性被所有实例共享
 * 创建 Child 实例无法向 Parent 传参
 * child.__proto__ === Child.prototype === new Parent
 * child.__proto__.constructor === Parent
*/
function Parent(){
    this.name = 'Parent'
}
Parent.prototype.say = function(){
    console.log(this.name)
}

function Child(){

}

Child.prototype = new Parent()

var child = new Child()
console.log(child.__proto__ === Child.prototype) // true
console.log(child.__proto__.constructor === Parent) // true
child.say()
复制代码
/**
 * 借用构造函数继承
 * 优点
 * 避免了引用类型被共享的问题
 * Child 可以向 Parent 传参
 * 缺点
 * 每次创建实例都会创建一遍父类方法
 * child.__proto__ === Child.prototype
 * child.__proto__.constructor === Child
 */
function Parent(name){
    this.name = name
    this.say = function(){
        console.log(this.name)
    }
}

function Child(){
    Parent.apply(this, arguments)
}

var child = new Child('child')
console.log(child.__proto__ === Child.prototype) // true
console.log(child.__proto__.constructor === Child) // true
child.say() // child
复制代码
/**
 * 组合模式
 * 优点:
 * 避免了引用被共享
 * 不需要重复创建方法
 * 缺点:
 * 需要多 new 一次
 */
function Parent(name){
    this.name = name
}
Parent.prototype.say = function(){
    console.log(this.name)
}
function Child(name){
    Parent.apply(this, arguments)
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
var child = new Child('child')
console.log(child.__proto__ === Child.prototype) // true
console.log(child.__proto__.constructor === Child) // true
child.say() // child
复制代码
/**
 * 原型式继承
 * 缺点
 * 引用类型共享
 * child.__proto__ === parent
 * child.__proto__.constructor === Object
 */
function CreateObj(o){
    function F(){}
    F.prototype = o
    return new F()
}
var parent = {
    name: 'parent',
    say: function(){
        console.log(this.name)
    }
}
var child = CreateObj(parent)
child.say()
console.log(child.__proto__ === parent)
console.log(child.__proto__.constructor === Object)
复制代码
/**
 * 寄生式继承
 * 创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
 */
function CreateObj(o){
    function F(){}
    F.prototype = o
    return new F()
}
var parent = {
    name: 'parent',
    say: function(){
        console.log(this.name)
    }
}
var Child = function(o, name){
    var clone = CreateObj(o)
    clone.name = name
    return clone
}
var child = Child(parent, 'child')
child.say()
console.log(child.__proto__ === parent)
console.log(child.__proto__.constructor === Object)
复制代码
/**
 * 寄生组合式继承
 * 优点
 * 只调用了一次父类的构造函数
 * 避免了在 Parent.prototype 上面创建不必要的、多余的属性
 * 原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf
 */
function inherit(Child, Parent){
    function F(){}
    F.prototype = Parent.prototype
    Child.prototype = new F()
    Child.prototype.constructor = Child
}
function Parent(name){
    this.name = name
}
Parent.prototype.say = function(){
    console.log(this.name)
}
function Child(){
    Parent.apply(this, arguments)
}
inherit(Child, Parent)
var child = new Child('child')
child.say()
console.log(child.__proto__ === Child.prototype)
console.log(child.__proto__.constructor === Child)
复制代码

create object

/*
工厂模式
函数 factory 能够接受参数来构建一个包含必要信息的对象,可以无数次的调用这个函数。而每次都返回一个对象
优点:
    批量生成相似对象
缺点:
    对象指向同一个原型 ,生成对象无法识别
    每个方法都需要创建一次
*/
function factory(name, age){
    var o = new Object()
    o.name = name
    o.age = age
    o.say = function(){
        console.log(`name=${this.name}, age=${this.age}`)
    }
    return o
}

var a = factory('a', 20)
var b = factory('b', 10)
a.say()
b.say()
复制代码
/*
构造函数模式
通过构造函数来创建特定类型的对象 要通过 new 操作符
优点:
    实例都可以被识别成一种特定类型
缺点:
    每次创建实例 每个实例方法都需要被创建一次
*/

function create(name, age){
    this.name = name
    this.age = age
    this.say = function(){
        console.log(`name=${this.name}, age=${this.age}`)
    }
}

var a = new create('a', 20)
var b = new create('b', 10)
a.say()
b.say()
复制代码
/*
原型模式
每个函数都有一个 prototype,可以使用原型对象,让所有的对象实例共享它所包含的属性方法
优点:
    方法不会重复创建
缺点:
    所有属性方法共享,不能初始化参数
*/
function Prototype(){

}
Prototype.prototype = {
    constructor: Prototype,
    name: 'a',
    say:function(){
        console.log(this.name)
    }
}

var a = new Prototype()
var b = new Prototype()
a.say()
b.say()
复制代码
/*
组合模式
构造函数跟原型模式双剑合璧。构造函数模式用于定义实例属性,原型属性定义方法和共享属性。
优点
    该共享的共享,该私有的私有
缺点
    封装性不足
*/

function Create(name, age){
    this.name = name;
    this.age = age;
}
Create.prototype.say = function(){
    console.log(`name=${this.name}, age=${this.age}`)
}

var a = new create('a', 20)
var b = new create('b', 10)
a.say()
b.say()
复制代码
/*
动态原型模式
为了解决独立的构造函数和原型,动态原型模式,把信息封装到构造函数中,而且通过在构造函数初始化原型
优点:
    组合模式的优点,且封装性更好
缺点:
    多判断一次
*/

function CreatePrototype(name, age){
    this.name = name
    this.age = age
    if(typeof this.say !== 'function'){
        CreatePrototype.prototype.say = function(){
            console.log(`name=${this.name}, age=${this.age}`)
        }
    }
}

var a = new CreatePrototype('a', 20)
var b = new CreatePrototype('b', 10)
a.say()
b.say()
复制代码
/*
寄生构造函数模式
创建一个函数,该函数的作用仅仅在封装创建对象的代码,然后返回新创建的对象
缺点:
    工厂模式 + new
*/

function NewFactory(name, age){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.say = function () {
        console.log(`name=${this.name}, age=${this.age}`)
    };
    return o;
}

var a = new NewFactory('a', 20)
var b = new NewFactory('b', 10)
a.say()
b.say()
复制代码
/*
稳妥函数模式
那些没有公共属性,而且其方法不引用this
优点:
    不需要 new 不引用 this
缺点:
    生成对象无法识别
*/

function StaticCreate(name, age){
    var o = new Object()
    o.say = function () {
        console.log(`name=${name}, age=${age}`)
    }
    return o
}

var a = new StaticCreate('a', 20)
var b = new StaticCreate('b', 10)
a.say()
b.say()
复制代码

instanceof

function instanceOf(l, r){
    while(true){
        if(l === null) return false
        if(l.__proto__ === r.prototype) return true
        l = l.__proto__
    }
}
复制代码

相等判断

function eq(a, b, aStack, bStack){
    if(a === b) return a !== 0 || 1 / a === 1 / b
    if(a === null || b === null) return false
    if(a !== a) return b !== b
    var type = typeof a
    if(type !== 'object' && type !== 'function' && typeof b !== 'object') return false
    return deepEq(a, b, aStack, bStack)
}

function deepEq(a, b, aStack, bStack){
    var type = Object.prototype.toString.call(a)
    if(type !== Object.prototype.toString.call(b)) return false

    switch (type) {
        case '[object Number]':
            if(+a !== +a) return +b !== +b
            return +a === +b && 1 / a === 1 / b
        case '[object String]':
        case '[object RegExp]':
            return '' + a === '' + b
        case '[object Date]':
        case '[object Boolean]':
            return +a === +b
        case '[object Symbol]':
            return Symbol.prototype.valueOf.call(a) === Symbol.prototype.valueOf.call(b)            
        default:
            break;
    }

    var areArrays = type === '[object Array]';
    if(!areArrays){
        if(typeof a !== 'object' && typeof b !== 'object') return false
        var aCtor = a.constructor
        var bCtor = b.constructor
        if(aCtor !== bCtor
            && !(typeof aCtor === 'function' && typeof bCtor === 'function' && aCtor instanceof aCtor && bCtor instanceof bCtor)
            && ('constructor' in a && 'constructor' in b)
        ) return false
    }

    aStack = aStack || []
    bStack = bStack || []
    var length = aStack.length
    if(length !== bStack.length) return false
    while(length--) if(aStack[length] === a) return bStack[length] === b

    aStack.push(a)
    bStack.push(b)
    if(areArrays){
        length = a.length
        if(length !== b.length) return false
        while(length--) if(!eq(a[length], b[length], aStack, bStack)) return false
    }else{
        var keys = Object.keys(a)
        length = keys.length
        if(length !== Object.keys(b).length) return false
        while(length--) if(!eq(a[keys[length]], b[keys[length]], aStack, bStack)) return false
    }
    aStack.pop()
    bStack.pop()
    return true
}
复制代码

去重

/**
 * 去重
 * @param {[]} array 待去重数组
 * @param {Boolean} isSorted 是否排序
 * @param {function} iteratee 比较函数
 * @param {object} context 作用域
 */
function unique(array, isSorted, iteratee, context){
    if(typeof isSorted !== 'boolean'){
        context = iteratee
        iteratee = isSorted
        isSorted = false
    }
    if(isSorted == true){
        iteratee = function(value){ return value }
    }else if(typeof iteratee !== 'function'){
        iteratee = function(value){
            value = value instanceof RegExp ? value.toString() : value
            var key = (typeof value) + JSON.stringify(value)
            if(this[key]) return false
            this[key] = true
            return true
        }
    }
    iteratee = iteratee.bind(context || {})
    var result = []
    var last
    for(var i = 0; i < array.length; i++){
        var value = array[i]
        var computed = iteratee(value, i, array)
        if(isSorted){
            if(!i || last !== computed) result.push(value)
            last = value
        }else{
            if(computed) result.push(value)
        }
    }
    return result
}
复制代码

冒泡排序

/**
最外层的 for 循环每经过一轮,
剩余数字中的最大值就会被移动到当前轮次的最后一位,
中途也会有一些相邻的数字经过交换变得有序。
总共比较次数是 (n-1)+(n-2)+(n-3)+…+1(n−1)+(n−2)+(n−3)+…+1。
这种写法相当于相邻的数字两两比较,并且规定:“谁大谁站右边”。
经过 n-1n−1 轮,数字就从小到大排序完成了。
 */
/**
 * 时间:O(n*n)
 * 空间:O(1)
 * 稳定
 * @param {[Number]} arr 待排序数组
 * @returns {[Number]}
 */
var bubbleSort = (arr) => {
    let n = arr.length
    for (let i = 0; i < n - 1; i++) {
        for (let j = 0; j < n - 1 - i; j++) {
            if(arr[j] > arr[j + 1]){
                swap(arr, j, j + 1)
            }
        }
    }
    return arr
}
// 冒泡排序改进版:如果某一趟没有发生交换,则提前跳出
var bubbleSort = arr => {
    let n = arr.length
    let swapped = true
    for (let i = 0; i < n - 1; i++) {
        if(!swapped) break
        swapped = false
        for (let j = 0; j < n - i - 1; j++) {
            if(arr[j] > arr[j + 1]){
                swap(arr, j, j + 1)
                swapped = true
            }
        }
    }
    return arr
}
// 冒泡排序改进版:记录上一次交换的位置
var bubbleSort = arr => {
    let lastIndex = arr.length - 1
    let swapped = true
    let swappedIndex = 0
    while (swapped){
        swapped = false
        for (let i = 0; i < lastIndex; i++) {
            if(arr[i] > arr[i + 1]){
                swap(arr, i, i + 1)
                swapped = true
                swappedIndex = i
            }
        }
        lastIndex = swappedIndex
    }
    return arr
}
复制代码

插入排序

/**
 * 插入排序:在新数字插入过程中,不断与前面的数字交换,直到找到自己合适的位置。交换法
 * 时间:O(n*n)
 * 空间:O(1)
 * 稳定
 * @param {[Number]} arr 待排序数组
 */
var InsertSort = function(arr){
    for (let i = 1; i < arr.length; i++) {
        let j = i
        while (j >= 1 && arr[j - 1] > arr[j]){
            swap(arr, j, j - 1)
            j--
        }
    }
    return arr
}
/**
 * 插入排序:在新数字插入过程中,不断与前面的数字交换,直到找到自己合适的位置。插入一次法
 * 时间:O(n*n)
 * 空间:O(1)
 * 稳定
 * @param {[Number]} arr 待排序数组
 */
var InsertSort = function(arr){
    for (let i = 1; i < arr.length; i++) {
        let x = arr[i]
        let j = i - 1
        while (j >= 0 && arr[j] > x){
            arr[j + 1] = arr[j]
            j--
        }
        arr[j + 1] = x
    }
    return arr
}
复制代码

希尔排序

/**
 * 希尔排序:本质上是对插入排序的一种优化,它利用了插入排序的简单,
 * 又克服了插入排序每次只交换相邻两个元素的缺点。它的基本思想是:
 * 将待排序数组按照一定的间隔分为多个子数组,每组分别进行插入排序。
 * 这里按照间隔分组指的不是取连续的一段数组,而是每跳跃一定间隔取一个值组成一组
 * 逐渐缩小间隔进行下一轮排序
 * 最后一轮时,取间隔为 11,也就相当于直接使用插入排序。
 * 但这时经过前面的「宏观调控」,数组已经基本有序了,所以此时的插入排序只需进行少量交换便可完成
 * 时间:O(n)~O(n*n) 普遍最高在O(1.3*n)
 * 空间:O(1)
 * 不稳定
 * @param {[Number]} arr 待排序数组
 */
var ShellSort = arr => {
    let maxGap = 1
    let n = arr.length
    while (maxGap <= n / 3){
        maxGap = maxGap * 3 + 1
    }
    for (let gap = maxGap; gap > 0; gap = (gap - 1) / 3) {
        for (let i = gap; i < n; i++) {
            let curr = arr[i]
            let preIndex = i - gap
            while (preIndex >= 0 && curr < arr[preIndex]){
                arr[preIndex + gap] = arr[preIndex]
                preIndex -= gap
            }
            arr[preIndex + gap] = curr
        }
    }
    return arr
}
复制代码

选择排序

/**
 * 选择排序:双重循环遍历数组,每经过一轮比较,找到最小元素的下标,将其交换至首位。
 * 时间:O(n*n)
 * 空间:O(1)
 * 不稳定
 * @param {[Number]} arr 待排序数组
 */
var SelectSort = (arr) => {
    let n = arr.length
    let min
    for (let i = 0; i < n - 1; i++) {
        min = i
        for (let j = i + 1; j < n; j++) {
            if(arr[j] < arr[min]) {
                min = j
            }
        }
        swap(arr, i, min)
    }
    return arr
}

/**
 * 选择排序改进版:双重循环遍历数组,每经过一轮比较,找到最小元素的下标,将其交换至首位。找到最大的坐标放在最后一位
 * 时间:O(n*n)
 * 空间:O(1)
 * 不稳定
 * @param {[Number]} arr 待排序数组
 */
var SelectSort = (arr) => {
    let n = arr.length
    let min
    let max
    for (let i = 0; i < n >> 1; i++) {
        min = i
        max = i
        for (let j = i + 1; j < n - i; j++) {
            if(arr[j] < arr[min]) {
                min = j
            }
            if(arr[j] > arr[max]) {
                max = j
            }
        }
        if(arr[min] == arr[max]) break
        swap(arr, i, min)
        // 如果最大值的下标刚好是 i,由于 arr[i] 和 arr[min] 已经交换了,所以这里要更新 max 的值。
        if(i == max) max = min
        swap(arr, max, n - i - 1)
    }
    return arr
}
复制代码

归并排序

/**
 * 归并排序:将一个数组分割成N个小数组,然后将小数组逐一合并成一个个有序的数组
 * 时间: O(nlogn)
 * 空间: O(n)
 * 稳定
 * @param {*} arr 待排序数组
 */
var MergeSort = function(arr){
    if(!arr.length) return
    let result = mergeSort(arr, 0 , arr.length - 1)
    for (let i = 0; i < arr.length; i++) {
        arr[i] = result[i]
    }
    return arr
}
/**
 * 二分分割数组
 * @param {[Number]} arr 
 * @param {Number} start 
 * @param {Number} end 
 * @returns 
 */
var mergeSort = function(arr, start, end){
    if(start == end) return [arr[start]]
    let mid = Math.floor((start + end) / 2)
    let left = mergeSort(arr, start, mid)
    let right = mergeSort(arr, mid + 1, end)
    return merge(left, right)
}
/**
 * 合并两个有序数组
 * @param {[Number]} arr1 
 * @param {[Number]} arr2 
 */
var merge = function(arr1, arr2){
    let result = new Array(arr1.length + arr2.length)
    let i = 0
    let j = 0
    while (i < arr1.length && j < arr2.length){
        result[i + j] = arr1[i] < arr2[j] ? arr1[i++] : arr2[j++]
    }
    while(i < arr1.length){
        result[i + j] = arr1[i++]
    }
    while(j < arr2.length){
        result[i + j] = arr2[j++]
    }
    return result
}

/**
 * 归并排序空间优化版:将一个数组分割成N个小数组,然后将小数组逐一合并成一个个有序的数组
 * 时间: O(nlogn)
 * 空间: O(n)
 * 稳定
 * @param {*} arr 待排序数组
 */
var MergeSort = function(arr){
    if(!arr.length) return
    let result = new Array(arr.length)
    mergeSort(arr, 0 , arr.length - 1, result)
    return arr
}
/**
 * 二分分割数组
 * @param {[Number]} arr 
 * @param {Number} start 
 * @param {Number} end 
 * @returns 
 */
var mergeSort = function(arr, start, end, result){
    if(start == end) return 
    let mid = Math.floor((start + end) / 2)
    mergeSort(arr, start, mid, result)
    mergeSort(arr, mid + 1, end, result)
    merge(arr, start, end, result)
}
/**
 * 合并两个有序数组
 * @param {[Number]} arr1 
 * @param {[Number]} arr2 
 */
var merge = function(arr, start, end, result) {
    let end1 = Math.floor((start + end) / 2)
    let start2 = end1 + 1
    let end2 = end

    let index1 = start
    let index2 = start2

    while(index1 <= end1 && index2 <= end2) {
        result[index1 + index2 - start2] = arr[index1] <= arr[index2] ? arr[index1++] : arr[index2++]
    }
    while(index1 <= end1){
        result[index1 + index2 - start2] = arr[index1++]
    }
    while(index2 <= end2){
        result[index1 + index2 - start2] = arr[index2++]
    }
    while(start <= end){
        arr[start] = result[start++]
    }
    return arr
}
复制代码

快速排序

/*
 * @Author: xiaohuolong
 * @Date: 2021-03-29 09:05:24
 * @LastEditors: xiaohuolong
 * @LastEditTime: 2021-05-14 20:36:10
 * @FilePath: /js-demo/algorithm/Sort/QuickSort.js
 */
/**
 * 划分函数:从头开始,双指针划分
 * @param {*} arr 数组
 * @param {*} p 左指针
 * @param {*} q 右指针
 * @returns 
 */
var partition = (arr, p, q) => {
    let x = arr[p]
    let i = p
    let j = p + 1
    for (j; j <= q; j++) {
        if(arr[j] < x){
            swap(arr, ++i, j)
        }
    }
    swap(arr, i, p)
    return i
}
/**
 * 划分函数:从头开始,双指针划分
 * @param {*} arr 数组
 * @param {*} p 左指针
 * @param {*} q 右指针
 * @returns 
 */
var partition = (arr, p, q) => {
    // 取第一个数为基数
    let pivot = arr[p]
    // 从第二个数开始分区
    let i = p + 1
    // 右边界
    let j = q
    // 相遇时退出循环
    while (i < j){
        // 找到第一个大于基数的位置
        // console.log(arr.join(','), i, j);
        while (i < j && arr[i] <= pivot) i++
        // console.log(i, arr[i], pivot);
        if(i != j){
            // 交换到右分区,使得左边分区都小于或等于基数,右边分区大于或等于基数
            swap(arr, i, j)
            j--
        }
    }
    // console.log('while-end')
    // console.log(arr.join(','))
    // 如果两个指针相等,单独比较 arr[j] pivot
    if(i == j && arr[j] > pivot) j--
    // 将基数和中间树交换
    // console.log(j, p, arr[p], arr[j]);
    if(j != p) swap(arr, p, j)
    // console.log(arr.join(','))
    // 返回中间的下标
    return j
}

/**
 * 划分函数:将 arr 从 p 到 q 分区,左边区域比基数小,右边区域比基数大,然后返回中间值的下标
 * @param {*} arr 数组
 * @param {*} p 左指针
 * @param {*} q 右指针
 * @returns 
 */
var partition = (arr, p, q) => {
    // 取第一个数为基数
    let pivot = arr[p]
    // 从第二个数开始分区
    let i = p + 1
    // 右边界
    let j = q
    // 相遇时退出循环
    while (i < j){
        // 找到第一个大于基数的位置
        // console.log(arr.join(','), i, j);
        while (i < j && arr[i] <= pivot) i++
        while (i < j && arr[j] >= pivot) j--
        // console.log(i, arr[i], arr[j], pivot);
        if(i < j){
            // 交换到右分区,使得左边分区都小于或等于基数,右边分区大于或等于基数
            swap(arr, i, j)
            i++
            j--
        }
    }
    // console.log('while-end')
    // console.log(arr.join(','))
    // 如果两个指针相等,单独比较 arr[j] pivot
    if(i == j && arr[j] > pivot) j--
    // 将基数和中间树交换
    swap(arr, p, j)
    // console.log(arr.join(','))
    // 返回中间的下标
    return j
}
/**
 * 划分函数:将 arr 从 p 到 q 分区,左边区域比基数小,右边区域比基数大,然后返回中间值的下标
 * @param {*} arr 数组
 * @param {*} p 左指针
 * @param {*} q 右指针
 * @returns 
 */
var partition = (arr, p, q) => {
    let pivot = arr[p]
    let i = p + 1
    let j = q
    while (i < j){
        while (i < j && arr[i] <= pivot) i++
        while (i < j && arr[j] >= pivot) j--
        if(i != j){
            swap(arr, i, j)
            i++
            j--
        }
    }
    if(i == j && arr[j] >= pivot) j--
    swap(arr, p, j)
    return j
}
/**
 * 快速排序算法的基本思想是:
 * 从数组中取出一个数,称之为基数(pivot)
 * 遍历数组,将比基数大的数字放到它的右边,比基数小的数字放到它的左边。遍历完成后,数组被分成了左右两个区域
 * 将左右两个区域视为两个数组,重复前两个步骤,直到排序完成
 * 时间复杂度: O(nlogn ~ n*n)
 * 空间复杂度: O(logn ~ n)
 * 稳定: 不稳定
 * @param {*} arr 
 * @param {*} p 
 * @param {*} q 
 * @returns 
 */
var sortArray = function(arr){
    shuffle(arr)
    return QuickSort(arr, 0, arr.length - 1)
}
var QuickSort = (arr, p, q) => {
    if(p < q){
         // 将数组分区,并获得中间值的下标
        const r = partition(arr, p, q)
        // 对左边区域快速排序
        QuickSort(arr, p, r - 1)
        // 对右边区域快速排序
        QuickSort(arr, r + 1, q)
    }
    return arr
}

/**
 * 优化: 将排序数组用洗牌算法打乱
 * @return {number[]}
 */
var shuffle = function(nums) {
    let n = nums.length
    // [n, m] 内的一个随机整数
    var randOne = function(n, m) {
        return Math.floor(Math.random() * (m - n + 1)) + n;
    };
    for (let i = 0; i < n; i++) {
        let rand = randOne(i, n - 1)
        swap(nums, i, rand)
    }
    return nums
};


// 交换
var swap = function(arr, i, j) {
    let temp = arr[i]
    arr[i] = arr[j]
    arr[j] = temp
};
复制代码

堆排序

/**
 * 堆:符合以下两个条件之一的完全二叉树:
 * 根节点的值 ≥ 子节点的值,这样的堆被称之为最大堆,或大顶堆;
 * 根节点的值 ≤ 子节点的值,这样的堆被称之为最小堆,或小顶堆。
 * 堆排序过程如下:
 * 用数列构建出一个大顶堆,取出堆顶的数字;
 * 调整剩余的数字,构建出新的大顶堆,再次取出堆顶的数字;
 * 循环往复,完成整个排序。
 * 时间: O(nlogn)
 * 空间: O(n)
 * 不稳定
 */
class Heap {
    constructor(size, handle){
        this.list = new Array(size + 1)
        this.list[0] = size
        this.handle = handle || this.handle
        this.realSize = 0
    }
    handle(a, b){
        return a > b
    }
    getParentIndex(i){
        return Math.floor(i / 2)
    }
    getLeftChildIndex(i){
        return i * 2
    }
    getRightChildIndex(i){
        return i * 2 + 1
    }
    swap(i, j){
        let temp = this.list[i]
        this.list[i] = this.list[j]
        this.list[j] = temp
    }
    heapUp(i){
        // 1. 找到节点的父节点,判断是否需要交换
        let j = this.getParentIndex(i)
        while(this.list[i] !== undefined && this.handle(this.list[i], this.list[j]) && j >= 1){
            this.swap(i, j)
            i = j
            j = this.getParentIndex(i)
        }
    }
    heapDown(i){
        let n = Math.floor(this.realSize / 2)
        while(i < this.realSize && i <= n){
            let l = this.getLeftChildIndex(i)
            let r = this.getRightChildIndex(i)
            let left = this.list[l]
            let right = this.list[r]
            let curr = this.list[i]
            let j = i
            // console.log(curr, left, right)
            if(left === undefined && right === undefined) break
            if(right === undefined && this.handle(curr, left)) break
            if(this.handle(curr, left) && this.handle(curr, right)) break
            if(left === undefined) j = r
            else if(right === undefined) j = l
            else{
                if(this.handle(left, right)){
                    j = l
                }else{
                    j = r
                }
            }
            this.swap(i, j)
            i = j
        }
    }
    add(val){
        if(this.realSize >= this.list[0]){
            if(this.handle(this.peek(), val)){
                this.pop()
            }else{
                return
            }
        }
        this.realSize++
        this.list[this.realSize] = val
        // 插入后上浮
        this.heapUp(this.realSize)
    }
    pop(){
        let head = this.list[1]
        this.list[1] = this.list[this.realSize]
        this.list[this.realSize--] = undefined
        this.heapDown(1)
        return head
    }
    peek(){
        return this.list[1] != undefined ? this.list[1] : -1
    }
    size(){
        return this.realSize
    }
    heapify(list = [], handle){
        let size = list.length
        this.list = new Array(size + 1)
        this.list[0] = size
        this.handle = handle || this.handle
        this.realSize = 0
        for (let i = 0; i < size; i++) {
            this.add(list[i])
        }
    }
}
class MinHeap extends Heap{
    constructor(size, handle){
        super(size, handle)
    }
    handle(a, b){
        return a < b
    }
}
var sortArray = function(arr){
    let n = arr.length
    let heap = new MinHeap(n)
    heap.heapify(arr)
    for (let i = 0; i < n; i++) {
        arr[i] = heap.pop()
    }
    return arr
}
复制代码

计数排序

/**
 * 计数排序
 * 每次遍历都是进行 n 次或者 k 次,所以计数排序的时间复杂度为 O(n + k),k 表示数据的范围大小。
 * 用到的空间主要是长度为 k 的计数数组和长度为 n 的结果数组,所以空间复杂度也是 O(n + k)
 * 稳定
 * 计数排序只适用于数据范围不大的场景
 * @param {*} arr 
 */
var CountingSort = (arr) => {
    // 判空及防止数组越界
    if (arr == null || arr.length <= 1) return arr;
    // 找最大最小
    let max = arr[0]
    let min = arr[0]
    for (let i = 1; i < arr.length; i++) {
        max = Math.max(max, arr[i])
        min = Math.min(min, arr[i])
    }
    let range = max - min + 1
    // 建立长度为 range 的数组,下标 0~8 对应数字 1~9
    let counting = new Array(range).fill(0)
    // 遍历 arr 中的每个元素
    for (const x of arr) {
        // 将每个整数出现的次数统计到计数数组中对应下标的位置
        counting[x-min] += 1
    }
    // 记录前面比自己小的数字的总数
    let preCounts = 0
    for (let i = 0; i < counting.length; i++) {
        // 将 counting 计算成当前数字在结果中的起始下标位置。位置 = 前面比自己小的数字的总数。
        preCounts += counting[i]
        // 当前的数字比下一个数字小,累计到 preCounts 中
        counting[i] = preCounts - counting[i]
    }
    let result = new Array(arr.length)
    for (const x of arr) {
        // counting[x - 1] 表示此元素在结果数组中的下标
        let index = counting[x - min]
        result[index] = x
        // 更新 counting[x - 1],指向此元素的下一个下标
        counting[x-min]+=1
    }
    for (let i = 0; i < arr.length; i++) {
        arr[i] = result[i]
    }
    return arr
}
复制代码

基数排序

/**
 * 基数排序
 * 基数排序可以分为以下三个步骤:
 * 找出数组中最大的数字的位数 maxDigit
 * 获取数组中每个数字的基数
 * 遍历 maxDigit 轮数组,每轮按照基数对其进行排序
 * 时间复杂度为 O(d(n + k))
 * 空间复杂度为 O(n+k)
 * 稳定
 * @param {number[]} arr 
 */
var radixSort = function(arr){
    if(arr.length <= 1) return arr
    let max = -Infinity
    for (const x of arr) {
        max = Math.max(max, Math.abs(x))
    }
    let maxDigit = 0;
    while (max != 0) {
        maxDigit++;
        max = Math.floor(max / 10);
    }
    let counting = new Array(20).fill(0)
    let result = new Array(arr.length)
    let dev = 1
    for (let i = 0; i < maxDigit; i++) {
        // console.log(arr)
        for (const x of arr) {
            let radix = (dev <= Math.abs(x) ? parseInt(x / dev) % 10 : 0) + 9
            // console.log(radix, x)
            counting[radix]++
        }
        // console.log(counting)
        for (let j = 1; j < counting.length; j++) {
            counting[j] += counting[j - 1]
        }
        for (let j = arr.length - 1; j >= 0; j--){
            let radix = (dev <= Math.abs(arr[j]) ? parseInt(arr[j] / dev) % 10 : 0) + 9 
            result[--counting[radix]] = arr[j]
        }
        // console.log(result)
        for (let j = 0; j < result.length; j++) {
            arr[j] = result[j];
        }
        counting.fill(0)
        dev *= 10
    }
    return arr
}
复制代码

diff

// react
function diff1(prev, next){
    let container = prev.slice()
    let nextIndex = {}
    let prevIndex = {}
    let prevLength = prev.length
    let nextLength = next.length
    let lastIdx = 0
    // 生成映射表,方便查找
    for(let i = 0; i < prevLength; i++) prevIndex[prev[i].key] = i
    for(let i = 0; i < nextLength; i++){
        // 1. 遍历 next 取出 nextNode,位置 i
        let nextNode = next[i]
        nextIndex[nextNode.key] = i
        // 3. 如果找到节点,位置为 j ,更新节点,判断 j 是否小于 lastIdx(lastIdx = 0),如果不小于,则 lastIdx = j
        let j = prevIndex[nextNode.key]
        if(j !== undefined){
            patch(prev[j], nextNode)
            // 4. 如果小于 lastIdx 则将节点插入到 next[i - 1] 之后
            if(j < lastIdx){
                container = insertBefore(next[i - 1], nextNode, container, 1)
            }else{
                lastIdx = j
            }
        }else{
            // 2. 在 prev 中查找 nextNode.key 一致的节点,如果没找到,则创建后插入到 next[i - 1] 之后
            container = insertBefore(next[i - 1], nextNode, container, 1)
        }
    }
    // 5. 遍历 next 结束后,遍历 prev 如果节点不在 next 中,则删除节点
    for(let p of prev){
        if(nextIndex[p.key] === undefined){
            remove(p, container)
        }
    }
    return container
}
复制代码
// vue2
function diff2(prev, next){
    let container = prev.slice()
    // 1. 声明4个变量 prevStart,prevEnd,nextStart,nextEnd 取出 prevStartNode, prevEndNode,nextStartNode,nextEndNode
    let prevStart = 0
    let nextStart = 0
    let prevEnd = prev.length - 1
    let nextEnd = next.length - 1
    let prevStartNode = prev[prevStart]
    let prevEndNode = prev[prevEnd]
    let nextStartNode = next[nextStart]
    let nextEndNode = next[nextEnd]
    let prevIndex = {}
    for(let i = 0; i < prev.length; i++) prevIndex[prev[i].key] = i
    // 2. 循环,条件不满足 prevStart <= prevEnd && nextStart <= nextEnd 跳出循环
    while(prevStart <= prevEnd && nextStart <= nextEnd){
        // 3. prevStartNode 或 prevEndNode 是否存在,不存在则 prevStart++,prevEnd--,回到 2
        if(!prevStartNode){
            prevStartNode = prev[++prevStart]
        }else if(!prevEndNode){
            prevEndNode = prev[--prevEnd]
        }else if(prevStartNode.key === nextStartNode.key){
            // 4. prevStartNode.key == nextStartNode.key,相等则更新节点, prevStart++ nextStart++,回到2
            patch(prevStartNode, nextStartNode)
            prevStartNode = prev[++prevStart]
            nextStartNode = next[++nextStart]
        }else if(prevEndNode.key === nextEndNode.key){
            // 5. prevEndNode.key == nextEndNode.key,相等则更新节点, prevEnd-- nextEnd --, 回到2
            patch(prevEndNode, nextEndNode)
            prevEndNode = prev[--prevEnd]
            nextEndNode = next[--nextEnd]
        }else if(prevStartNode.key === nextEndNode.key){
            // 6. prevStartNode.key == nextEndNode.key,相等则更新节点, 将 prevStartNode 插入到 prevEndNode.next 之前,prevStart++ nextEnd--,回到 2 
            patch(prevStartNode, nextEndNode)
            container = insertBefore(prevEndNode, prevStartNode, container, 1)
            prevStartNode = prev[++prevStart]
            nextEndNode = next[--nextEnd]
        }else if(prevEndNode.key === nextStartNode.key){
            // 7. prevEndNode.key == nextStartNode.key,相等则更新节点,将 prevEndNode 插入到 prevStartNode 之前,prevEnd-- nextStart++,回到 2
            patch(prevEndNode, nextStartNode)
            container = insertBefore(prevStartNode, prevEndNode, container)
            prevEndNode = prev[--prevEnd]
            nextStartNode = next[++nextStart]
        }else{
            // 8. 如果全都不相等,查找 prev,看是否存在一个与 nextStartNode.key相同的节点,有则更新,没有则创建一个新的节点,将其插入到 prevStartNode 之前,nextStart++,prev[j]标记为操作过,回到2
            let j = prevIndex[nextStartNode.key]
            if(j !== undefined){
                patch(prev[j], nextStartNode)
                prev[j] = undefined
            }
            container = insertBefore(prevStartNode, nextStartNode, container)
            nextStartNode = next[++nextStart]
        }
    }
    if(prevEnd < prevStart){
        // 9. 循环结束后 如果 prevEnd < prevStart 证明存在新节点未处理,从 nextStart 开始 插入节点,直到newEnd,每次节点都插入在 next[newEnd + 1]之前
        let ref = next[nextEnd + 1] || null
        while(nextStart <= nextEnd){
            container = insertBefore(ref, next[nextStart++], container)
        }
    }else if(nextEnd < nextStart){
        // 10. 如果 nextEnd < newStart 证明存在节点被移除,未处理,从 prevStart 开始 移除节点,直到 prevEnd
        while(prevStart <= prevEnd){
            remove(prev[prevStart++], container)
        }
    }
    return container
}
复制代码
// vue3
function diff3(prev, next){
    let container = prev.slice()
    // 1. j 表示当前匹配到第几个位置,初始值为0
    let j = 0
    let prevEnd = prev.length - 1
    let nextEnd = next.length - 1
    let prevNode = prev[j]
    let nextNode = next[j]
    // 2. 从 j 开始匹配相同的元素,如果 prev[j].key == next[j].key,更新后 j++ 如果 j > prevEnd || j > nextEnd 提前跳出
    while(prevNode && nextNode && prevNode.key === nextNode.key){
        patch(prevNode, nextNode)
        j++
        if(j > prevEnd || j > nextEnd) break
        prevNode = prev[j]
        nextNode = next[j]
    }
    // 3. 匹配完前面的相同元素后 j 停在第一个不同点
    prevNode = prev[prevEnd]
    nextNode = next[nextEnd]
    // 4. 从 prevEnd 和 nextEnd 开始从后面查找相同的后缀,更新后 prevEnd-- nextEnd-- 如果 j > prevEnd || j > nextEnd提前跳出
    while(prevNode && nextNode && prevNode.key === nextNode.key){
        patch(prevNode, nextNode)
        prevEnd--
        nextEnd--
        if(j > prevEnd || j > nextEnd) break
        prevNode = prev[prevEnd]
        nextNode = next[nextEnd]
    }
    // console.log(j, prevEnd, nextEnd)
    // console.log(container, prev, next)
    // 5. 此时已经更新完相同的前缀和后缀,需要看 j 处于什么位置
    if(j > prevEnd && j <= nextEnd){
        // 6. 如果 j > prevEnd && j <= nextEnd ,则 next 在[ j , nextEnd]  新增元素,直接在next[nextEnd + 1] 前插入新增元素
        let ref = next[nextEnd + 1] || null
        while(j <= nextEnd){
            container = insertBefore(ref, next[j++], container)
        }
    }else if(j > nextEnd && j <= prevEnd){
        // 7. 如果 j > nextEnd && j  <= prevEnd ,则 prev 在 [j , prevEnd]  中元素被删除,直接删除多余元素
        while(j <= prevEnd){
            remove(prev[j++], container)
        }
    }else if(j <= nextEnd){
        // 8. 如果都不是上两种情况,则说明在 [j , prevEnd] 段存在乱序的节点,长度为 nextLeft = nextEnd - j + 1
        let nextLeft = nextEnd - j + 1
        // 9. 初始化一个辅助数组  source 长度为 nextLeft 默认值为 -1,patched = 0,move = false, pos = 0
        let source = new Array(nextLeft).fill(-1)
        let pos = 0
        let patched = 0
        let move = false
        let keyIndex = {}
        // 10. 遍历 next, 从 j 到 nextEnd,生成一个 key - i 的映射表 keyIndex
        for(let i = j; i <= nextEnd; i++) keyIndex[next[i].key] = i
        // 11. 遍历 prev ,从 j 到 prevEnd 
        for (let i = j; i <= prevEnd; i++) {
            let prevNode = prev[i]
            // 12. 如果 patched 大于 nextLeft,则说明相同元素从 prev 中取完,后面均为待删除的元素,直接删除
            if(patched < nextLeft){
                let k = keyIndex[prevNode.key]
                // 13. k = keyIndex[prev[i]],如果 k 不存在,证明该节点不存在于 next,直接删除
                if(k !== undefined){
                    // 14. next[k].key == prev[i].key,则更新该节点,patched++,source[k - j] = i,如果 k < pos,则prev[i] 是需要移动的 move = true,否则 pos = k
                    patch(prevNode, next[k])
                    source[k - j] = i
                    patched++
                    if(k < pos){
                        move = true
                    }else{
                        pos = k
                    }
                }else{
                    remove(prevNode, container)
                }
            }else{
                remove(prevNode, container)
            }
        }
        // console.log(source)
        // 15. 处理完成后判断 move 是否为真
        if(move){
            // 17. 如果为真,则在这段范围内发生乱序 / 新增的情况
            // 18. 求出最长上升子序列 seq = lis(source) j = seq.length - 1
            let seq = lis(source)
            let k = seq.length - 1
            for(let i = nextLeft - 1; i >= 0; i--){
                if(source[i] === -1 || i !== seq[k]){
                    // 19. i = nextLeft - 1 倒序遍历,如果 source[i] == -1 时,pos = j + i,在 next[pos + 1]前增加节点next[pos]
                    // 20. 如果 i == seq[j],则需要移动此节点,pos = j + i,将 prev 中的 next[pos]插入到 prev 中的 next[pos + 1] 之前
                    let pos = j + i
                    container = insertBefore(next[pos + 1] || null, next[pos], container)
                }else{
                    // 21. 其他情况,j-- 
                    k--
                }
                // console.log(container)
            }
        }else{
            // 16. 如果不为真,i = nextLeft - 1 倒序遍历,如果 source[i] == -1 时,pos = j + i,在 next[pos + 1]前增加节点 next[pos]
            for(let i = nextLeft - 1; i >= 0; i--){
                if(source[i] === -1){
                    let pos = j + i
                    container = insertBefore(next[pos + 1] || null, next[pos], container)
                }
            }
        }
    }
    return container
}
function lis(arr) {
    const p = arr.slice()
    const result = [0]
    let i
    let j
    let u
    let v
    let c
    const len = arr.length
    for (i = 0; i < len; i++) {
        const arrI = arr[i]
        if (arrI !== 0) {
            j = result[result.length - 1]
            if (arr[j] < arrI) {
                p[i] = j
                result.push(i)
                continue
            }
            u = 0
            v = result.length - 1
            while (u < v) {
                c = ((u + v) / 2) | 0
                if (arr[result[c]] < arrI) {
                    u = c + 1
                } else {
                    v = c
                }
            }
            if (arrI < arr[result[u]]) {
            if (u > 0) {
                p[i] = result[u - 1]
            }
                result[u] = i
            }
        }
    }
    u = result.length
    v = result[u - 1]
    while (u-- > 0) {
        result[u] = v
        v = p[v]
    }
    return result
}
复制代码
文章分类
前端
文章标签