前端面试系列
一.如何确定this的值
1.全局执行坏境,指向全局对象window(严格和非严格模式下)
2.函数内部,取决于函数被调用的方式
1.直接调用的this值:严格模式下是undefined;非严格模式下是window
2.对象.方法():都指向调用者
注释严格模式是指在全局或函数内部顶端写下'use strict'
二.指定this的值
1.调用时指定:
function.call(),function.apply()
const person={a:1}
function fn(a,b){
console.log(this,a,b)
}
// fn.call(person,10,20)
// fn(10,20)
fn.apply(person,[0,1,2])
2.创建时指定:
const fn2 = fn.bind(person,5,6)
fn2(3,4) //5 6
const obj = {
name:'张三',
fn(){
console.log(this)
// setTimeout(function(){
// console.log(this) //window
// },1000)
setTimeout(()=>{
console.log(this) //obj
})
}
}
obj.fn()
以下是一些使用这些方法的常见情况:
- 在继承中使用:
call和apply方法可以在实现继承时调用父类的构造函数,从而避免代码重复。 - 改变函数执行上下文:当需要在不同的对象上调用同一个函数时,
call和apply方法可以临时改变函数的执行上下文。 - 函数式编程:在函数式编程中,
bind方法可以用于创建新的函数,固定部分参数,方便函数组合和柯里化。 - 回调函数:在一些异步操作中,使用
call或apply方法可以确保回调函数中的this指向正确。
1.手撕call()方法,个人理解就是给第一个传入的对象上先加上这个方法,然后就传入参数调用,得到结果后,再从对象上删去,给人的感觉就是借鸡下蛋。。。
call方法的作用不就是改变函数的指向嘛,将调用call的函数的this指向传入的第一个对象参数,所以实际上就是吧调用call的函数赋给这个对象,让它来调用,调用完再删了,而调用的过程中,就可以用一下这个对象上的其它属性和值喽,理解了的话就会感觉,切,还以为啥牛逼呢?!!!就这。。。
Function.prototype.myCall = function (context, ...args) {
const key = Symbol('key')
context[key] = this
const res = context[key](...args)
delete context[key]
return res
}
const person = {
name: 'zhangsan'
}
function fn(a, b) {
console.log(this)
console.log(a, b)
console.log(this.name)
return a + b
}
// fn(1,2)
const res = fn.myCall(person, 10, 20)
console.log(res)
2.手撕apply(),和call()方法一样,唯一的区别就是传参成数组了,上代码。。。
Function.prototype.myCall = function (context,numArr) {
const key = Symbol('key')
context[key] = this
const res = context[key](numArr)
delete context[key]
return res
}
const person = {
name: 'zhangsan'
}
function fn(numArr) {
console.log(this)
let sum = 0
numArr.forEach(item=>{
console.log(item)
sum+=item
})
console.log(this.name)
return sum
}
// fn(1,2)
const res = fn.myCall(person,[1,2,3,4,5])
console.log(res) //1 2 3 4 5 zhangsan 15
3.手撕bind,里面用到了call
Function.prototype.myBind = function (context,...Args){
return (...reArgs)=>{
// this就是调用myBind的函数
return this.call(context,...Args,...reArgs)
}
}
const person = {
name: 'zhangsan'
}
function fn(a,b,c) {
console.log(this)
return a+b+c
}
const resFn = fn.myBind(person,1,2)
console.log(resFn(3)) //6
三.js继承:子类可以具有父类的方法和属性,无需自己编写
class类的实现:
class Person{
name
age=18
constructor(name){
this.name = name
}
study(){
console.log(`${this.name}爱学习`)
}
}
const p = new Person('小明')
console.log(p)
p.study()
class类实现继承:
class Student extends Person{
schoolNum
constructor(name,schoolNum){
//必须调用父类构造函数,否则会报错
super(name)
this.schoolNum = schoolNum
}
talk(){
console.log(`${this.name}同学的学号是${this.schoolNum}`)
}
}
const stu = new Student('小王',20210806)
静态属性/方法:通过static关键字定义静态方法,通过类本身来调用(实例也能调用,但不推荐)
私密属性/方法:前缀加#,只能在类内使用
测试过,实例可以给静态属性和私密属性赋值
class Student extends Person{
static schoolNum
#hobby='私密爱好~~'
constructor(name,schoolNum,hobby){
//必须调用父类构造函数,否则会报错
super(name)
this.schoolNum = schoolNum
this.#hobby = hobby
}
static talk(){
console.log('我是静态方法,可以在类内部调用')
}
#exercise(){
console.log('我是私有方法,只能在类内部调用')
}
out(){
console.log('私密爱好是:',this.#hobby+'\n静态属性是:'+this.schoolNum)
this.#exercise()
}
}
const stu = new Student('小王',20210806,'洗脚')
console.log(stu)
stu.out()
ES5-寄生组合式继承:通过借用构造函数继承属性(使用的父类的构造函数,this指向自己,所以给自己动态添加了属性),通过原型链来继承方法
寄生:把父类原型中的constructor改为指向自身的
function Person(name){
this.name = name
}
Person.prototype.say=function(){
console.log('我爱说实话',this)
}
function Student(name){
//通过构造函数继承属性
Person.call(this,name)
}
const prototype = Object.create(Person.prototype,{
constructor:{
value:Student
}
})
Student.prototype = prototype
const stu = new Student('小明')
console.log(stu.name,stu.say())
fetch:返回一个promise对象,可以用await等待
let btn = document.querySelector('.btn')
btn.addEventListener('click', async () => {
const p = new URLSearchParams({ pname: '湖南省', cname: '长沙市' })
const res = await fetch('http://hmajax.itheima.net/api/area?' + p.toString())
if (res.status >= 200 && res.status < 300) {
console.log(res)
const data = await res.json()
console.log(data)
} else {
console.log(res.status, '请求失败')
}
})
fetch上传文件
<input type="file">
<img src="" alt="">
<script>
document.querySelector('input').addEventListener('change',async function(){
const img = this.files[0]
console.log(img)
const data = new FormData()
data.append('img',img)
const res = await fetch('http://hmajax.itheima.net/api/uploadimg',{
method:'post',
body:data
})
console.log(res)
const resData = await res.json()
console.log(resData)
document.querySelector('img').src = resData.data.url
})
feath上传json
document.querySelector('.btn').addEventListener('click',async()=>{
const headers = new Headers()
headers.append('content-type','application/json')
const res = await fetch('http://hmajax.itheima.net/api/register',{
method:'post',
headers,
body:JSON.stringify({
username:'whdjajdnajd',
password:'121348342j'
})
})
console.log(res)
const resData = await res.json()
console.log(resData)
})
在JavaScript中,for...of语句用于遍历可迭代对象(如数组、Map、Set等),而普通对象(Object)是不可迭代的,因此无法直接使用for...of来遍历普通对象。
如果要遍历普通对象的属性,可以使用for...in语句来实现。示例代码如下:
const obj = {a: 1, b: 2, c: 3};
for (const key in obj) {
console.log(key, obj[key]);
}
如果想要使用for...of来遍历普通对象的属性,可以通过将对象的属性转换为可迭代对象来实现。示例代码如下:
const obj = {a: 1, b: 2, c: 3};
for (const entry of Object.entries(obj)) {
console.log(entry);
}
在这个示例中,Object.entries(obj)将对象的属性转换为一个包含键值对的数组,然后就可以使用for...of来遍历这个数组了。
Generator函数是ES6提供的一种异步编程解决方案
function* generatorArr(){
yield 'x',
yield 'y',
yield 'z'
}
const genArr = generatorArr()
// for (const iterator of genArr) {
// console.log(iterator)
// }
三种异步:
1.promise:异步编程,内部返回一个新的promise对象,后面就可以接着then()调用
2.generator():同步定义,使用起来繁琐,利用yield关键字来分隔逻辑比如示例中依次调用了多个接口,通过yield分隔,通过next来触发调用
3.async:使用方便,阅读方便
Promise
实例方法:.catch(...),.finally(...)
静态方法:.resolve(...),.reject(...),.race(...),.all(...),.allSettled(...),.any(...)
手撕Promise
// 声明变量存储三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class myPromise{
state = PENDING
result = undefined
constructor(func){
//定义resolve
const resolve = (result)=>{
if(this.state === PENDING)
{
this.state = FULFILLED
this.result = result
}
}
const reject = (result)=>{
if(this.state === PENDING){
this.state = REJECTED
this.result = result
}
}
// const my_finally = ()=>{
// console.log('最后执行finally!!')
// }
func(resolve,reject)
// my_finally()
}
//实现then()方法
then(onFulfilled, onRejected){
onFulfilled = typeof onFulfilled ==='function'? onFulfilled :x=>x
onRejected = typeof onRejected ==='function'? onRejected :x=>{throw x}
if(this.state === FULFILLED){
onFulfilled(this.result)
}else if(this.state === REJECTED){
onRejected(this.result)
}
}
}
// myPromise实例
const my_promise = new myPromise((resolve,reject)=>{
// resolve('成功')
reject('失败')
})
my_promise.then(res=>{
console.log('成功回调:',res)
},err=>{
console.log('失败回调:',err)
})
现在实现异步回调和链式调用
Vue实现异步回调选用queueMicrotask和MutationObserve
console.log('1')
queueMicrotask(()=>{
console.log('3')
})
console.log('2')
console.log('start')
const mo = new MutationObserver(()=>{
console.log('创建mo实例时,传入回调函数,当监听的节点发生变化后,执行回调!!')
})
const divNode = document.createElement('div')
mo.observe(divNode,{childList:true})
divNode.innerHTML = '最爱喝牛奶'
console.log('end')
console.log('start')
const p =new Promise((resolve,reject)=>{
//这里还是同步执行的,直到遇到resolve()或reject()才放入异步队列
console.log(111)
// console.log('resolve',resolve)
// console.log('reject',reject)
console.log(this)
resolve(reject(resolve(reject('sb'))))
})
p.then(res=>{
console.log('成功',res)
}).catch(err=>{
console.log('失败',err)
})
console.log('end')
console.log(p)
函数柯里化:将接收多个参数的原函数改写为只接受一个参数(原函数的第一个参数)并返回结果(是函数)的函数。
function sum(a){
return function(b){
return a+b
}
}
console.log(sum(1)(2)) //3
动生成加数的个数,传入加数得到和
const fn = count => {
let arr = [] //保存传入参数
return (...args) => {
arr.push(...args)
if (arr.length >= count) {
const res = arr.slice(0, count).reduce((p, v) => p + v, 0)
arr = []
return res
} else {
return fn
}
}
}
fn(3)(1,2,3) //6
函数柯里化具体应用——判断类型
const typeOfTest = type => thing => typeof thing === type
const objectType = typeOfTest('object')
console.log(objectType({})) //true
JS设计模式
工厂模式:调用即可返回新对象的函数
- Vue3的createApp:将全局改变Vue实例的行为,移到单个Vue实例上
- axios-create:基于自定义配置新建实例
单例模式:单例对象整个系统保证只有一个存在
观察者模式:在对象之间定义一对多的依赖,目标对象变化后,所有观察者对象都会自动收到通知,从而触发回调函数。
1.监听事件
2.watch
发布订阅模式:和观察者模式很像,区别是多了个中间商
手写一个事件总线
<button class="on">注册事件</button>
<button class="emit">触发事件</button>
<button class="off">移除事件</button>
<button class="once-on">一次性事件注册</button>
<button class="once-emit">一次性事件触发</button>
class myEmmiter {
// 私有属性,键值对方式存贮事件名及其回调函数
#handler = {}
// 注册事件
$on(event, callback) {
if (this.#handler[event] === undefined) {
this.#handler[event] = []
}
this.#handler[event].push(callback)
}
// 触发事件
$emit(event, ...args) {
const events = this.#handler[event] || [] //拿到事件对应回调函数的数组
events.forEach(callback => callback(...args))
}
$off(event){
this.#handler[event] = undefined
}
$once(event,callback){
this.$on(event,(...args)=>{
callback(...args)
this.$off(event)
})
}
}
const bus = new myEmmiter()
document.querySelector('.on').addEventListener('click', () => {
bus.$on('event1', () => console.log('我是无敌的'))
bus.$on('event2', () => console.log('你是无敌的'))
})
// 触发事件
document.querySelector('.emit').addEventListener('click', () => {
bus.$emit('event1')
bus.$emit('event2')
})
// 移除事件
document.querySelector('.off').addEventListener('click', () => {
bus.$off('event2')
})
// 一次性事件注册
document.querySelector('.once-on').addEventListener('click', () => {
bus.$once('event3',(name)=>{
console.log(name+',天下无双')
})
})
// 一次性事件触发
document.querySelector('.once-emit').addEventListener('click', () => {
bus.$emit('event3','宫本武藏')
})
原型模式:基于一个已有的对象复制一个新的对象,而不是新建
1.Object.create():以传入的对象作为原型,创建一个对象
2.Vue2数组的7中方法:push,pop,unshift, shift, splice, reserve, sort
代理模式:拦截和控制与目标对象的交互
for ... in ... : 索引遍历对象,包括继承来的属性
for... of ... : 遍历 Array,Map,Set,String,TypeArray,arguments等等
迭代器模式
迭代协议可以定制对象的迭代行为 分为2个协议:
1.可迭代协议: 增加方法Symbol.iterator{} 返回符合 迭代器协议 的对象
2.迭代器协议:
有next方法的对象,next方法返回:
已结束: {done:true}
继续迭代: {done:false,value:'x'}
- 使用Generator
- 自己实现 对象,next
const o = {
// 1.generator方法
// [Symbol.iterator]() {
// function* foodGenerator() {
// yield 'a',
// yield 'b',
// yield 'c'
// }
// const r = foodGenerator()
// return r
// }
// 2.自己手写
[Symbol.iterator]() {
const arr = [1,2,3]
let index =0
return {
next(){
if(index<arr.length){
return {
done:false,
value:arr[index++]
}
}
return {done:true}
}
}
}
}
for (const iterator of o) {
console.log(iterator)
}
防抖:触发频率高,耗费性能,只执行最后一次操作
1.频率高:改变视口宽高 2.input输入 3.scroll 4.keyup..
2.耗费性能:操纵页面、网络请求...
防抖功能,this指向,参数
function debounce(func,wait=0){
let timeId
return function(...args){
const _that = this
clearTimeout(timeId)
timeId = setTimeout(()=>{
func.apply(_that,args)
},wait)
}
}
节流:触发频率高,耗费性能,只处理一次
function throttle(func,wait=0){
let timeId
return function(...args){
const _that = this
if(timeId){
return
}else{
timeId = setInterval(()=>{
func.apply(_that,args)
timeId = undefined
},wait)
}
}
}