前端开发 大前端 W3Cbest

一个专注 WEB 开发的技术博客

0%

组件传值(通信)式

父传后代(后代拿到了父的数据)

  • 父组件引入子组件,绑定数据,
    优点:子组件通过props来接收父组件绑定的数据。这种方式父传子很方便,但是父传给孙子辈分的组件就很麻烦 (父->子->孙)
    缺点:子组件无法直接修改父组件数据,属单向绑定
  • 子组件直接使用父组件的数据,
    优点:子组件通过this.$parent.xxx使用父组件的数据
    缺点:子组件可直接修改父组件数据
  • 依赖注入 provide / inject
    优点:这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
    缺点:不容易定位祖先组件注入位置

后代向父组件传值

  • 子组件通过触发事件向父组件传值 $.emit()
  • 使用ref属性
  • 使用slot插槽方式

兄弟之间组件传值

  • eventBus

父组件直接修改子组件的值,子组件直接修改父组件的值

  • 父组件修改子组值
    <Child ref="setChild"></Child>
    this.$refs.setChild.xxx = '修改值'
  • 子组件直接修改父组件的值
    this.$parent.xxx = '修改值'

eventBus示例

初始化时全局定义

// eventBus.js 中
import Vue from 'vue'
// 第一种定义方式
Vue.prototype.$eventBus = new Vue() 
// 第二种定义方式
window.eventBus = new Vue();
// 第三种定义方式
export const eventBus = new Vue()

触发事件

//使用方式一定义时
// params 多个参数
this.$eventBus.$emit('eventName', param1,param2,...)

//使用方式二定义时
eventBus.$emit('eventName', param1,param2,...)

监听事件

//使用方式一定义时
this.$eventBus.$on('eventName', (param1,param2,...)=>{
    //需要执行 逻辑代码
    // params 多个参数
})

//使用方式二定义时
eventBus.$on('eventName', (param1,param2,...)=>{
    //需要执行 逻辑代码
})

移除事件

在开发的过程中,我们要及时移除不使用的 eventBus ,原因:

  • 为了避免在监听时,事件被反复触发
  • 由于热更新,事件可能会被多次绑定监听,这时也需要移除事件监听
  • 未及时移除的 eventBus 会导致内存泄漏
this.$eventBus.$off('eventName');

//使用方式二定义时
eventBus.$off('eventName');

//使用方式三定义时
eventBus.$off.$on('eventName');

vue踩坑之eventBus.$on多次触发的问题

this.root.Bus.on实际是向Bus容器中添加一个事件监听器,当页面跳转时,原来的vue组件被注销,但是原来vue组件向Bus容器中添加的事件监听器并不会被移除。因此,当下次进入这个vue组件对应的页面时,执行到this.root.Bus.on时,又会向Bus容器中添加一个重复的事件监听器,以此类推,导致Bus容器中有很多个一模一样的事件监听器,从而导致事件只被触发一次,但是回调函数被执行多次的现象。

解决方法:

方法1:在每次on之前调用off卸载掉事件

created(){
    eventBus.$off.$on("fetchList",()=>{
        this.getListData()
    })
}

方法2:在生命周期beforeDestroy destroyed里面调用$off卸载掉事件

beforeDestroy(){
    eventBus.$off("fetchList")
}

如果这变得重复,你可以写一个mixin来自动化。

所有生命周期钩子的 this 上下文将自动绑定至实例中,因此你可以访问 data、computed 和 methods。这意味着你不应该使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos())。因为箭头函数绑定了父级上下文,所以 this 不会指向预期的组件实例,并且this.fetchTodos 将会是 undefined。

vue的生命周期有哪些?

beforCreated

类型:Function

详细:
在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用。

参考:生命周期图示

created

类型:Function

详细:
在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el property 目前尚不可用。

参考:生命周期图示

beforMounted

类型:Function

详细:
在挂载开始之前被调用:相关的 render 函数首次被调用。

该钩子在服务器端渲染期间不被调用。
参考:生命周期图示

mounted

