前端面试2

## 节流防抖

 

防抖和节流严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死。所以还是很有必要早点掌握的。(信我,你看完肯定就懂了)

 

--------

 

## 从滚动条监听的例子说起

 

去抖动。策略是当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。 这是debounce的基本思想,在后期又扩展了前缘debounce,即执行动作在前,然后设定周期,周期内有事件被触发,不执行动作,且周期重新设定。

 

先说一个常见的功能,很多网站会提供这么一个按钮:用于返回顶部。

 

返回顶部按钮

 

这个按钮只会在滚动到距离顶部一定位置之后才出现,那么我们现在抽象出这个功能需求-- 监听浏览器滚动事件,返回当前滚条与顶部的距离

这个需求很简单,直接写:

 

```javascript

function showTop  () {

    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;

  console.log('滚动条位置:' + scrollTop);

}

window.onscroll  = showTop

```

但是!

 

在运行的时候会发现存在一个问题:这个函数的默认执行频率,太!高!了!。 高到什么程度呢?以chrome为例,我们可以点击选中一个页面的滚动条,然后点击一次键盘的【向下方向键】,会发现函数执行了8-9次!


 

然而实际上我们并不需要如此高频的反馈,毕竟浏览器的性能是有限的,不应该浪费在这里,所以接着讨论如何优化这种场景。

 

--------

 

## 防抖(debounce)

 

基于上述场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:

 

* .如果在200ms内没有再次触发滚动事件,那么就执行函数

* .如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时

* .效果:如果短时间内大量触发同一事件,只会执行一次函数。

 

实现:既然前面都提到了计时,那实现的关键就在于setTimeOut这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现:

 

```javascript

/*

* fn [function] 需要防抖的函数

* delay [number] 毫秒,防抖期限值

*/

function debounce(fn,delay){

    let timer = null //借助闭包

    return function() {

        if(timer){

            clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时

            timer = setTimeOut(fn,delay) 

        }else{

            timer = setTimeOut(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时

        }

    }

}

```

 

当然 上述代码是为了贴合思路,方便理解(这么贴心不给个赞咩?),写完会发现其实 time = setTimeOut(fn,delay)是一定会执行的,所以可以稍微简化下:

 

```javascript

/*****************************简化后的分割线 ******************************/

function debounce(fn,delay){

    let timer = null //借助闭包

    return function() {

        if(timer){

            clearTimeout(timer) 

        }

        timer = setTimeout(fn,delay) // 简化写法

    }

}

// 然后是旧代码

function showTop  () {

    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;

  console.log('滚动条位置:' + scrollTop);

}

window.onscroll = debounce(showTop,1000) // 为了方便观察效果我们取个大点的间断值,实际使用根据需要来配置

```

 

此时会发现,必须在停止滚动1秒以后,才会打印出滚动条位置。

 

到这里,已经把防抖实现了,现在给出定义:

 

对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。

 

--------

 

## 节流(throttle)

 

节流的策略是,固定周期内,只执行一次动作,若有新事件触发,不执行。周期结束后,又有事件触发,开始新的周期。 节流策略也分前缘和延迟两种。与debounce类似,延迟是指 周期结束后执行动作,前缘是指执行动作后再开始周期。


 

继续思考,使用上面的防抖方案来处理问题的结果是:

 

如果在限定时间段内,不断触发滚动事件(比如某个用户闲着无聊,按住滚动不断的拖来拖去),只要不停止触发,理论上就永远不会输出当前距离顶部的距离。

 

但是如果产品同学的期望处理方案是:即使用户不断拖动滚动条,也能在某个时间间隔之后给出反馈呢?(此处暂且不论哪种方案更合适,既然产品爸爸说话了我们就先考虑怎么实现)


 

其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。

 

效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。

 

实现 这里借助setTimeout来做一个简单的实现,加上一个状态位valid来表示当前函数是否处于工作状态:

 

```javascript

function throttle(fn,delay){

    let valid = true

    return function() {

       if(!valid){

           //休息时间 暂不接客

           return false 

       }

       // 工作时间,执行函数并且在间隔期内把状态位设为无效

        valid = false

        setTimeout(() => {

            fn()

            valid = true;

        }, delay)

    }

}

/* 请注意,节流函数并不止上面这种实现方案,

   例如可以完全不借助setTimeout,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。

   也可以直接将setTimeout的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行fn之后消除定时器表示激活,原理都一样

    */

 

// 以下照旧

function showTop  () {

    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;

  console.log('滚动条位置:' + scrollTop);

}

window.onscroll = throttle(showTop,1000) 

```

 

运行以上代码的结果是:

 

如果一直拖着滚动条进行滚动,那么会以1s的时间间隔,持续输出当前位置和顶部的距离

 

--------

 

## 其他应用场景举例

 

讲完了这两个技巧,下面介绍一下平时开发中常遇到的场景:

 

* .搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求。

* .页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)

 

--------

 

## 思考总结

 

上述内容基于防抖和节流的核心思路设计了简单的实现算法,但是不代表实际的库(例如undercore js)的源码就直接是这样的,最起码的可以看出,在上述代码实现中,因为showTop本身的很简单,无需考虑作用域和参数传递,所以连apply都没有用到,实际上肯定还要考虑传递argument以及上下文环境(毕竟apply需要用到this对象)。这里的相关知识在本专栏《柯里化》和《this对象》的文章里也有提到。本文依然坚持突出核心代码,尽可能剥离无关功能点的思路行文因此不做赘述。


 

--------

 

## 非父子组件传值

 

--------

 

## 第一个组件 first.vue

