javaScript 手写

数组扁平化及去重升序排列

const arr = [1,5,6,[3,4,5,1],[3,[9,17,14],[6,8,[3,12,11]]]]
//使用 Infinity,可展开任意深度的嵌套数组
console.log([...new Set(arr.flat(Infinity))].sort((a, b) => a - b))

手写一个

Array.prototype.flat = function (){
	return [].concat(...this.map(item => Array.isArray(item) ? item.flat() : [item]))
}

Array.prototype.unique = function (){
	return [...new Set(this)]
}

const sort = (a, b) => a - b
const arr = [1,5,6,[3,4,5,1],[3,[9,17,14],[6,8,[3,12,11]]]]
console.log(arr.flat().unique().sort(sort ))

// 用reduce 实现
function fn(arr){
	return arr.reduce((prev, next) => {
		return prev.concat(Array.isArray(next) ? fn(next) : next)
	},[])
}
let newArr = [...new Set(fn(arr))].sort(sort)
console.log(newArr )

new 运算符

new 运算符调用函数的具体过程:

  1. 创建一个空对象,让空对象的_proto_成员指向构造函数的prototype
  2. 把上面创建的空对象赋值构造函数内部的this,用构造函数内部的方法修改空对象
  3. 如果构造函数中没有返回其他对象,那么返回 this,即创建的这个新对象;否则返回构造函数返回的对象

以下两种方式同理:

function new(fn, ...args) {
	const obj = Object.create(fn.prototype)
	const result = fn.apply(obj, args)
	if(result && (typeof result === 'object' || typeof restlt === 'function')) {
		return result 
	}
	return obj
}
function new(fn, ...args){
	if(typeof fn !== 'function') {
		throw 'new function the first param must be a function'
	}

	var obj = Object.create(fn.prototype)  //创建一个继承自fn.prototype的新对象
	var result = fn.apply(obj, args)  //将构造函数fn的this绑定到obj 中
	var isObject = typeof result === 'object' && result !== null
	var isFunction = typeof result === 'function' 
	if( isObject || isFunction) {
		return result
	}
	return obj
	
}

instanceof 的实现

instanceof 是用来判断A是否为B的实例,表达式为:A instanceof B,如果A是B的实例,则返回true,否则返回false
instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
不能检测基本数据类型,在原型链上的结果未必准确,不能检测null,undefined
实现:遍历左边变量的原型链,直到找到右边变量的 prototype,如果没有找到,返回 false

function myInstanceof(left, right){
	let proto = left._proto_
	let prototype = right._prototype
	while(true){
		if(!proto) return false
		if(proto === prototype) return true
		proto = Object.getPrototypeOf(proto)
	}
}

请把俩个数组 [‘A1’, ‘A2’, ‘B1’, ‘B2’, ‘C1’, ‘C2’, ‘D1’, ‘D2’] 和 [‘A’, ‘B’, ‘C’, ‘D’],合并为 [“A1”, “A2”, “A”, “B1”, “B2”, “B”, “C1”, “C2”, “C”, “D1”, “D2”, “D”]。

function concatArr(arr1, arr2){
	const arr = [...arr1]
	let currentIndex = 0
	for(let i = 0; i < arr2.length; i++) {
		const RE = new RegExp(arr2[i])
		while(currentIndex < arr.length) {
			++currentIndex
			if(!RE.test(arr[currentIndex])){
				arr.splice(currentIndex, 0, arr2[i])
				break
			}
		}
	}
	return arr
}
const a1 = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2'] 
const a2 = ['A', 'B', 'C', 'D']
concatArr(a1, a2)

// 补充
arr.splice(index, howmany, item1,......, itemX)
index 代表索引,从第几个索引开始
howmany 代表要删除的项目数量
item 代表数组要新插入的项目

改造下面的代码,使之能够输出0-9

for (var i = 0; i< 10; i++){
	setTimeout(() => {
		console.log(i);
    }, 1000)
}

// 解法 1
for (let i = 0; i < 10; i++){
	setTimeout(()=>{
		console.log(i)
	}, 1000)
}

// 解法 2
for(var i = 0; i < 10; i++){
	((i) => {
		setTimeout(()=>{
			console.log(i)
		}, 1000)
	})(i)
}

下面的代码打印什么内容,为什么?

var b = 10;
(function b() {
   // 内部作用域,会先去查找是有已有变量b的声明,有就直接赋值20,确实有了呀。发现了具名函数 function b(){},拿此b做赋值;
   // IIFE的函数无法进行赋值(内部机制,类似const定义的常量),所以无效。
  // (这里说的“内部机制”,想搞清楚,需要去查阅一些资料,弄明白IIFE在JS引擎的工作方式,堆栈存储IIFE的方式等)
    b = 20;
    console.log(b); // [Function b]
    console.log(window.b); // 10,不是20
})();
简单改造,使之分别打印 10 和 20
var b = 10;
(function b() {
    var b = 20; // IIFE内部变量
    console.log(b); // 20
   	console.log(window.b); // 10 
})();

