整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript 中问号的三种用法 ??和?.以及?: 的您知道吗?




近看了一些关于JavaScript的测试脚本,觉得JS 中问号的用法还是蛮有意思的,于是做了一下总结,在这里分享给大家!JS中的问号大概有三种用法,分别是:空值合并操作符、可选链操作符和三目运算。

问号问号(??)

空值合并操作符??是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。

例如

console.log(null ?? "xx")
输出 xx
console.log(1 ?? "xx")
输出 1

问号点 (?.)

可选链操作符(?.)可选链操作符允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。 使用它的好处是引用为null 或者 undefined的情况下不会引起错误。

语法:obj?.prop obj?.[expr] arr?.[index] func?.(args)

例如

var obj={a:{b:1}}
console.log(obj?.a?.b)
输出1
console.log(obj?.a?.c)
输出 undefined

问号冒号(?: )

这是三目运算,具体表达式是(condition ? exprIfTrue : exprIfFalse)

该表达式的含义是 条件condition是真,则执行exprIfTrue ,否则执行exprIfFalse

举个例子大家就懂了

var n = 10;
console.log((n >= 11) ? "a" : "b");
输出b
当 var n = 12;
输出a

如果您还知道哪些JS 中关于问号的特殊用法欢迎留言讨论。如果文章帮到了您,劳烦点赞转发!

原理

事件高频触发后,n秒内函数只会执行一次,若n秒内事件再次触发,则重新计时,总之就是要等触发完事件 n 秒内不再触发事件,函数才执行

代码实现

function debounce(callback, wait) {
    let timer
    return function (...args) {
        clearTimeout(timer)
        timer = setTimeout(() => {
            callback.call(this,args)
        },wait)
    }
}

// 使用
document.body.addEventListener('mousemove',debounce((e)=>{
  console.log(this,e,'mousemove-debounce')
},1000))

节流

原理

如果事件持续触发,在指定时间内,只执行一次事件

代码实现

时间戳方式

// 时间戳方式
/**
使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),
如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
*/
function throttle(callback, wait) {
    let start = 0
    return function(...args) {
        const now = +new Date()
        if(now-start >= wait ) {
            callback.call(this,args)
            start = now
        }

    }
}
//使用
const cb = throttle(function(e){
    console.log(this)
},1000)
document.body.addEventListener('mousemove',()=>{
    cb.call({name:'张三'})
},1000)
// {name: '张三'}

定时器方式

// 定时器方式
/**
 * 
 * 当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,
 * 直到定时器执行,然后执行函数,清空定时器。
 */
function throttle(callback, wait) {
    let timer
    return function(...args) {
        if(!timer) {
            timer = setTimeout(()=>{
                timer = null
                callback.call(this,args)
            },wait)
        }
    }
}

const cb = throttle(function(e){
    console.log(this)
},1000)
document.body.addEventListener('mousemove',()=>{
    cb.call({name:'张三'})
},1000)
// {name: '张三'}

模拟new运算符

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

原理

  • 新建一个空对象
  • 链接到原型
  • 绑定this
  • 返回该对象

代码实现

function myNew() {
// 1.新建一个空对象
let obj = {}
// 2.获得构造函数
let con = [].shift.call(arguments)
// 3.链接原型,实例的 __proto__ 属性指向构造函数的 prototype
obj.__proto__ = con.prototype
// 4.绑定this,执行构造函数
let res = con.apply(obj, arguments)
// 5.返回新对象
return typeof res === 'object' ? res : obj
}

function Person(name) {
    this.name = name
}
let person = myNew(Person,'nanjiu')
console.log(person) //{name: "nanjiu"}
console.log(typeof person === 'object') //true
console.log(person instanceof Person) // true

模拟instanceof

instanceof 用于检测构造函数的prototype是否在实例的原型链上,需要注意的是instanceof只能用来检测引用数据类型,对于基本数据检测都会返回false

原理

通过循环检测实例的__proto__属性是否与构造函数的prototype属性相等

代码实现

/**
 * instanceof 用于检测构造函数的prototype是否在实例的原型链上
 */
function myInstanceof(left, right) {
    // 先排除基本数据类型
    if(typeof left !== 'object' || left === null) return false
    let proto = left.__proto__
    while(proto) {
        if(proto === right.prototype) return true
        proto = proto.__proto__
    }
    return false
}

function Person() {}
let person = new Person()
console.log(myInstanceof(person,Person)) // true