```javascript

import Bus from '../bus.js';

export default {

  name: 'first',

  data () {

    return {

      value: '我来自first.vue组件!'

    }

  },

  methods:{

    add(){// 定义add方法,并将msg通过txt传给second组件

      Bus.$emit('txt',this.value);

    }

  }

}

```

--------

 

## 第二个组件second.vue

 

```javascript

import Bus from '../bus.js';

export default {

  name: 'second',

  data () {

    return {

    }

  },

  mounted:function(){

    Bus.$on('txt',function(val){//监听first组件的txt事件

      console.log(val);

    });

  }

}

 

```

 

这样,就可以在第二个非父子关系的组件中,通过第三者bus.js来获取到第一个组件的value。

 

--------

 

## vue3 新特性

 

总结起来,Vue 3 以下方面值得我们期待 :

 

* .更快

* .更小

* .更易于维护

* .更多的原生支持

* .更易于开发使用

 

Evan 和 Vue 团队的目标是尽可能顺利地过渡到 Vue 3 ,在这个过程中,这些变化在无形地改善了框架。

 

--------


 

## 重写虚拟DOM (Virtual DOM Rewrite)

 

随着虚拟 DOM 重写,我们可以期待更多的 编译时(compile-time)提示来减少 运行时(runtime)开销。重写将包括更有效的代码来创建虚拟节点。

 

--------

 

## 优化插槽生成(Optimized Slots Generation)

 

在当前的 Vue 版本中,当父组件重新渲染时,其子组件也必须重新渲染。 使用 Vue 3 ,可以单独重新渲染父组件和子组件。

 

--------

 

## 静态树提升(Static Tree Hoisting)

 

使用静态树提升,这意味着 Vue 3 的编译器将能够检测到什么是静态组件,然后将其提升,从而降低了渲染成本。它将能够跳过未整个树结构打补丁的过程。

 

--------

 

## 静态属性提升(Static Props Hoisting)

 

此外,我们可以期待静态属性提升,其中 Vue 3 将跳过不会改变节点的打补丁过程。

 

--------

 

## 基于 Proxy 的观察者机制

 

目前,Vue 的反应系统是使用 ObectDefineProperty 的 getter 和 setter。 但是,Vue 3 将使用 ES2015 Proxy 作为其观察者机制。 这消除了以前存在的警告,使速度加倍,并节省了一半的内存开销。

 

为了继续支持 IE11,Vue 3 将发布一个支持旧观察者机制和新 Proxy 版本的构建。

 

--------

 

## 使 Vue 更小

 

Vue已经非常小了,在运行时(runtime)压缩后大约 20kb 。 但我们可以期待它会变得更加小,新的核心运行时压缩后大概 10kb 。 这将在很大程度上通过消除不使用的库(也称为Tree Shaking)来实现。 例如,如果您没有使用 过渡(transition)元素,则不会包含它。

 

--------

 

## 使其更具可维护性

 

虽然大多数 Vue 开发人员都没有在库本身上工作,但很高兴知道 Vue 3 将带来更多可维护的源代码。 它不仅会使用 TypeScript ,而且许多软件包将被解耦,使所有内容更加模块化。

 

--------

 

## 更多的原生支持

 

运行时内核也将与平台无关,使得 Vue 可以更容易地与任何平台(例如Web,iOS或Android)一起使用。

 

--------

 

## 更易于开发使用

 

Observer 模块已被解压缩到自己的包中,允许您以新的方式使用它:


 

跟踪重新渲染的位置也会更容易。 在 Evan 的演讲中,他做了一些实时编码,并展示了如何跟踪 Vue 应用程序(使用下面的代码)来找出触发组件重新渲染的内容。 这在更大的应用程序和性能微调中非常有用。



 

## Vue 3.0 还会改进对 TypeScript 的支持,允许在编辑器中进行高级的类型检查和有用的错误和警告。

 

--------

 

## 实验性的 Hooks API

 

当我们需要在 Vue 中共享两个组件之间的行为时,我们通常使用 Mixins 。然而,Evan 正在尝试使用 Hooks API 来避免来自 Mixins 的一些问题,并且更适合使用惯用的 Vue 代码。

 

--------

 

## 实验性的 Time Slicing 支持

 

当您有许多组件同时尝试重新渲染时,任何浏览器都可以开始变得很慢,从而使用户体验下降。

 

Evan展示了他如何尝试使用 Time Slicing,将 JS 的执行分解为几个部分,如果有用户交互需要处理,这些部分将提供给浏览器。

 

--------

 

## 前端性能优化

 

--------

 

## 减少请求资源大小或者次数 

* 1、尽量和并和压缩css和js文件。(将css文件和并为一个。将js合并为一个)

  原因:主要是为了减少http请求次数以及减少请求资源的大小

  打包工具:

  * .webpack

  * .gulp

  * .grunt

  * ....

* 2、尽量所使用的字体图标或者SVG图标来代替传统png图

  因为字体图标或者SVG是矢量图,代码编写出来的,方大不会变形,而且渲染速度快

 

* 3、采用图片的懒加载(延迟加载)

  目的为了,减少页面第一次加载过程中http的请求次数

  具体步骤:

    1、页面开始加载时不去发送http请求,而是放置一张占位图

    2、当页面加载完时,并且图片在可视区域再去请求加载图片信息

 

* 4、能用css做的效果,不要用js做,能用原生js做的,不要轻易去使用第三方插件。

  避免引入第三方大量的库。而自己却只是用里面的一个小功能

 

* 5、使用雪碧图或者是说图片精灵

  把所有相对较小的资源图片,绘制在一张大图上,只需要将大图下载下来,然后利用

  图片定位来讲小图展现在页面中(background-position:百分比,数值)

 