下面代码中 a 在什么情况下会打印 1?

考察隐式转换

var a = ?;
if(a == 1 && a == 2 && a == 3){
 	console.log(1);
}

//利用toString
let a = {
  i: 1,
  toString () {
    return a.i++
  }
}

// 利用valueOf
let a = {
  i: 1,
  valueOf () {
    return a.i++
  }
}

实现一个 sleep 函数

// 第一种方式
const sleep = time => {
	return new Promise(resolve => setTimeout(resolve, time))
}
sleep(1000).then(() => console.log(1))

// 第二种方式 Generator
function* sleepGenerator(time) {
	yield new Promise(resolve => setTimeout(resolve, time))
}

sleepGenerator(1000).next().value.then(() => console.log(1))

// 第三种方式 async/await
const sleep = time => {
	return new Promise(resolve => setTimeout(resolve, time))
}

const output = async time => {
	let out = async sleep(time)
	console.log(1)
	return out
}
output()

// 第四种方式  不用setTimeout
function sleep(delay) {
	const start= (new Date()).getTime()
	while((new Date()).getTime - start < delay ) {
		continue
	}
}

function fn(time) {
	console.log(1)
	sleep(time)
	console.log(2)
}

fn(1000)

object.create() 的实现

Object.create()会将参数对象作为一个新创建的空对象的原型, 并返回这个空对象

// 简略版
function create(obj) {
	// 新声明一个函数
	function C(){}
	// 将函数的原型指向 obj
	C.prototype = obj
	// 返回这个函数的实例化对象
	return new C()
}

// 升级版
object.create = function (proto, propertyObject = undefined) {
	if( typeof proto !== 'object' && typeof proto !== 'function') {
		throw new Error()
	} else if ( proto === null) {
		throw new Error()	
	}
	
	function F(){}
	F.prototype = proto
	const obj = new F()
	if (propertyObject != undefined) {
		// Object.defineProperties(obj, props) 
		// 方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
        Object.defineProperties(obj, propertyObject)
    }

	return obj 
}

Object.assign(target, …source) 的实现

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。

Object.assign = function(target, ...source) {
	if( target === null) {
		throw new Error('Cannot convert undefined or null to object')
	}
	
	let result = Object(target)
	source.forEach((obj) => {
		if( obj !== null) {
			for(let key in obj) {
				result[key] = obj[key]
			}
		}
	})
	
	return result
}

浅拷贝

function shallowCopy(obj) {
	if( typeof obj !== 'object' || obj === null) return obj
	
	let newObj = obj instanceof Array ? [] : {}

	for(let key in obj ) {
		if(newObj.hasOwnProperty(key)) {
			newObj[key] = obj[key]
		}
	}
	
	return newObj
}

深拷贝

  • 判断类型,正则和日期直接返回新对象
  • 空或者非对象类型,直接返回原值
  • 考虑循环引用,判断如果hash中含有直接返回hash中的值
  • 新建一个相应的new obj.constructor加入hash
  • 遍历对象递归
function deepclone(obj, hash = new WeakMap()){
	if(obj instanceof Date) return new Date(obj)
	if(obj instanceof RegExp) return new RegExp(obj)
	if(obj === null || typeof obj !== 'object') return obj

	// 循环引用的情况
	if(hash.has(obj)) {
		return hash.get(obj)
	}

	// new 一个相应的对象
    // obj为Array,相当于new Array()
    // obj为Object,相当于new Object()
    
	// let newObj = new obj.constructor()
	// 上下两步均可
	let newObj = Array.isArray(obj) ? [] : {}
	// 为循环引用的对象做标记
	hash.set(obj, true)
	for(let key in obj) {
		if(obj.hasOwnProperty(key)) {
			newObj[key] = deepclone(obj[key], hash)
		}
	}

	return newObj
	
}

函数柯里化

什么叫函数柯里化?其实就是将使用多个参数的函数转换成一系列使用一个参数的函数的技术。还不懂?来举个例子。

function add(a, b, c) {
    return a + b + c
}
add(1, 2, 3) // 6

let addCurry = curry(add)
addCurry(1)(2)(3)  // 6

现在就是要实现 curry 这个函数,使函数从一次调用传入多个参数变成多次调用每次传一个参数。

function curry(fn) {
	let judge = (...args) {
		if(args.length === fn.length) return fn(args)
		return (...arg) => judge(...args, ...arg)
	}
	return judge()
}

实现flatten 函数

