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 运算符调用函数的具体过程:
_proto_
成员指向构造函数的prototype
this
,用构造函数内部的方法修改空对象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
是用来判断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)
}
}
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 代表数组要新插入的项目
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
})();
var b = 10;
(function b() {
var b = 20; // IIFE内部变量
console.log(b); // 20
console.log(window.b); // 10
})();
考察隐式转换
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++
}
}
// 第一种方式
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()会将参数对象作为一个新创建的空对象的原型, 并返回这个空对象
// 简略版
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() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。
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
}
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()
}
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))
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
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))
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: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'
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 核心原理: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)
}
})
}
原型链存在的问题:
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)