* 6、减少对cookie的使用(最主要的就是减少本地cookie存储内容的大小),因为客户端操作cookie的时候,这些信息总是在客户端和服务端传递。如果上设置不当,每次发送

 

一个请求将会携带cookie

 

* 7、前端与后端进行数据交互时,对于多项数据尽可能基于json格式来进行传送。相对于使用xml

  来说传输有这个优势

  目的:是数据处理方便,资源偏小

 

* 8、前端与后端协商,合理使用keep-alive

 

* 9、前端与服务器协商,使用响应资源的压缩

 

* 10、避免使用iframe

  不仅不好管控样式,而且相当于在本页面又嵌套其他页面,消耗性能会更大。因为还回去加载这个嵌套页面的资源

 

11、在基于ajax的get请求进行数据交互的时候,根据需求可以让其产生缓存(注意:这个

缓存不是我们常看到的304状态码,去浏览器本地取数据),这样在下一次从相同地址获取是数据

时,取得就是上一次缓存的数据。(注意:很少使用,一般都会清空。根据需求来做)

 

--------

 

## 代码优化相关

 

* 1、在js中尽量减少闭包的使用

  原因:使用闭包后,闭包所在的上下文不会被释放

 

* 2、减少对DOM操作,主要是减少DOM的重绘与回流(重排)

  关于重排(回流)的分离读写:如果需要设置多个样式,把设置样式全放在一起设置,不要一条一条的设置。使用文档碎片或者字符串拼接做数据绑定(DOM的动态创建)

 

* 3、在js中避免嵌套循环和"死循环"(一旦遇到死循环,浏览器就会直接卡掉)

 

* 4、把css放在body上,把js放在body下面

  让其先加载css(注意:这里关于优化没有多大关系)

 

* 5、减少css表达式的使用

 

* 6、css选择器解析规则所示从右往左解析的。减少元素标签作为对后一个选择对象

 

* * 7、尽量将一个动画元素单独设置为一个图层(避免重绘或者回流的大小)

  注意:图层不要过多设置,否则不但效果没有达到反而更差了

 

* 8、在js封装过程中,尽量做到低耦合高内聚。减少页面的冗余代码

 

* 9、css中设置定位后,最好使用z-index改变盒子的层级,让盒子不在相同的平面上

 

* 10、css导入的时候尽量减少@import导入式,因为@import是同步操作,只有把对应的样式导入后,才会继续向下加兹安,而link是异步的操作

 

* 11、使用window.requestAnimationFrame(js的帧动画)代替传统的定时器动画

  如果想使用每隔一段时间执行动画,应该避免使用setInterval,尽量使用setTimeout

  代替setInterval定时器。因为setInterval定时器存在弊端:可能造成两个动画间隔时间

  缩短

 

* 12、尽量减少使用递归。避免死递归

  解决:建议使用尾递归

 

* 13、基于script标签下载js文件时,可以使用defer或者async来异步加载

 

* 14、在事件绑定中,尽可能使用事件委托,减少循环给DOM元素绑定事件处理函数。

 

* 15、在js封装过程中,尽量做到低耦合高内聚。减少页面的冗余代码

 

* 16、减少Flash的使用

 

--------

 

## 存储

 

* 1、结合后端,利用浏览器的缓存技术,做一些缓存(让后端返回304,告诉浏览器去本地拉取数据)。(注意:也有弊端)可以让一些不太会改变的静态资源做缓存。比如:一些图片,js,cs

 

* 2、利用h5的新特性(localStorage、sessionStorage)做一些简单数据的存储,

  避免向后台请求数据或者说在离线状态下做一些数据展示。

 

--------

 

## 其他优化

 

* 1、避免使用iframe不仅不好管控样式,而且相当于在本页面又嵌套其他页面,消耗性能会更大。因为还回去加载这个嵌套页面的资源

 

* 2、页面中的是数据获取采用异步编程和延迟分批加载,使用异步加载是数据主要是为了避免浏览器失去响应。如果你使用同步,加载数据很大并且很慢

  那么,页面会在一段时间内处于阻塞状态。目的:为了解决请求数据不耽搁渲染,提高页面的

  渲染效率。解决方法:需要动态绑定的是数据区域先隐藏,等数据返回并且绑定后在让其显示

  延迟分批加载类似图片懒加载。减少第一次页面加载时候的http请求次数

 

* 3、页面中出现音视频标签,我们不让页面加载的时候去加载这些资源(否则第一次加载会很慢)

  解决方法:只需要将音视频的preload=none即可。

  目的:为了等待页面加载完成时,并且音视频要播放的时候去加兹安音视频资源

 

* 4、尽量将一个动画元素单独设置为一个图层(避免重绘或者回流的大小)

  注意:图层不要过多设置,否则不但效果没有达到反而更差了


 

--------

 

## 大量插入dom元素的方法(性能问题)

 

--------

 

向```<ul id="root"></ul>```插入1000条```<li>```我是li标签```</li>```标签

 

一般我们会想到使用循环,但是,由于渲染回流,在for循环内部多次appendChild会造成多次渲染,从而出现卡、闪屏的现象

 

我们经常使用javascript来操作DOM元素,比如使用appendChild()方法。

每次调用该方法时,浏览器都会重新渲染页面。如果大量的更新DOM节点,则会非常消耗性能,影响用户体验.

javascript提供了一个文档碎片DocumentFragment的机制。

如果将文档中的节点添加到文档片段中,就会从文档树中移除该节点。

 

把所有要构造的节点都放在文档片段中执行,这样可以不影响文档树,也就不会造成页面渲染。