模拟Function.prototype.apply()

apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

Function.prototype.myApply = function(context) {
    var context = context || window // 获取需要绑定的this
    context.fn = this // 获取需要改变this的函数
    const arg = arguments[1] // 获取传递给函数的参数

    if(!(arg instanceof Array)) {
        throw Error('参数需要是一个数组')
    }
    const res = context.fn(...arg) // 执行函数
    delete context.fn // 删除该方法
    return res // 返回函数返回值
}
function say(a,b,c) {
    console.log(this.name,a,b,c)
}
say.myApply({name:'nanjiu'},[1,2,3]) //nanjiu 1 2 3
say.apply({name:'nanjiu'},[1,2,3]) //nanjiu 1 2 3

模拟Function.prototype.call()

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

Function.prototype.myCall = function(context) {
    var context = context || window // 获取需要改变的this
    context.fn = this // 获取需要改变this的函数
    const args = [...arguments].slice(1) // 获取参数列表
    const res = context.fn(...args) // 将参数传给函数并执行
    delete context.fn // 删除该方法
    return res // 返回函数返回值
}

function say(a,b,c) {
    console.log(this.name,a,b,c)
}
say.myCall({name:'nanjiu'},1,2,3) //nanjiu 1 2 3
say.call({name:'nanjiu'},1,2,3) //nanjiu 1 2 3

模拟Function.prototype.bind()

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

Function.prototype.myBind = function(context) {
    var context = context || window //获取需要改变的this
    context.fn = this  // 获取需要改变this的函数

    //获取函数参数
    const args = [...arguments].slice(1)
    // 与apply,call不同的是这里需要返回一个函数
    return () => {
        return context.fn.apply(context,[...args])
    }

}

function say(a,b,c) {
    console.log(this.name,a,b,c)
}
say.bind({name: 'nanjiu'},1,2,3)() //nanjiu 1 2 3
say.myBind({name: 'nanjiu'},1,2,3)() //nanjiu 1 2 3

模拟Array.prototype.forEach()

forEach() 方法对数组的每个元素执行一次给定的函数,无返回值。

语法

arr.forEach(callback(currentValue [, index [, array]])[, thisArg])

参数

  • callback
  • 为数组中每个元素执行的函数,该函数接收一至三个参数:currentValue数组中正在处理的当前元素。index 可选数组中正在处理的当前元素的索引。array 可选forEach() 方法正在操作的数组。
  • thisArg 可选
  • 可选参数。当执行回调函数 callback 时,用作 this 的值。

代码实现

Array.prototype.myForEach = function(callback, context) {
    const arr = this // 获取调用的数组
    const len = arr.length || 0

    let index = 0  // 数组下标
    while(index < len) {
        callback.call(context ,arr[index], index)
        index++
    }
}

let arr = [1,2,3]
arr.forEach((item,index) => {
    console.log(`key: ${index} - item: ${item}`)
})
console.log('----------')
arr.myForEach((item,index) => {
    console.log(`key: ${index} - item: ${item}`)
})
/**
 * key: 0 - item: 1
key: 1 - item: 2
key: 2 - item: 3
----------
key: 0 - item: 1
key: 1 - item: 2
key: 2 - item: 3
 */

模拟Array.prototype.map()

map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。

语法

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
 // Return element for new_array 
}[, thisArg])

参数

callback

生成新数组元素的函数,使用三个参数:

  • currentValue
  • 数组中正在处理的当前元素。
  • index可选
  • 数组中正在处理的当前元素的索引。
  • array可选
  • map 方法调用的数组。

thisArg可选

执行 callback 函数时值被用作this

代码实现

/**
 * map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
 */
Array.prototype.myMap = function(callback, context) {
    const arr = this,res = []
    const len = arr.length || 0
    let index = 0
    while(index < len) {
        res.push(callback.call(context, arr[index], index))
        index ++
    }
    return res  // 与forEach不同的是map有返回值
}
const arr = [1,2,3]
let res1 = arr.map((item,index) => {
    return `k:${index}-v:${item}`
})
let res2 = arr.myMap((item,index) => {
    return `k:${index}-v:${item}`
})
console.log(res1) // [ 'k:0-v:1', 'k:1-v:2', 'k:2-v:3' ]
console.log(res2) // [ 'k:0-v:1', 'k:1-v:2', 'k:2-v:3' ]

模拟Array.prototype.filter()

filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

语法

var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])

参数

callback

用来测试数组的每个元素的函数。返回 true 表示该元素通过测试,保留该元素,false 则不保留。它接受以下三个参数:

  • element
  • 数组中当前正在处理的元素。
  • index可选
  • 正在处理的元素在数组中的索引。
  • array可选
  • 调用了 filter 的数组本身。

thisArg可选

执行 callback 时,用于 this 的值。

代码实现

/**
 * `filter()` 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。 
 */

Array.prototype.myFilter = function(callback, context) {
    const arr = this,res = []
    const len = arr.length
    let index = 0
    while(index < len) {
        if(callback.call(context,arr[index],index)) {
            res.push(arr[index])
        }
        index ++   
    }
    return res
}

const arr = [1,2,3]
let res1 = arr.filter((item,index) => {
    return item<3
})
let res2 = arr.myFilter((item,index) => {
    return item<3
})

console.log(res1) // [ 1, 2 ]
console.log(res2) // [ 1, 2 ]

函数柯里化

柯里化,英语:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

先来理解一下什么是函数柯里化,上面文绉绉的内容可能不是那么容易理解,我们还是直接上代码来理解吧

// 假如有这样一个函数
function add (a,b,c) {
    console.log(a+b+c)
}
add(1,2,3) //6
/**
 * 我们希望可以通过add(1,2)(3)或add(1)(2)(3)或add(1)(2,3)这样调用也能够得倒正确的计算结果
 这就是函数柯里化的简单应用
 */

代码实现

function curry(fn, curArgs) {
    const len = fn.length  // 需要柯里化函数的参数个数
    curArgs = curArgs || []

    return function() {
        let args = [].slice.call(arguments) // 获取参数
        args = curArgs.concat(args) //拼接参数
        // 基本思想就是当拼接完的参数个数与原函数参数个数相等才执行这个函数,否则就递归拼接参数
        if(args.length < len) {
            return curry(fn, args)
        }else{
            return fn.apply(this, args)
        }
    }
}

let fn = curry(function(a,b,c){
    console.log([a,b,c])
})
fn(1,2,3) // [ 1, 2, 3 ]
fn(1,2)(3) // [ 1, 2, 3 ]
fn(1)(2,3) // [ 1, 2, 3 ]
fn(1)(2)(3) // [ 1, 2, 3 ]

类数组转数组

类数组是具有length属性,但不具有数组原型上的方法。常见的类数组有arguments、DOM操作方法返回的结果。

function translateArray() {
    //方法一:Array.from
    const res1 = Array.from(arguments)
    console.log(res1 instanceof Array, res1) // true [ 1, 2, 3 ]

    // 方法二:Array.prototype.slice.call
    const res2 = Array.prototype.slice.call(arguments)
    console.log(res2 instanceof Array, res2) // true [ 1, 2, 3 ]

    // 方法三:concate
    const res3 = [].concat.apply([],arguments)
    console.log(res3 instanceof Array, res3) // true [ 1, 2, 3 ]

    // 方法四:扩展运算符
    const res4 = [...arguments]
    console.log(res4 instanceof Array, res4) // true [ 1, 2, 3 ]
}

translateArray(1,2,3)

实现深拷贝

在拷贝的时候判断一下属性值的类型,如果是对象,递归调用深拷贝函数

/**
 * 在拷贝的时候判断一下属性值的类型,如果是对象,递归调用深拷贝函数
 */

function deepClone(obj, cache=new Map()) {
    // 基本数据类型直接返回
    if(typeof obj !== 'object' || obj === null) return obj
    // 防止循环引用
    const cacheTarget = cache.get(obj)
    // 已经存在就直接返回
    if(cacheTarget) return cacheTarget

    let newObj = obj instanceof Array ? [] : {} // 新建一个对象

    cache.set(obj, newObj)
    // 遍历原对象
    for(let key in obj) {
        if(obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]
        }
    }
    return newObj
}
const obj = {
    name: '张三'
}
const obj1 = obj
const obj2 = deepClone(obj)
console.log(obj1===obj) //true
console.log(obj2===obj) //false

继承的实现

原型链继承

原型链继承实现的原理就是将构造函数的原型设置为另一个构造函数的实例对象,这样就可以继承另一个原型对象的所有属性和方法,可以继续往上,最终形成原型链。

