近看了一些关于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 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
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 用于检测构造函数的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
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
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
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
forEach() 方法对数组的每个元素执行一次给定的函数,无返回值。
arr.forEach(callback(currentValue [, index [, array]])[, thisArg])
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
*/
map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])
callback
生成新数组元素的函数,使用三个参数:
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' ]
filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])
callback
用来测试数组的每个元素的函数。返回 true 表示该元素通过测试,保留该元素,false 则不保留。它接受以下三个参数:
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() // 张三
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
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 主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。
步骤:
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
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执行一次后将setInterval清除即可
function mySetTimeout(callback, delay) {
let timer = null
timer = setInterval(()=>{
callback()
clearInterval(timer)
},delay)
}
mySetTimeout(()=>{
console.log(1)
},1000)
实现一个函数,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()
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 ]
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 ]
function arrayToHeavy3(arr) {
return arr.filter((item, index) => {
return arr.indexOf(item) == index
})
}
console.log(arrayToHeavy3(arr)) //[ 1, 2, 3, 4, 5, 6 ]
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 ]
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 ]
根据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>
赋予不同的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默认选择一项
<textare cols="30" rows="10"></textarea>
clos显示多少列,rows现实多少行
单独使用没有效果,一般配合js点击按钮的时候执行什么操作
<input type="button" value="自定义按钮标题" />
回到表单初识状态
<input type="reset" value="重置表单" />
目的在于收集或发送信息 页面上面没有任何效果 CSRF跨域攻击在此作用
<input type="hidden" value="ABCD1234" />
为input元素定义一个标记,label元素不会向用户呈现任何特殊效果。不过,它为鼠标用户改进了可用性。如果你在label元素内点击文本,就会触发此控件。就是说,当用户选择该标签是,浏览器就会自动將焦点转到和标签相关的表单控件上
<input id="man" type="radio" /><label for="man">男</label>
属性 | 描述 |
---|---|
action | 指定提交到哪个url上 |
method | 提交方式,常用的GET / POST |
Method | Description |
---|---|
GET | URL地址栏上做拼接问号再加参数 |
POST | 隐式提交方式,看不到,可以抓包 |
pre可定义预格式化的文本。
pre元素中的文本通常会保留空格和换行符。
主要用于在网页上显示代码,比如在网页当中显示html模板
*请认真填写需求信息,我们会在24小时内与您取得联系。