当节点都构造完成后,再将文档片段对象添加到页面中,这时所有的节点都会一次性渲染出来,

这样就能减少浏览器负担,提高页面渲染速度

 

解决办法:创建文档片段Fragment,将标签全部放入该片段中,再统一插入document,这样只会渲染一次

 

```javascript

<ul id="root"></ul>

<script>

var root = document.getElementById('root')

var fragment = document.createDocumentFragment()

for(let i = 0; i < 1000; i++){

    let li = document.createElement('li')

    li.innerHTML = '我是li标签'

    fragment.appendChild(li)

}

root.appendChild(fragment);

</script>

```


 

--------

 

## proxy相比Object.defineProperty的优势

 

Proxy的优势:

 

可以直接监听对象而非属性

可以直接监听数组的变化

拦截方式较多

Proxy返回一个新对象,可以只操作新对象达到目的,而Object.defineProperty只能遍历对象属性直接修改

Proxy作为新标准将受到浏览器厂商重点持续的性能优化

Object.defineProperty的优势如下:

 

兼容性好,支持IE9

 

--------

 

## 股价问题

 

```javascript

/*

  题目:给定n天股价,问只能买入卖出各一次的情况下,求最大盈利额(时间复杂度要求为O(n))

  输入:[7,1,2,3,4,6,5]    周二以1元买入,周六以6元卖出能够获取最大利润5元

  输出:5

  输入:[7,6,5,4,3,2,1]    股价持续走低,无论怎么买都会亏本,所以盈利额为0

  输出:0

*/

function getMaxProfit(arr) {

  let min = 99

  let profit = 0

  let length = arr.length

  for (let i = 0; i < length; i++) {

    if (arr[i] < min) {

      min = arr[i]

    } else if (arr[i] - min > profit) {

      profit = arr[i] - min

    }

  }

  return profit

}

```

 

--------

 

## 实现一个两列固定,中间自适应有哪些方法

 

--------

 

## 第一种:左右侧采用浮动 中间采用margin-left 和 margin-right 方法。

 

```HTML

<div style="width:100%; margin:0 auto;"> 

 

    <div style="width:200px; float:right; background-color:#960">这是右侧的内容 固定宽度</div>

 

    <div style="width:150px; float:left; background:#6FF">这是左侧的内容 固定宽度</div>

 

    <div style="margin-left:150px;margin-right:200px; background-color:#9F3">中间内容,自适应宽度</div>

 

</div>

```

 

--------

 

## 第二种:负的margin

 

```HTML

<div id="main">

 <div id="mainContainer">main content</div>

</div>

<div id="left">

 <div id="leftContainer" class="inner">left content</div>

</div>

<div id="right">

 <div id="rightContainer" class="inner">right</div>

</div>

 

<style>

#main {

 float: left;

 width: 100%;

}

#mainContainer {

 margin: 0 230px;

 height: 200px;

 background: green;

}

#left {

 float: left;

 margin-left: -100%;

 width: 230px

#right {

 float: left;

 margin-left: -230px;

 width: 230px;

#left .inner,

#right .inner {

 background: orange;

 margin: 0 10px;

 height: 200px;

}

</style>

```

 

--------

 

##  使用flex布局

 

```CSS

<style>

body{

    height: 100vh;

}

.container {

    display: flex;

    flex-direction: row;

    height: 200px;

    background-color: lightskyblue;

    word-break: break-all;

}

.left, .right {

    /* 左右固定长度 */

    flex-basis: 100px;

    /* 将增长比和缩小比都设置为 0 ,避免宽度变化 */

    flex-grow: 0;

    flex-shrink: 0;

    background-color: lightslategray;

}

.middle {

    /* 中间自动适应 */

    flex-grow: 1;

    flex-shrink: 1;

    background-color: lightpink;

}

</style>

```


 

--------

 

## css 两种盒模型说一下

 

box-sizing: context-box; 这是W3C盒模型,width = content

box-sizing: border-box;  这是IE盒模型, width = border + padding + content


 

--------


 

## 清除浮动的方式


 

## 额外标签法(在最后一个浮动标签后,新加一个标签,给其设置clear:both;)(不推荐)

 

```HTML

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <meta http-equiv="X-UA-Compatible" content="ie=edge">

    <title>Document</title>

    <style>

    .fahter{

        width: 400px;

        border: 1px solid deeppink;

    }

    .big{

        width: 200px;

        height: 200px;

        background: darkorange;

        float: left;

    }

    .small{

        width: 120px;

        height: 120px;

        background: darkmagenta;

        float: left;

    }

    .footer{

        width: 900px;

        height: 100px;

        background: darkslateblue;

    }

    .clear{

        clear:both;

    }

    </style>

</head>

<body>

    <div class="fahter">

        <div class="big">big</div>

        <div class="small">small</div>

        <div class="clear">额外标签法</div>

    </div>

    <div class="footer"></div>

</body>

</html>

```

 

--------

 

## 父级添加overflow属性(父元素添加overflow:hidden)(不推荐)

 

通过触发BFC方式,实现清除浮动

 

```CSS

.fahter{

    width: 400px;

    border: 1px solid deeppink;

    overflow: hidden;

}

```

 

--------


 

## 使用after伪元素清除浮动(推荐使用)

 

```CSS

.clearfix:after{/*伪元素是行内元素 正常浏览器清除浮动方法*/

    content: "";

    display: block;

    height: 0;

    clear:both;

    visibility: hidden;

}

.clearfix{

    *zoom: 1;/*ie6清除浮动的方式 *号只有IE6-IE7执行,其他浏览器不执行*/

}

<body>

    <div class="fahter clearfix">

        <div class="big">big</div>

        <div class="small">small</div>

        <!--<div class="clear">额外标签法</div>-->

    </div>

    <div class="footer"></div>

</body>

 

```

 