类型:Function

详细:
实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。

注意 mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick:

mounted: function () {
    this.$nextTick(function () {
        // 仅在整个视图都被渲染之后才会运行的代码
    })
}

该钩子在服务器端渲染期间不被调用。

参考:生命周期图示

beforUpdated

类型:Function

详细:
在数据发生改变后,DOM 被更新之前被调用。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。

该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务器端进行。

参考:生命周期图示

updated

类型:Function

详细:
在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。

当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。

注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等到整个视图都渲染完毕,可以在 updated 里使用 vm.$nextTick:

updated: function () {
    this.$nextTick(function () {
        //  仅在整个视图都被重新渲染之后才会运行的代码     
    })
}

该钩子在服务器端渲染期间不被调用。

参考:生命周期图示

beforDestroy

类型:Function

详细:

实例销毁之前调用。在这一步,实例仍然完全可用。

该钩子在服务器端渲染期间不被调用。

参考:生命周期图示

destroyed

类型:Function

详细:

实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。

该钩子在服务器端渲染期间不被调用。

参考:生命周期图示

activated

类型:Function

详细:
被 keep-alive 缓存的组件激活时调用。

该钩子在服务器端渲染期间不被调用。

参考:
构建组件 - keep-alive
动态组件 - keep-alive

deactivated

类型:Function

详细:被 keep-alive 缓存的组件失活时调用。

该钩子在服务器端渲染期间不被调用。

参考:
构建组件 - keep-alive
动态组件 - keep-alive

errorCaptured

类型:(err: Error, vm: Component, info: string) => ?boolean

详细:

在捕获一个来自后代组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

参考:渲染函数

进入组件会执行那些生命周期

进入到页面或进入到组件,会执行 beforeCreated created beforMounted mounted这四个生命周期。beforeCreated在实例初始化后进行数据监听和事件监听之前同步调用。created在实例创建完成后被立即同步调用。beforeCreated 和 created 无法获取到 DOM,mounted时DOM被创建,此时可以获取到DOM

父组件引入子组件,执行生命周期的顺序是什么

  • 父组件引入子组件会先执行父组件的beforeCreated created beforMounted 前三个生命周期。
  • 再执行子组件的beforeCreated created beforMounted mounted前四个生命周期。
  • 然后再执行父组件的 mounted生命周期

在created中获取DOM

created在实例创建完成后被立即同步调用。但此时DOM还未被创建,只有通过异步调用后,在异步中可以获取到DOM
例如:setTimeout、请求接口、$nextTick, $refs等

发送请求在created还是mounted?

发送请求在created还是mounted这个问题具体要看项目和业务的情况

  • 如果组件的加载顺序是父组件引入了子组件,那么会先执行父组件的beforeCreated created和beforMounted的前三个生命周期,再执行子组件的再执行子组件的beforeCreated created beforMounted mounted前四个生命周期,然后再执行父组件的 mounted生命周期。
  • 如果业务是父组件引入子组件,并且优先加载子组件的数据,那么在父组件中当前的请求应放在mounted中。
  • 如果当前组件没有依赖关系那么放在哪个生命周期中请求都可以的。

beforCreated 中发请求的优劣势,beforeCreate和created有什么区别?

优势:可以提前加载好数据,整理数据
劣势:如果请求是在methods封装好了,在beforeCreate调用的时候,beforeCreate阶段是拿不到methods里面的方法的(会报错了) 。

区别:

$data methods
beforeCreate 无 无法获取
created 有 可以获取

created和mounted的区别

created() 和 mounted() 之间的根本区别在于访问DOM,在 created() 中返回 null,在 mounted() 中返回 DOM 元素。因此,任何 DOM 操作,甚至使用诸如 querySelector 之类的方法获取 DOM 元素结构将无法在 created() 中使用。因此根据这点区别 created() 非常适合调用 API,而 mounted() 非常适合在 DOM 元素完全加载后执行任何操作。

加入keep-alive会执行哪些生命周期?