function flat(arr) {
	// 判断是否是多维数组
	const isDeep = arr.some(item => item instanceof Array)
	// 不是多维数组直接返回
	if(!isDeep) {
		return arr
	}
	// apply方法会调用一个函数,apply方法的第一个参数会作为被调用函数的this值,apply方法的第二个参数(一个数组,或类数组的对象)会作为被调用对象的arguments值,也就是说该数组的各个元素将会依次成为被调用函数的各个参数
	// arr作为apply方法的第二个参数,本身是一个数组,数组中的每一个元素会被作为参数依次传入到concat中,
	/// 效果等同于[].concat(1, 2, 3, [4, [5, 6]], 7, 8, [9, 10])
	const newArr = Array.prototype.concat.apply([], arr)
	// 递归
	return flat(newArr)
}
const arr = [1, 2, 3, [4, [5, 6]], 7, 8, [9, 10]]
console.log(flat(arr))

ES6 实现flatten 函数

function flatten(arr) {
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}

使用迭代的方式实现 flatten 函数。

function wrap() {
	let newArr = []
	return	function flat(arr) {
		for(let item of arr) {
			if(item.constructor === Array) {
				newArr.concat(flat(item))
			} else {
				newArr.push(item)
			}
		}
		return newArr
	}
}

const arr2 = [0, 1, 2, [[[3, 4]]]];
console.log(wrap()(arr2)) // [0, 1, 2, 3, 4]

手写深度比较

function isEqual(obj1, obj2) {
    if(!isObject(obj1) || !isObject(obj2)) {
        // 值类型(注意,参与 equal 的一般不会是函数)
        return obj1 === obj2
    }

    // 若是全等,则直返回true
    if(obj1 === obj2) {
        return true
    }

    // 两个都是对象或数组,而且不相等
    // 先取出 obj1 和 obj2 的keys,比较个数
    const objKeys1 = Object.keys(obj1)
    const objKeys2 = Object.keys(obj2)
    if(objKeys1.length !== objKeys2.length) {
        return false
    }

    // 以 obj1 为基准,和 obj2 依次递归比较
    for(let key in obj1) {
        // 比较当前 key 的 val,递归
        const res = isEqual(obj1[key], obj2[key])
        if(!res) {
            return false
        }
    }

    return true
}

function isObject(obj) {
    if(typeof obj !== 'object' || obj === null) {
        return false
    }
    return true
}

const obj1 = {
    a: 100,
    b: 200,
    c: {
        x: 100,
        y: 200
    }
}

const obj2 = {
    a: 100,
    b: 200,
    c: {
        x: 100,
        y: 200
    }
}

console.log(isEqual(obj1, obj2))

实现 (5).add(3).minus(2) 功能

Number.prototype.add = function(n) {
	return this.valueOf() + n
}
Number.prototype.minus = function(n) {
	return this.valueOf() - n
}
console.log((5).add(3).minus(2) )

某公司 1 到 12 月份的销售额存在一个对象里面

如下:{1:222, 2:123, 5:888},请把数据处理为如下结构:[222, 123, null, null, 888, null, null, null, null, null, null, null]。

// Array.from 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
let obj = {1:222, 2:123, 5:888}
const result = Array.from({length: 12}).map((item, index) => obj[index + 1] || null)
console.log(result)

事件总线(发布订阅模式)

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]
			}
		}
	}
}

let eventBus = new EventEmitter()
let fn1 = function(name, age) {
	console.log(`${name} ${age}`)
}
let fn2 = function(name, age) {
	console.log(`hello, ${name} ${age}`)
}

eventBus.on('aaa', fn1)
eventBus.on('aaa', fn2)
eventBus.emit('aaa', false, '张三', 22)
// '张三 22'
// 'hello, 张三 22'

解析 url 参数为对象

function parseParam(url) {
	const paramsStr = /.+\?(.+)$/.exec(url)[1]  // 将 ? 后面的字符串取出来
	const paramsArr = paramsStr.split('&')  // 将字符串以 & 分割存到数组中
	let paramsObj = {}
	// 将 params 存到对象中
	paramsArr.forEach(item => {
		if(/=/.test(item)) {  // 处理有 value 的参数
			let [key, val] = item.split('=')  // 分割 key 和 value
			val = decodeURIComponent(val)  // 解码
			val = /^\d+$/.test(val) ? parseFloat(val) : val  // 判断是否转为数字
			
			if(paramsObj.hasOwnProperty(key)) {  // 如果对象有 key,则添加一个值
				paramsObj[key] = [].concat(paramsObj[key], val)
			} else {  // 如果对象没有这个 key,创建 key 并设置值
				paramsObj[key] = val
			}
		} else {  // 处理没有 value 的参数
			paramsObj[key] = true
		}
	})

	return paramsObj
}

图片懒加载

let imgList = [...document.querySelectorAll('img')]
let length = imgList.length