--------

 

## 使用before和after双伪元素清除浮动

 

```CSS

.clearfix:after,.clearfix:before{

content: "";

display: table;

}

.clearfix:after{

clear: both;

}

.clearfix{

*zoom: 1;

}

<div class="fahter clearfix">

<div class="big">big</div>

<div class="small">small</div>

</div>

<div class="footer"></div>

```

 

--------

 

##  CSS实现隔行变色

 

```CSS

tr:nth-child(odd) {

    background-color: #ccc;

}

 

tr:nth-child(even) {

     background-color: #F9F9F9;

}

```

 

--------

 

## 触发BFC的方式有哪些

 

* .根元素(```<html>```)

* .overflow 值不为 visible 的块元素

* .浮动元素(元素的 float 不是 none)

* .绝对定位元素(元素的 position 为 absolute 或 fixed)

* .行内块元素(元素的 display 为 inline-block)

* .弹性元素(display为 flex 或 inline-flex元素的直接子元素)

* .网格元素(display为 grid 或 inline-grid 元素的直接子元素)


 

--------

 

## 移动端布局

 

--------

 

## 固定meta视图

 

```HTML

<meta name="viewport" content="width=750px,user-scalable=no">

```

 

这种写法中,利用meta标签,将视图宽度定位了750px,固定值,也就是ios6的标准,然后css也是基于750px的设计稿进行布局

 

* .优点:前端开发十分快速,都是死值

 

* .缺点:匹配不完全,手机像素高于这个的,显示这个效果,不理想状态;低于这个状态的,不兼容这个状态;

 

   固定宽在做项目的时候由于每个手机屏幕的高度不同需要有一个最小的内容区域

 

--------

 

## rem布局

 

rem是一种基于页面根元素的布局方式

 

当手机屏幕大小改变了,只要改变对应的页面根元素,就可以实现页面的缩小放大。

 

按照750px的设计稿,进行布局的时候 可以在head中添加下图的一段javascript,监听屏幕改变,从而动态改变页面根元素的fonsize大小,对页面进行缩放改变

 

换句话说 设置根元素 font-size=16px 那么 1rem=16px,所以根据屏幕大小 而动态改变 font-size的值,从而做到 移动端rem适配效果。

 

```javascript

<script type="text/javascript">

      (function (win,doc) {

        function setSize() {

          doc.documentElement.style.fontSize=20*document.documentElement.clientWidth/750+'px';

        }

        setSize();

        win.addEventListener('resize',setSize,false)

      })(window,document)

  </script>

```

 

* .优点:引入js后,通过动态修改根元素fontsize,根据手机屏幕 自动缩放

 

* .缺点:head中 就要引入js,会有一点加载影响

 

--------


 

## media媒体查询 动态设置,几个手机的比例,进行对应匹配;

 

media标签,通过媒体查询 按照不同手机的像素宽高不同,进行条件匹配

 

优点:根据不同手机定制不同css,达到完美展示

 

缺点:需要写匹配很多手机,写很多套css,对前端工作量比较大


 

--------

 

## 改变缩放比例,进行布局,类似于第2种方式,2是根据设备宽高对根元素进行font-size的动态改变,4这种方式则是通过改变meta种缩放比例,来进行动态改变页面的

 

这里有一个点需要说明 

 

像素比 window.devicePixelRatio = 设备像素/css像素

 

* .高分辨率:eg每一毫米5像素点,像素点越多,色块越多,页面越清晰

* .低分辨率:eg每一毫米3像素点,像素点越少,色块越少,页面越模糊

* .肉眼看得请不清晰,跟屏幕实际尺寸的大小没关系,而跟单位长度的像素点有关,低分辨率的放在大屏上 也只是一个 放大的不清楚的画面而已

* .对于前端开发来说,设计图是1920或者是1366并不代表是宽度

 

所以当你的网页完全按照设计图使用px来实现的话,有可能出现两种情况:

- html宽度用了设计图1366px,但无奈到了1024的电脑或者手机的时候,就会出现了横向滚动条

- html宽度用了设计图1366px,到了1920的电脑上,就会出现大量的留白

 

--------

 

## 百分比布局

 

将整个页面 按照百分比 进行布局 对于宽度 比较好把握 但是 高度还是需要具体的值 


 

--------

 

## vh vw


 

做一个响应式布局的页面,我们第一时间会想到通过rem单位来实现适配,但是往往还需要内嵌一段脚本去动态计算跟元素大小,有点不方便。

 

我们需要一个单位,但不需要JS和CSS耦合在一?那么就是vw/vh。

 

```CSS

vw=view width(视口宽度)

 

vh=view height(视口高度)

```

 

这两个单位是CSS3引入的,以上称为视口单位允许我们更接近浏览器窗口定义大小。


 

--------

 

## 视口单位(Viewport units)

 

## 什么是视口?

 

A:Peter-Paul Koch(”PPK大神”)提出视口的解释是:在桌面端,视口指的是在桌面端,指的是浏览器的可视区域;而在移动端,它涉及3个视口:Layout Viewport(布局视口),Visual Viewport(视觉视口),Ideal Viewport(理想视口)。

 

视口单位中的“视口”,桌面端指的是浏览器的可视区域;移动端指的就是Viewport中的Layout Viewport。


 

## 单位解释

 

|单位|解释|

|----|----|

|vw:|            1vw = 视口宽度的1%|

 

|vh:            | 1vh = 视口高度的1%|

 

|vmin           | 选取vw和vh中最小的那个|

 