如果使用了keep-alive组件会额外增加2个生命周期activated deactivated
如果当前组件加入了keep-alive第一次进入这个组件会执行5个生命周期 beforeCreated created beforMounted mounted activated

第二次或者第N次进去组件会执行哪些生命周期?

如果当前组件加入了keep-alive,只会执行一个activated生命周期

如果没有加入keep-alive就会执行beforeCreated created beforMounted mounted前四个生命周期

你在什么情况下用过哪些生命周期?说一说生命周期使用场景

created   ===> 单组件请求
mounted   ===> 同步可以获取dom,如果先子组件请求后父组件请求
activated ===> 用于缓存同页面使用,判断id是否相等,如果不相同发起请求
destroyed ===> 关闭页面记录浏览位置,初始化的时候从上一次的历史开始浏览

vue的设计原理

  • 渐进式的JavaScript框架:vue的核心只关注视图层,容易上手,还便于与第三方库或既有项目整合
  • 易用性:vue提供数据响应式、声明式模板语法和基于配置的组件系统等核心特性。这些使我们只需关注应用的核心业务即可。
  • 灵活性:渐进式框架最大优点就是灵活性,如果应用足够小,我们可能仅需要vue核心特性即可完成功能;随着应用规模的不断扩大,我们才可能逐渐引入路由、状态管理、vue-cli等库和工具,不管是应用体积还是学习难度都是一个逐渐增加的平和曲线。
  • 高效性:超快的虚拟DOM和diff算法使我们的应用拥有最佳的性能表现。追求高效的过程还在继续,vue3中引入Proxy对数据响应式改进以及编译器中对于静态内容编译的改进都会让vue更加高效。

vue的常见性能优化

  • 路由懒加载
  • keep-alive缓存页面
  • 使用v-show复用DOM
  • v-for遍历时避免同时使用v-if
  • 长列表性能优化,
    如果列表是纯粹的数据展示,不作任何改变,就不需要做响应化,
    如果是大数据列表,可采用虚拟滚动,只渲染可见区域内容
  • 事件的销毁,解绑全部指令及事件监听器
  • 图片懒加载
  • 第三方插件按需引入

v-model的实现原理

绑定响应数据,触发input事件并传递数据

修改computed计算属性的结果值

  • 需要通过get/set写法
  • 当前组件v-model绑定的值是computed来的,需要通过get/set写法

computed和watch的区别

对于computed

  • 计算属性的结果会被缓存,只有依赖的数据发生变化才会重新计算
  • 不支持异步,computed中有异步操作时,无法监听数据的变化
  • computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。
  • 如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用computed
  • 如果computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法。

对于watch

  • 它支持缓存,只要数据变化时,就会触发响应的操作
  • 支持异步监听
  • 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
  • 当一个属性发生变化时,就需要执行相应的操作
  • 监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数:
    • immediate:组件加载立即触发回调函数
    • deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。

总结

  • computed计算属性:computed的值有缓存,依赖其他属性值,只有它依赖的属性值发生变化,下一次获取computed的值时才会重新计算computed的值。
  • watch监听器:更多的是观察作用,无缓存,类似于某些数据的监听回调,每当监听的数据发生变化时,都会执行回调进行后续操作。

运用场景

  • 当需要进行数据计算,并且依赖于其他数据时,应使用computed,因为可以利用computed的缓存特性,避免每次获取值时都要重新计算。
  • 当需要在数据变化时执行异步或开销较大的操作时,应该使用watch,使用watch选项允许执行异步操作,限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

闭包是 JavaScript 中最强大的特性之一。JavaScript 允许函数嵌套,并且内部函数具有定义在外部函数中的所有变量和函数(以及外部函数能访问的所有变量和函数)的完全访问权限。

但是,外部函数却不能访问定义在内部函数中的变量和函数。这给内部函数的变量提供了一种封装。

此外,由于内部函数可以访问外部函数的作用域,因此当内部函数生存周期大于外部函数时,外部函数中定义的变量和函数的生存周期将比内部函数执行的持续时间要长。当内部函数以某一种方式被任何一个外部函数之外的任何作用域访问时,就会创建闭包。