function Parent1(name, age) {
    this.name = name,
    this.age = age
}
Parent1.prototype.say = function() {
    console.log(this.name)
}
function Child1(name) {
    this.name = name
}
Child1.prototype = new Parent1()
Child1.prototype.constructor = Child1
let child1 = new Child1('诚实',18)
console.log(child1) //Child1 {name: '诚实'}
child1.say() // 诚实

缺点:

  • 当实现继承后,另一个原型的实例属性,变成了现在这个原型的原型属性,然后该原型的引用类型属性会被所有的实例共享,这样继承原型引用类型属性的实例之间不再具有自己的独特性了。
  • 在创建子类型的实例时,没有办法在不影响所有对象实例的情况下给超类型的构造函数中传递参数。

构造函数继承

为了解决原型中包含引用类型值的问题,开始使用借用构造函数,也叫伪造对象或经典继承

构造函数继承实现的原理就是在子类中调用父类构造函数来实现继承

function Parent2(age) {
    this.age = age
    this.say = function() {
        console.log(this.name)
    }
}
function Child2(name,age,gender) {
    this.name = name
    Parent2.call(this, age)
    this.gender = gender
}
let child2 = new Child2('张三', 18, 'boy')
console.log(child2) //Child2 {name: '张三', age: 18, gender: 'boy'}
child2.say() // 张三

优点: 可以传递参数以及避免了引用类型的属性被所有实例共享

缺点: 所有方法都在构造函数内,每次创建对象都会创建对应的方法,大大浪费内存

组合继承*

也叫伪经典继承,将原型链和借用构造函数的技术组合到一块。使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。

function Parent3(age) {
    this.age = age
}
Parent3.prototype.say = function() {
    console.log(this.name)
}
function Child3(name,age,gender) {
    this.name = name
    Parent3.call(this, age)
    this.gender = gender
}

Child3.prototype = new Parent3
Child3.prototype.constructor = Child3
let child3 = new Child3('张三', 18, 'boy')
console.log(child3) //Child3 {name: '张三', age: 18, gender: 'boy'}
child2.say() // 张三
  • 将Child3的原型指定为Parent3的一个实例,大致步骤和原型链继承类似,只是多了在Child3中借调Parent3的过程。
  • 实例属性定义在构造函数中,而方法则定义在构造函数的新原型中,同时将新原型的constructor指向构造函数。
  • 可以通过instanceofisPrototypeOf()来识别基于组合继承创建的对象。
  • 避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JS中最常用的继承模式。

原型式继承

借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

function object(obj) {
    function F(){}
    F.prototype = obj
    return new F()
}
let parent4 = {
    age: 18,
    name: '张三',
    say() {
        console.log(this.name)
    }
}
let child4 = object(parent4)
console.log(child4) 
/**{[[Prototype]]: Object
    age: 18
    name: "张三"
    say: ƒ say()
    [[Prototype]]: Object
    }
*/
child4.say() // 张三

在 object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制

  • 这种原型式继承,要求必须要有一个对象可以作为另一个对象的基础
  • 用这种方式创建的对象相当于是传入参数对象的副本

它其实就是ES5 Object.create的模拟实现,将传入的对象作为创建对象的原型

在只想让一个对象与另一个对象保持类似的情况下,原型继承是完全可以胜任的。原型模式下的缺点:引用类型属性的共享问题。

寄生继承

寄生式继承与原型式继承紧密相关,与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象。

function createAnother(original) {
  var clone = object(original) //通过调用函数创建一个新对象
  clone.say = function(){        // 以某种方式来增强这个对象
    console.log('nanjiu')
  };
  return clone            // 返回这个对象
}

缺点: 跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

寄生组合式继承*

组合继承是 JavaScript 最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。没错,子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function prototype(child, parent) {
    var prototype = object(parent.prototype);
    prototype.constructor = child;
    child.prototype = prototype;
}


function Parent6(age) {
    this.age = age
}
Parent6.prototype.say = function() {
    console.log(this.name)
}
function Child6(name, gender) {
    this.name = name
    this.gender = gender
}

// 使用
prototype(Child6, Parent6);
let child6 = new Child6('nanjiu', 'boy')

console.log(child6) // Child6 {name: 'nanjiu', gender: 'boy'}
child6.say() // nanjiu

总结

JavaScript 主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。

  • 原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用。
  • 解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就可以做到每个实例都具有自己的属性,同时还能往超类型构造函数中传递参数,但是没有函数复用。
  • 使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。
  • 此外,还存在下列可供选择的继承模式。
  • 原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。
  • 寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问题,可以将这个模式与组合继承一起使用。
  • 寄生组合式继承,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。