const imgLazyLoad = () => {
	let count = 0
	return (function() {
		let deleteIndexList = []
		imgList.forEach((img, index) => {
			let rect = img.getBoundingClientRect()
			if(rect.top < Window.innerHeight) {
				img.src = img.dataset.src
				deleteIndexList.push(index)
				count ++
				if(count === length) {
					document.removeEventListener('scroll', imgLazyLoad)	
				}
			}
		})
		imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
	})()
}

document.addEventListener('scroll', imgLazyLoad)

观察者模式

class Subject {
	constructor(name){
		this.name = name
		this.observers = []
		this.state = 'xxxx'
	}
	// 被观察者要提供一个接受观察者的方法
	attach(observer) {
		this.observers.push(observer)	
	}
	
	// 改变被观察者的状态
	setState(newState) {
		this.state = newState
		this.observers.forEach( o => {
			o.update(newState)	
		})
	}
}

class Observer {
	constructor(name) {
		this.name = name
	}
	update(newState) {
		console.log(`${this.name}say:${newState}`)
	}
}

// 被观察者 灯
let sub = new Subject('灯')

// 观察者
let mm = new Observer('小明')
let jj = new Observer('小金')

// 订阅 观察者
sub.attach(mm)
sub.attach(jj)

sub.setState('灯亮了电来了')

JSONP

JSONP 核心原理:script 标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于 GET 请求。

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 scriptSrc = document.createElement('script')
		scriptSrc.src = generateUrl()
		document.appendChild(scriptSrc)
		window[callbackName] = data => {
			resolve(data)
			document.reoveChild(scriptEle)	
		}
	})
}

原型链继承

原型链存在的问题:

  • 问题1:原型中包含的引用类型属性将被所有实例共享
  • 问题2:子类在实例化的时候不能给父类构造函数传参
function Animal() {
	this.colors = ['black', 'white']
}

Animal.prototype.getColor = function() {
	return this.colors
}

function Dog() {}
	Dog.prototype = new Animal()
}

let dog1 = new Dog()
dog1.colors.push('green')
let dog2 = new Dog()
console.log(dog2.getColor)

借用构造函数实现继承

借用构造函数实现继承解决了原型链继承的 2 个问题:引用类型共享问题以及传参问题。但是由于方法必须定义在构造函数中,所以会导致每次创建子类实例都会创建一遍方法。

function Animal(name) {
	this.name = name
	this.getName = function() {
		return this.name
	}
}

function Dog(name) {
	Animal.call(this, name)
}
Dog.prototype = new Animal()

组合继承

优点:1、可以继承父类原型上的属性,可以传参,可复用。
   2、每个新实例引入的构造函数属性是私有的。

缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数

function Animal(name) {
	this.name = name
	this.colors = ['black', 'white']
}
Animal.prototype.getName = function() {
	return this.name
}
function Dog(name, age) {
	Animal.call(this, name)
	this.age = age
}
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog

let dog1 = new Dog('奶昔', 2)
dog1.colors.push('brown')
let dog2 = new Dog('哈赤', 1)
console.log(dog2)  // { name: '哈赤', colors: ['black', 'white'], age: 1}

寄生组合继承

function Animal(name) {
	this.name = name
	this.colors = ['black', 'white']
}
Animal.prototype.getName = function() {
	return this.name
}
function Dog(name, age) {
	Animal.call(this, name)
	this.age = age
}
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog

let dog1 = new Dog('奶昔', 2)
dog1.colors.push('brown')
let dog2 = new Dog('哈赤', 1)
console.log(dog2)  // { name: '哈赤', colors: ['black', 'white'], age: 1}

实现防抖函数

连续触发在最后一次执行方法,场景:输入框匹配

let debounce = (fn, time = 1000) {
	let timer = null
	return (...args) => {
		clearTimeout(timer)
		timer = setTimeout(() => {
			fn(...args)
		}, time)
	}
}

节流函数

在一定时间内只触发一次,场景:长列表滚动节流

let throttle = (fn, time = 1000) => {
	let flag = true
	return (...args) => {
		if(flag){
			flag = false
			setTimeout(() => {
				fn(...args)
				flag = true
			}, time)
		}
	}
}

生成随机数的各种方法

function getRandom(min, max) {
	return Math.floor(Math.random() * (max - min) + min)
}

实现数组的随机排序

function randomSort(a,b) {
	return Math.random() > 0.5 ? -1 : 1
}
let arr = [2,3,45,16,54,89]
arr.sort(randomSort)

转化为驼峰命名

const s1 = "get-element-by-id"
// 转化为 getElementById

const fn = str => {
	return str.replace(/-\w/g, word => {
		return word.slice(1).toUpperCase()
	})
}
fn(s1)

你可能感兴趣的:(前端,javascript)