// 外部函数定义了一个名为“name”的变量
const pet = function (name) {
  const getName = function () {
    // 内部函数可以访问外部函数的“name”变量
    return name;
  };
  return getName; // 返回内部函数,从而将其暴露给外部作用域
};
const myPet = pet("Vivie");

console.log(myPet()); // "Vivie"

实际上可能会比上面的代码复杂的多。它可以返回一个包含用于操作外部函数的内部变量的方法的对象。

const createPet = function (name) {
  let sex;

  const pet = {
    // 在这个上下文中:setName(newName) 等价于 setName: function (newName)
    setName(newName) {
      name = newName;
    },

    getName() {
      return name;
    },

    getSex() {
      return sex;
    },

    setSex(newSex) {
      if (
        typeof newSex === "string" &&
        (newSex.toLowerCase() === "male" || newSex.toLowerCase() === "female")
      ) {
        sex = newSex;
      }
    },
  };

  return pet;
};

const pet = createPet("Vivie");
console.log(pet.getName()); // Vivie

pet.setName("Oliver");
pet.setSex("male");
console.log(pet.getSex()); // male
console.log(pet.getName()); // Oliver

在上面的代码中,外部函数的 name 变量对内部函数来说是可访问的,而除了通过内部函数本身,没有其他任何方法可以取得内部的变量。内部函数的内部变量就像对外部参数和变量的保险柜。它们会为内部函数保留“稳定”而又“被封装”的数据参与运行。而这些内部函数甚至不会被分配给一个变量,或者有个名称。

const getCode = (function () {
  const apiCode = "0]Eal(eh&2"; // 我们不希望外部能够修改的代码......

  return function () {
    return apiCode;
  };
})();

console.log(getCode()); // "0]Eal(eh&2"

备注: 使用闭包时需要注意许多陷阱!

如果一个闭包的函数定义了一个和外部外部的某个变量名称相同的变量,那么这个闭包将无法引用外部作用域中的这个变量。(内部作用域的变量“覆盖”外部作用域,直至程序退出内部作用域。可以将其视作命名冲突。)

const createPet = function (name) {
  // 外部函数定义了一个名为“name”的变量。
  return {
    setName(name) {
      // 闭包函数还定义了一个名为“name”的变量。
      name = name; // 我们如何访问外部函数定义的“name”?
    },
  };
};

思路:加载列表数据切数据不进行额外操作,做数据优化

  • 当页面第一次加载先判断本地储存中有没有旧数据 //储存数据格式{time:Date.now(), data}
  • 没有旧数据直接发送请求,并将数据储存本地
  • 有旧数据并且数据没有过期,就使用本地储存的旧数据
init(){
    <!-- 1,获取本地数据 -->
    const getData = sessionStorage.getItem('key');
    <!-- 2,判断数据是否存在 -->
    if(!getData){
        this.getData()
    }else{
        <!-- 定义过期时间 (如:10s) -->
        if(Date.new() - getData.time > 1000*10){
            <!-- 如果时间超过过期时间 重新发送请求 -->
            this.getData()
        }else{
            <!-- 如果时间未超过过期时间 使用本地储存数据 -->
            this.render(getData.data);
        }
    }
},
getData(){
    <!-- 没有旧数据直接发送请求 -->
    request('url').then(data=>{
        <!-- 并将数据储存本地 -->
        sessionStorage.setItem('key',{time: Date.new(), data});
        this.render(data)
    })
},
render(data){
    <!-- 这里是数据展示 -->
    console.log(data)
}

思路:

let url = 'http://localhost:8080';
let requestTotal = 0;
const request = (params) =>{
  requestTotal++;
  wx.showLoading({
   title: '加载中...',
   mask: true
  })
  return new Promise((resolve, reject) =>{
    wx.request({
      url: url + params.url,
      data: params.data || {},
      method: params.method || 'GET',
      header: {'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'},
      success:(result)=>{
        resolve(result.data)
      },
      fail:(error)=>{
        console.log(error);
        reject(error)
      },
      complete:()=>{
        requestTotal--;
        if(requestTotal===0){
         wx.hideLoading()
        }
      }
    })
  })
}