|vmax           |选取vw和vh中最大的那个|



 

比如:浏览器视口尺寸为370px,那么 1vw = 370px * 1% = 6.5px(浏览器会四舍五入向上取7)

 

## vh/vw与%区别在于,

 

单位依赖于

 

|单位|解释|

|----|----|

 

|% |                        元素的祖先元素|

 

|vh/vw |                  视口的尺寸|


 

--------

 

## 仅使用vw作为CSS单位

 

使用 vw 单位作为唯一应用的一种 CSS 单位的这种做法下

 

根据设计稿的尺寸转换为vw单位(SASS函数编译)

 

```CSS

//我们以iPhone 6尺寸作为设计稿基准

 

$vm_base: 375;

 

     @function vm($px) {

 

       @return ($px / 375) * 100vw;

 

}

```

 

无论是文本还是布局高宽、间距等都使用 vw


 

## 最优做法——搭配vw和rem

 

使用vm作为css单位代码量确实减少很多,但是你会发现它是利用视口单位实现,依赖于视口大小而自动缩放,失去了最大最小宽度的限制。

 

所以,我们需要结合rem单位来实现布局,而rem正好可以动态改变根元素大小,做法是:

 

给根元素大小设置vw–动态改变大小。

 

限制根元素font-size的最大最小值,配合body加上最大最小宽度。

 

```CSS

// rem 单位换算:定为 75px 只是方便运算,750px-75px、640-64px、1080px-108px,如此类推

 

$vm_fontsize: 75; // iPhone 6尺寸的根元素大小基准值

 

@function rem($px) {

 

@return ($px / $vm_fontsize ) * 1rem;

 

}

 

// 根元素大小使用 vw 单位

 

$vm_design: 750;

 

html {

 

font-size: ($vm_fontsize / ($vm_design / 2)) * 100vw;

 

// 同时,通过Media Queries 限制根元素最大最小值

 

@media screen and (max-width: 320px) {

 

font-size: 64px;

 

}

 

@media screen and (min-width: 540px) {

 

font-size: 108px;

 

}

 

}

 

// body 也增加最大最小宽度限制,避免默认100%宽度的 block 元素跟随 body 而过大过小

 

body {

 

max-width: 540px;

 

min-width: 320px;

 

}

```

 

--------

 

## vue 中如何获取原生的 DOM 结点

 

```HTML

<div ref="alex"></div>

<p ref="a"></p>

<Home ref="b"></Home>


 

this.$refs.alex    获取原始的DOM对象

this.$refs.a

this.$refs.b       获取组件实例化对象

```

 

--------

 

##  vue NextTick

 

* *在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中

 

* *在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。

 

* *在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。

 

--------

 

## 具体原因在Vue的官方文档中详细解释:

 

Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0)代替。

 

例如,当你设置vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。


 

## 深拷贝 浅拷贝

 

浅拷贝:let newObj = JSON.parse(JSON.stringify(oldObj))

深拷贝:Object.assign({}, obj)


 

## SameSite 

 

--------

 

## CSRF 攻击是什么?

 

Cookie 往往用来存储用户的身份信息,恶意网站可以设法伪造带有正确 Cookie 的 HTTP 请求,这就是 CSRF 攻击。

 

举例来说,用户登陆了银行网站your-bank.com,银行服务器发来了一个 Cookie。

 

```HTML

Set-Cookie:id=a3fWa;

```

 

用户后来又访问了恶意网站malicious.com,上面有一个表单。

 

```HTML

<form action="your-bank.com/transfer" method="POST">

  ...

</form>

```

 

用户一旦被诱骗发送这个表单,银行网站就会收到带有正确 Cookie 的请求。为了防止这种攻击,表单一般都带有一个随机 token,告诉服务器这是真实请求。

 

```HTML

<form action="your-bank.com/transfer" method="POST">

  <input type="hidden" name="token" value="dad3weg34">

  ...

</form>

```

 

这种第三方网站引导发出的 Cookie,就称为第三方 Cookie。它除了用于 CSRF 攻击,还可以用于用户追踪。

 

比如,Facebook 在第三方网站插入一张看不见的图片。

 

```HTML

<img src="facebook.com" style="visibility:hidden;">

```

 

浏览器加载上面代码时,就会向 Facebook 发出带有 Cookie 的请求,从而 Facebook 就会知道你是谁,访问了什么网站。

 

--------

 

## SameSite 属性

 

Cookie 的SameSite属性用来限制第三方 Cookie,从而减少安全风险。

 

它可以设置三个值。

 

*.Strict

*.Lax

* .None


 

## Strict

 

Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。

 

```HTML

Set-Cookie: CookieName=CookieValue; SameSite=Strict;

```

 

这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。

 

## Lax

 

Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。

 

```HTML

Set-Cookie: CookieName=CookieValue; SameSite=Lax;

```

 

导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。

 

|请求类型|  示例| 正常情况|   Lax|

|----|----|----|----|

|链接|    <a href="..."></a>| 发送 Cookie|  发送 Cookie|

|预加载|   <link rel="prerender" href="..."/>| 发送 Cookie|  发送 Cookie|

|GET 表单|    <form method="GET" action="...">|   发送 Cookie|  发送 Cookie|

|POST 表单|   <form method="POST" action="...">|  发送 Cookie|  不发送|

|iframe|    <iframe src="..."></iframe>|    发送 Cookie|  不发送|

|AJAX|  $.get("...")|   发送 Cookie|  不发送|

|Image| <img src="...">|    发送 Cookie|  不发送|


 

设置了Strict或Lax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。

 

##  None

 

Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。

 

下面的设置无效。