实现AJAX

步骤:

  • 创建XMLHttpRequest对象
  • 打开链接 (指定请求类型,需要请求数据在服务器的地址,是否异步i请求)
  • 向服务器发送请求(get类型直接发送请求,post类型需要设置请求头)
  • 接收服务器的响应数据(需根据XMLHttpRequest的readyState属性判定调用哪个回调函数)
function ajax(url, method, data=null) {
    const xhr = XMLHttpRequest() // 咱们这里就不管IE低版本了
    // open()方法,它接受3个参数:要发送的请求的类型,请求的url和是否异步发送请求的布尔值。
    xhr.open(method, url ,false) // 开启一个请求,当前还未发送

    xhr.onreadyStatechange = function() {
        if(xhr.readyState == 4) {
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                alert(xhr.responseText);
            } else {
                console.log("Request was unsuccessful: " + xhr.status);
            }
        }
    }
    if(method === 'post') {
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    }
    xhr.send(data) // get请求,data应为null,参数拼接在URL上
}

多维数组扁平化

function flat(arr) {
    const res = []
    // 递归实现
    const stack = [...arr] // 复制一份
    while(stack.length) {
        //取出复制栈内第一个元素
        const val = stack.shift()
        if(Array.isArray(val)) {
            // 如果是数组,就展开推入栈的最后
            stack.push(...val)
        }else{
            // 否则就推入res返回值
            res.push(val)
        }
    }
    return res
}
const arr = [1,[2],[3,4,[5,6,[7,[8]]]]]
console.log(flat(arr)) //[1, 2, 3, 4, 5, 6, 7, 8]

当然你也可以用数组自带的方法flat,将展开层数指定为Infinity无穷大,看看面试官搭不搭理你

const arr = [1,[2],[3,4,[5,6,[7,[8]]]]]
console.log(arr.flat(Infinity)) //[1, 2, 3, 4, 5, 6, 7, 8]

setTimeout 模拟 setInterval

思路就是递归调用setTimeout

function mySetInterval(callback, delay) {
    let timer = null
    let interval = () => {
        timer = setTimeout(()=>{
            callback()
            interval() // 递归
        }, delay)
    }
    interval() // 先执行一次
    return {
        id: timer,
        clear: () => {
            clearTimeout(timer)
        }
    }
}

let time = mySetInterval(()=>{
    console.log(1)
},1000)
setTimeout(()=>{
    time.clear()
},2000)

setInterval 模拟 setTimeout

思路就是setInterval执行一次后将setInterval清除即可

function mySetTimeout(callback, delay) {
    let timer = null
    timer = setInterval(()=>{
        callback()
        clearInterval(timer)
    },delay)
}

mySetTimeout(()=>{
    console.log(1)
},1000)

sleep

实现一个函数,n秒后执行指定函数

function sleep(func, delay) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(func())
        }, delay)
    })
}

function say(name) {
    console.log(name)
}
async function go() {
    await sleep(()=>say('nanjiu'),1000) //过一秒打印nanjiu
    await sleep(()=>say('前端张三'),2000) // 再过两秒打印前端张三
}
go()

数组去重的多种实现方式

使用Set

let arr = [1,2,3,2,4,5,3,6,2]
function arrayToHeavy1(arr) {
    return [...new Set(arr)]
}
console.log(arrayToHeavy1(arr)) //[ 1, 2, 3, 4, 5, 6 ]

使用indexOf

function arrayToHeavy2(arr) {
    let newArr = []
    for(let i=0; i<arr.length; i++) {
        if(newArr.indexOf(arr[i]) == -1){
            newArr.push(arr[i])
        }
    }
    return newArr
}
console.log(arrayToHeavy2(arr)) //[ 1, 2, 3, 4, 5, 6 ]

使用filter

function arrayToHeavy3(arr) {
    return arr.filter((item, index) => {
        return arr.indexOf(item) == index
    })
}

console.log(arrayToHeavy3(arr)) //[ 1, 2, 3, 4, 5, 6 ]

使用Map

function arrayToHeavy4(arr) {
    let map = new Map()
    for(let i=0; i<arr.length; i++) {
        if(!map.has(arr[i])){
            map.set(arr[i], 1)
        }
    }
    return [...map.keys()]
}
console.log(arrayToHeavy4(arr)) //[ 1, 2, 3, 4, 5, 6 ]