1.声明 color-scheme

有两种方式。

1.1 meta

在head中声明 meta,声明当前页面支持 light 和 dark 两种模式,系统切换到深色模式时,浏览器默认样式也会切换到深色;

<meta name="color-scheme" content="light dark">

1.2 CSS

下面的 css 同样可以实现上面 meta 声明的效果

:root {
    color-scheme: light dark;
}

注意:此声明并非为页面做自动适配,只影响浏览器默认样式

更多信息可查阅:
W3C文档: CSS Color Adjustment Module Level 1
微信开放文档:DarkMode 适配指南

2.通过 CSS 媒体查询

prefers-color-scheme CSS 媒体特性用于检测用户是否有将系统的主题色设置为亮色或者暗色。

no-preference
表示系统未得知用户在这方面的选项。在布尔值上下文中,其执行结果为 false。

light
表示用户已告知系统他们选择使用浅色主题的界面。

dark
表示用户已告知系统他们选择使用暗色主题的界面。

:root {
    color-scheme: light dark;
    background: white;
    color: black;
}

@media (prefers-color-scheme: dark) {
    :root {
        background: black;
        color: white;
    }
}

颜色较多的情况,建议使用CSS变量对颜色值进行管理

:root {
    color-scheme: light dark;
    --nav-bg-color: #F7F7F7;
    --content-bg-color: #FFFFFF;
    --font-color: rgba(0,0,0,.9);
}

@media (prefers-color-scheme: dark) {
    :root {
        --nav-bg-color: #2F2F2F;
        --content-bg-color: #2C2C2C;
        --font-color: rgba(255, 255, 255, .8);
    }
}

:root {
    color: var(--font-color)
}

.header {
    background-color: var(--nav-bg-color);
}

.content {
    background-color: var(--content-bg-color);
}

3.图片适配

利用picture+source标签,设置不同模式下的图片 url。

HTML 元素通过包含零或多个 元素和一个 元素来为不同的显示/设备场景提供图像版本。浏览器会选择最匹配的子 元素,如果没有匹配的,就选择 元素的 src 属性中的URL。然后,所选图像呈现在元素占据的空间中。

<picture>
    <!-- 深色模式下的图片 -->
    <source srcset="dark.jpg" media="(prefers-color-scheme: dark)" />
    <!-- 默认模式下的图片 -->
    <img src="light.jpg"/>
</picture>

4. JavaScript中判断当前模式&监听模式变化

4.1 matchMedia

Window 的matchMedia() 方法返回一个新的MediaQueryList 对象,表示指定的媒体查询 (en-US)字符串解析后的结果。返回的MediaQueryList 可被用于判定Document是否匹配媒体查询,或者监控一个document 来判定它匹配了或者停止匹配了此媒体查询。

4.2 addListener()

MediaQueryList接口的addListener()方法向MediaQueryListener添加一个侦听器,该侦听器将运行自定义回调函数以响应媒体查询状态的更改。

具体用法参考以下例子:

const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')

function darkModeHandler() {
    if (mediaQuery.matches) {
        console.log('现在是深色模式')
    } else {
        console.log('现在是浅色模式')
    }
}

// 判断当前模式
darkModeHandler()
// 监听模式变化
mediaQuery.addListener(darkModeHandler)

随着移动设备的普及,日常生活中随处可见二维码,大有一码走天下之势。那么前端如何来生成二维码?

通过QRCode.js我们可以快速生成二维码啦,一起来看看怎么使用吧。

1、引入插件

/*html*/
<div id="qrcode"></div>

/*js*/
<script src="qrcode.js"></script>

2、基本用法

var qrcode = document.getElementById('qrcode')

/*直接生成二维码*/
new QRCode(qrcode, 'http://www.w3cbest.com')