```HTML

Set-Cookie: widget_session=abc123; SameSite=None

```

下面的设置有效。

```HTML

Set-Cookie: widget_session=abc123; SameSite=None; Secure

```

 

--------

 

## 变量提升


 

变量提升(Hoisting)被认为是, Javascript中执行上下文 (特别是创建和执行阶段)工作方式的一种认识。在 ECMAScript® 2015 Language Specification 之前的JavaScript文档中找不到变量提升(Hoisting)这个词。不过,需要注意的是,开始时,这个概念可能比较难理解,甚至恼人。

 

例如,从概念的字面意义上说,“变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,但这么说并不准确。实际上变量和函数声明在代码里的位置是不会动的,而是在编译阶段被放入内存中。


 

## 技术范例

JavaScript 在执行任何代码段之前,将函数声明放入内存中的优点之一是,你可以在声明一个函数之前使用该函数。例如:

 

```javascript

/**

* 正确的方式:先声明函数,再调用函数 (最佳实践)

*/

function catName(name) {

    console.log("我的猫名叫 " + name);

}

 

catName("Tigger");

 

/*

以上代码的执行结果是: "我的猫名叫 Tigger"

*/

```

 

上面的代码片按照是你的正常思维(先声明,后调用)去书写的。现在,我们来看看当我们在写这个函数之前调用这个函数会发生什么:

 

```javascript

/**

* 不推荐的方式:先调用函数,再声明函数 

*/

 

catName("Chloe");

 

function catName(name) {

    console.log("我的猫名叫 " + name);

}

 

/*

代码执行的结果是: "我的猫名叫 Chloe"

*/

```


 

即使我们在定义这个函数之前调用它,函数仍然可以工作。这是因为在 JavaScript 中执行上下文的工作方式造成的。

 

变量提升也适用于其他数据类型和变量。变量可以在声明之前进行初始化和使用。但是如果没有初始化,就不能使用它们。

 

译者注: 函数和变量相比,会被优先提升。这意味着函数会被提升到更靠前的位置。

 

--------

 

## 只有声明被提升

 

```javascript

console.log(num); // Returns undefined 

var num;

num = 6;

If you declare the variable after it is used, but initialize it beforehand, it will return the value:

 

num = 6;

console.log(num); // returns 6

var num;

JavaScript 仅提升声明,而不提升初始化。如果你先使用的变量,再声明并初始化它,变量的值将是 undefined。以下两个示例演示了相同的行为。

 

// Example 1 - only y is hoisted

var x = 1;                 // 声明 + 初始化 x

console.log(x + " " + y);  // '1 undefined'

var y = 2;                 // 声明 + 初始化 y

 

// Example 2 - Hoists

var num1 = 3;                   // Declare and initialize num1

num2 = 4;                       // Initialize num2

console.log(num1 + " " + num2); //'3 4'

var num2;                       // Declare num2 for hoisting

 

// Example 3 - Hoists

a = 'Cran';              // Initialize a

b = 'berry';             // Initialize b

console.log(a + "" + b); // 'Cranberry'

var a, b;                // Declare both a & b for hoisting

```

 

--------

## 剪头函数与普通函数区别

 

--------

 

## 外形不同:

 

箭头函数使用箭头定义,普通函数中没有。

 

代码实例如下:

 

```javascript

// 普通函数

function func(){

  // code

}

// 箭头函数

let func=()=>{

  // code

}

```

这种表象的区别实在太明显,无需多做介绍。

 

--------

 

## 箭头函数全都是匿名函数:

 

普通函数可以有匿名函数,也可以有具名函数。

 

```javascript

// 具名函数

function func(){

  // code

}

 

// 匿名函数

let func=function(){

  // code

}

```

 

代码说明如下:

 

(1).第一个函数是具名函数,使用严格的函数声明语法创建。

 

(2).第二个是匿名函数,其实它的本质是声明一个变量并赋值为一个匿名函数对象(函数没有名称)。

 

而箭头函数全都是匿名函数,代码如下:

 

```javascript

// 箭头函数全都是匿名函数

let func=()=>{

  // code

}

```

--------

 

## 箭头函数不能用于构造函数:

 

普通函数可以用于构造函数,以此创建对象实例。

 

代码实例如下:

 

```javascript

function Antzone(webName,age){

   this.webName=webName;

   this.age=age;

}

let antzone=new Antzone("蚂蚁部落",5);

console.log(antzone.webName);

```

 

代码运行效果截图如下:

 

```a:3:{s:3:\"pic\";s:43:\"portal/201810/12/214600csy5zo9sa15zbuks.png\";s:5:\"thumb\";s:0:\"\";s:6:\"remote\";N;}```


 

Antzone被用作构造函数,通过它可以创建对象实例。

 

但是箭头函数并不能用作构造函数。

 

--------

 

## 箭头函数中this的指向不同:

 

this一直是让初学者比较头疼的概念。

 

在普通函数中,this总是指向调用它的对象或者,如果用作构造函数,它指向创建的对象实例。


 

在箭头函数中,this的指向发生了本质变化

 

--------

 

## 箭头函数不具有arguments对象:

 

每一个普通函数调用后都具有一个arguments对象,用来存储实际传递的参数。

 

但是箭头函数并没有此对象。

 

--------


 

## 其他区别:

 

(1).箭头函数不能Generator函数。

 

(2).箭头函数不具有prototype原型对象。

 

(3).箭头函数不具有super。

 

(4).箭头函数不具有new.target。

 

--------

 

## 组件中的data为什么是个函数

 

我们先假设将data作为一个对象:

 