使用include

function arrayToHeavy5(arr) {
    let res = []
    for(let i=0; i<arr.length; i++) {
        if(!res.includes(arr[i])) {
            res.push(arr[i])
        }
    }
    return res
}
console.log(arrayToHeavy5(arr)) //[ 1, 2, 3, 4, 5, 6 ]

解析URL参数

根据key获取URL上的参数值

function queryData(key) {
    let url = window.location.href,obj = {}
    let str = url.split('?')[1] // 先拿到问号后面的所有参数
    let arr = str.split('&') // 分割参数
    for(let i=0; i< arr.length; i++) {
        let kv = arr[i].split('=')
        obj[kv[0]] = decodeURIComponent(kv[1])
    }
    console.log(url,obj)
    // {a: '1', b: '2', c: '3', name: '张三'}
    return obj[key]

}
//http://127.0.0.1:5500/src/js/2022/%E6%89%8B%E5%86%99/index.html?a=1&b=2&c=3&name=%E5%8D%97%E7%8E%96
console.log(queryData('name')) // 张三

斐波那契数列

F(n) = F(n - 1) + F(n - 2),其中 n > 1

暴力递归版本

function fib(n) {
    if(n == 0) return 0
    if(n == 1 || n == 2) return 1
    return fib(n-1) + fib(n-2)
}

// console.log(fib(4)) //F(4)=F(3)+F(2)=F(2)+F(1)+F(2)=1+1+1=3
let t = +new Date()
console.log(fib(40)) //102334155
console.log(+new Date()-t) //783ms

优化版本

function fib2(n) {
    if(fib2[n] !== undefined) return fib2[n]
    if(n == 0) return 0
    if(n == 1 || n == 2) return 1

    const res = fib2(n-1) + fib2(n-2)
    fib2[n] = res
    return res
}
let t1 = +new Date()
console.log(fib2(40)) //102334155
console.log(+new Date()-t1)  //5ms

发布订阅

用过Vue的eventBus的同学应该很熟悉,$on订阅事件,$emit发布事件

欢web前端网页开发和python开发的、请加下企鹅群:526929231 内有大量案例和学习教程,对python、和web感兴趣的朋友可以加下哦

form表单域

所有的控件(表单元素都写在form表单标签中)

<form>
 使用form标签建立表单域,当提交数据的时候会收集表单域里面的数据然后发送给服务器</form>

input表单控件元素

赋予不同的type值可实现不同的表单控件

type类型描述
text文本输入框 maxlength最大长度、onlyready只读、 disabled禁止、 placeholder
password密码遮掩框
radio单选按钮,checked默认选择
checkbox多选框
submit收集表单域的name数据,然后提交到服务器上

下拉列表框

<select>
 <option value="music">听音乐</option>
 <option value="running">跑步</option>
 <option value="study">学习</option>
 <option value="coffee">找小姐姐一起喝咖啡</option></select><!-- selected="selected"默认选中 --><!-- size="2" 现实两行下拉项 --><!-- disabled 禁止选择 -->

selected默认选择一项

多行文本输入框(文本域) textarea

<textare cols="30" rows="10"></textarea>

clos显示多少列,rows现实多少行

按钮

单独使用没有效果,一般配合js点击按钮的时候执行什么操作

<input type="button" value="自定义按钮标题" />

重置按钮

回到表单初识状态

<input type="reset" value="重置表单" />

隐藏域

目的在于收集或发送信息 页面上面没有任何效果 CSRF跨域攻击在此作用

<input type="hidden" value="ABCD1234" />

label元素

为input元素定义一个标记,label元素不会向用户呈现任何特殊效果。不过,它为鼠标用户改进了可用性。如果你在label元素内点击文本,就会触发此控件。就是说,当用户选择该标签是,浏览器就会自动將焦点转到和标签相关的表单控件上

<input id="man" type="radio" /><label for="man">男</label>

form表单属性

属性描述
action指定提交到哪个url上
method提交方式,常用的GET / POST

提交方式

MethodDescription
GETURL地址栏上做拼接问号再加参数
POST隐式提交方式,看不到,可以抓包

格式化文本

  • pre可定义预格式化的文本。

  • pre元素中的文本通常会保留空格和换行符。

    主要用于在网页上显示代码,比如在网页当中显示html模板