/*也可以配置二维码的宽高等*/
var qrcodeObj = new QRCode('qrcode', {
    text: 'http://www.w3cbest.com',
    width: 256,
    height: 256,
    colorDark : '#000000', //前景色
    colorLight : '#ffffff'  //背景色
})

3、API

设置二维码内容–makeCode

qrcodeObj.makeCode('http://www.w3cbest.com/?p=6518&preview=true')

清除二维码–clear

qrcodeObj.clear()

CSS中的选择器用于选择元素并设置其样式。在我们使用其中一个之后,它们可能会非常强大。在本文中,我将引导您通过6个功能强大的CSS选择器,这些选择器将真正帮助您在下一个项目中编写干净的CSS。

1. div >a

这个选择器将使我们能够选择所有父元素是div标签的元素。

<!-- This one will be selected --> 
<div>
<a></a>
</div>

<!-- This one won't be selected -->
<p>
<a></a>
</p>

2. div +a

选择紧接在div元素之后的每个标签。如果我们在div和a标签之间有一个元素,则不会选择该元素。

<main>
<!-- This one will be selected -->
<div></div>
<a></a>
<!-- This one won't be selected -->
<div></div>
<p></p>
<a></a>
</main>

3. div ~a

a标签将选择每个标签,然后在同一级别上添加div标签。换句话说,如果a标签不是紧跟在div标签之后,而是具有div标签作为同级元素,则将选择该标签。

<main>
  <!-- This one will be selected -->
  <div></div>
  <a></a>
  <!-- This one will be selected -->
  <div></div>
  <p></p>
  <a></a>
  <section>
    <!-- This one will be selected -->
    <div></div>
    <p></p>
    <a></a>
  </section>


  <footer>
    <!-- This one won't be selected -->
    <p></p>
    <a></a>
  </footer>
</main>

4. [属性^=值]

例如:[class ^ ="list-"]此选择器将选择每个包含class属性且其值以list-开头的元素。

<main>
<!-- This one will be selected -->
<div class="list-element"></div>
<!-- This one will be selected -->
<div class="list-container"></div>
<!-- This one will be selected -->
<div class="list-item"></div>
<!-- This one won't be selected -->
<div class="list__footer"></div>
</main>

5. [属性$=值]

例如:[src $ =”.png”]这将选择每个值以.png结尾的src属性。

<div>
<!-- This one will be selected -->
<img src="image1.png">
<!-- This one will be not selected -->
<img src="image2.jpg">
<!-- This one will be selected -->
<img src="image3.png">
<!-- This one won't be selected -->
<img src="image4.svg">
</div>

6. [属性*=值]

例如:[class * ="-list"]此选择器将选择其class属性包含-list的每个元素。 不管-list是在类值的开头,中间还是结尾都没有关系。最重要的是该值必须包含-list。

<main>
<!-- This one will be selected -->
<div class="main-list-container"></div>
<!-- This one will be selected -->
<div class="primary-list"></div>
<!-- This one will be selected -->
<div class="primary-list-item"></div>
<!-- This one won't be selected -->
<div class="list-footer"></div>
</main>

结论 有时候,很难找到所需样式的元素,这是因为我们对CSS选择器不够熟悉,而导致被滥用。在实际应用中,这些选择器可能是非常有用。

防抖

触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

思路:

每次触发事件时都取消之前的延时调用方法

function debounce(fn) {
    let timeout = null; // 创建一个标记用来存放定时器的返回值
    return function () {
    clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
    timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
        fn.apply(this, arguments);
    }, 500);
    };
}
function sayHi() {
    console.log('防抖成功');
}

var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖

节流

高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率

思路:
每次触发事件时都判断当前是否有等待执行的延时函数

function throttle(fn) {
    let canRun = true; // 通过闭包保存一个标记
    return function () {
    if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
    canRun = false; // 立即设置为false
    setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
        fn.apply(this, arguments);
        // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
        canRun = true;
    }, 500);
    };
}
function sayHi(e) {
    console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));