我们前面说组件是可以被复用的,那么注册了一个组件本质上就是创建了一个组件构造器的引用,而真正当我们使用组件的时候才会去将组件实例化,

 

```HTML

// 创建一个组件

var Component= function() {

}

Component.prototype.data = {

  a: 1,

  b: 2

}

 

// 使用组件

var component1 = new Component()

var component2 = new Component()

component1.data.b = 3

component2.data.b   // 3

```

 

我们可以发现当我们使用组件的时候,虽然data是在构造器的原型链上被创建的,但是实例化的component1和component2确是共享同样的data对象,当你修改一个属性的时候,data也会发生改变,这明显不是我们想要的效果。

 

```javascript

var Component= function() {

}

Component.prototype.data = function() {

  return {

     a: 1,

     b: 2

  }

}

 

// 使用组件

var component1 = new Component()

var component2 = new Component()

component1.data.b = 3

component2.data.b   // 2

```

 

当我们的data是一个函数的时候,每一个实例的data属性都是独立的,不会相互影响了。你现在知道为什么vue组件的data必须是函数了吧。这都是因为js本身的特性带来的,跟vue本身设计无关

 

js本身的面向对象编程也是基于原型链和构造函数,应该会注意原型链上添加一般都是一个函数方法而不会去添加一个对象了


 

--------

 

##  给定一个包含大写英文字母和数字的句子,找出这个句子所包含的最大的十六进制整数,返回这个整数的值。数据保证该整数在int表示范围内。例如:"012345BZ16" ,最大数“12345B”对应十进制为1193051。

 

```javascript

function solve( s ) {

    // write code here

  // 记录最大的字符串

  let maxstr = ''

  // 记录最大的值

  let maxnum = 0

  // 全局匹配符合要求的字符串

  let arr = s.match(/[0-9A-F]+/g)

  // 记录当前执行的字符串的长度

  let templen = 0

  for(let i = 0; i < arr.length; i++){

      // 这个if是用来过滤比之前运行过的字符串更短的字符串

      // 短的字符串肯定比之前的数字小

      // 当然还可以直接比较字符串的大小,然后再进行运算

    if(arr[i].length >= templen){

      templen = arr[i].length

      let temp = parseInt(arr[i],16)

      if(temp > maxnum){

        maxstr = arr[i]

        maxnum = temp

      }

    }

  }

  return maxnum

}

```

 

--------

 

## CSS画三角形

 

```CSS

.trangle {

    width: 0px;                           /*设置宽高为0,所以div的内容为空,从才能形成三角形尖角*/

    height: 0px;

    border-bottom: 200px solid #00a3af;

    border-left: 200px solid transparent;    /*transparent 表示透明*/

    border-right: 200px solid transparent;

}

```

 

--------

 

## CSS画扇形

 

```CSS

.circle{

    border-radius: 50% 0 0 0;

    width: 200px;

    height: 200px;

    background-color: red;

}

```

 

--------

 

## requestAnimationFrame

 

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

 

注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()

当你准备更新动画时你应该调用此方法。这将使浏览器在下一次重绘之前调用你传入给该方法的动画函数(即你的回调函数)。回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的```<iframe>``` 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。

 

回调函数会被传入DOMHighResTimeStamp参数,DOMHighResTimeStamp指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。在同一个帧中的多个回调函数,它们每一个都会接受到一个相同的时间戳,即使在计算上一个回调函数的工作负载期间已经消耗了一些时间。该时间戳是一个十进制数,单位毫秒,最小精度为1ms(1000μs)。

 

## 语法

window.requestAnimationFrame(callback);

 

## 参数

 

callback

 

下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻。

 

## 返回值

 

一个 long 整数,请求 ID ,是回调列表中唯一的标识。是个非零值,没别的意义。你可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。

 

## 范例

 

```javascript

var start = null;

var element = document.getElementById('SomeElementYouWantToAnimate');

element.style.position = 'absolute';

 

function step(timestamp) {

  if (!start) start = timestamp;

  var progress = timestamp - start;

  element.style.left = Math.min(progress / 10, 200) + 'px';

  if (progress < 2000) {

    window.requestAnimationFrame(step);

  }

}

 

window.requestAnimationFrame(step);

```

 

--------

## 1000个button需要注册点击事件,怎么实现

 

```javascript

<div class="btns" style="width:60px;margin:20px"></div>

function bindEvent() {

    for (var i = 0; i < 100; i++) {

        var btn = document.createElement('button');

        btn.innerText = "btn" + i;

        document.getElementsByClassName('btns')[0].appendChild(btn);

    }

    document.getElementsByClassName('btns')[0].addEventListener('click', function (e) {

        var e = e || window.event;

        var target = e.target || e.srcElement;

        if (target.tagName == 'BUTTON') {

            alert(target.innerText);

        }

    }, false)

}

 

```

 

使用事件委托, 利用了事件的冒泡机制. 

 

针对btn绑定事件就一定要判断点击的target是不是btn, 如果做这一步, 点击被委托的父容器其他地方也会触发事件.

 

不要阻止冒泡事件!

 

(10000只是形容数量较多, 不要当真)


 

--------

 

##  轮播图的实现原理


 

--------

 

##  页面生命周期

 

页面生命周期:DOMContentLoaded, load, beforeunload, unload

 

HTML页面的生命周期有以下三个重要事件:

 

* .DOMContentLoaded — 浏览器已经完全加载了HTML,DOM树已经构建完毕,但是像是 <img> 和样式表等外部资源可能并没有下载完毕。

* .load — 浏览器已经加载了所有的资源(图像,样式表等)。

* .beforeunload/unload -- 当用户离开页面的时候触发。


 

--------

 

## js的错误监控机制


 

## 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值