美文网首页
面试题【Day12】

面试题【Day12】

作者: 小王子__ | 来源:发表于2021-09-07 18:26 被阅读0次

本篇绪论

1,this指向相关问题

2,移动端1px问题及解决方案

3,防抖、节流

1,this指向相关问题

每次面试前,都会查度娘,确保自己有十足的底气,但是面试完结果总是不尽人意。

this总是(非严格模式下)指向一个对象,具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而不是函数被声明时的环境

1, 作为对象的方法调用

当函数作为对象的方法被调用的时候,this指向该对象

var obj = {
  name: 'xiaowang',
  sayName () {
    console.log(this) // {name: 'xiaowang', sayName(){}}
    console.log(this === obj) // true
    console.log(this.name) // xiaowang
  }
}
obj.sayName()

当函数作为对象的方法被调用的时候,this指向该对象

2,作为普通函数调用
window.name = 'xiaowang'
var getName = function () {
  console.log(this)  // window
  console.log(this.name)  // xiaowang
}
getName()

当作为普通函数被调用时,this总是指向全局对象(在浏览器中,通常是window对象)

获取下面的迷惑性的代码

window.name = 'xiaowang'
var obj = {
  name: 'xiaoma',
  getName () {
    console.log(this) // window
    console.log(this.name) // xiaowang
  }
}
var foo = obj.getName
foo()  // foo在window下定义的,所以它的宿主环境是window不是obj

在ES5的严格模式下,this被规定不会指向全局对象,而是undefined

3,构造函数调用

大多数JS的函数都可以称为构造函数,它和普通函数的区别在于被调用的方式不同

当new运算符调用函数的时候,总是返回一个对象,this指向这个对象

var getName = function () {
  this.name = 'xiaowang'
}
var p1 = new getName()
console.log(p1.name)  // xiaowang

但是如果显式的返回一个object对象,最终会返回这个对象

var getName = function () {
  this.name = 'xiaowang'
  return {
    name: 'xiaoma'
  }
}
var p1 = new getName()
console.log(p1.name)  // xiaoma
4,call、apply调用

call、apply可以改变this的指向

var obj = {
  name: 'xiaowang',
  getName () {
    return this.name
  }
}
var obj1 = {
  name: 'xiaoma'
}
console.log(obj.getName()) // xiaowang
console.log(obj.getName.call(obj1)) // xiaoma
console.log(obj.getName.apply(obj1)) // xiaoma
5,箭头函数

剪头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this

this.name = 'xiaowang'
var obj = {
  name: 'xiaoma',
  getName: () => {
    console.log(this.name)
  }
}
obj.getName()  // xiaowang

常见的坑:

var obj = {
  name: 'xiaowang',
  getName (v) {
    console.log(this.name)
  }
}
obj.getName()
var getName2 = obj.getName
getName2()  // 这时候getName2作为普通函数被调用的时候,this指向全局对象-window
6,call、apply

call、apply的区别在于传参的格式不一样

  • apply接受两个参数
    • 第一个参数执行了函数体内this对象的指向
    • 第二个参数为一个带下标的参数集合(可以是数组或类数组)
  • call接受的参数不固定
    • 第一个参数指定了函数体内this对象的指向
    • 第二个参数及以后的参数为函数调用的参数
      因为在所有(非箭头)函数中都可以通过arguments对象在函数中引用函数的参数,此对象包含传递给函数的每个参数,它本身就是一个类数组,我们apply在实际使用中更常见一些

call是包装在apply上的语法糖,如果我们明确的知道参数的数量,并希望展示它们,可以使用call

当call、apply的第一个参数为null,函数体内的this会默认指向宿主对象,在浏览器中则是window

Math.max.apply(null, [1,2,3,4,5]) // 5

2,移动端1px问题及解决方案

web移动端开发中,UI设计稿中边框为1px,代码中border: 1px,测试会发现在某些机型上,1px会比较粗,首先我们先看下像素

device pixels(设备像素):设备像素是设备出厂时候设定好的,一旦设定好就不会改变,官方在产品说明书上写的1920 * 1080就是说的物理像素。

css pixels(css像素): 是css、js所理解的像素单位,和设备像素没必然关系,比如windows的桌面显示器,当修改显示器的硬件分辨率,比如把1920改为1024分辨率,你会发现网页里的图形和字体变的很大,同样的显示器,原来能显示全部网页,现在只能显示一半宽度,也就是说css像素变大了。所以,css像素是可以被硬件和软件任意调节的单位。我们通过css、js代码设置的像素都是逻辑像素。

1,媒体查询利用设备像素比缩放,设置小数像素
.test {border: 1px solid #000;}
@media screen and (-webkit-min-device-pixel-ratio: 2) {
  .test {border: .5px solid #000;}
}
@media screen and (-webkit-min-device-pixel-ratio: 3) {
  .test {border: .3333333px solid #000;}
}
2,操作viewport + rem + js修改缩放
const viewport = document.querySelector('meta[name=viewport]')
const pdr = window.devicePixelRatio
if (pdr == 1) {
  viewport.setAttribute('content', 'width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no')
} else if (pdr == 2) {
  viewport.setAttribute('content', 'width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no')
} else if (pdr == 3) {
  viewport.setAttribute('content', 'width=device-width, initial-scale=0.333333333, maximum-scale=0.333333333, minimum-scale=0.333333333, user-scalable=no')
}
const ele = document.documentElement
const fontSize = (ele.clientWidth/750)*100+'px'
ele.style.fontSize = fontSize
3, 伪元素 + scale

这种方法的原理:把元素的border去掉,用::before、::after重做border,原来的元素相对定位,新做的border绝对,定位使用transform的scale把线条高度缩小一半,新边框相当于0.5px

.test {
  position: relative;
  border: 0;
}
.test:after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  background-color: #000;
  width: 100%;
  height: 1px;
  transform: scaleY(0.5);
  transform-origin: 0 0;
}

还有其他方案,可自行百度

3,防抖、节流

防抖节流针对的是快速连续、不可控的高频触发问题的解决方案。目的就是降低一个函数的触发频率,以提高性能或避免资源浪费。比如鼠标移动事件、滚动条滚动事件、窗口改变等瞬间的操作会导致这些事件会被高频触发。如果事件的回调函数复杂,会导致响应跟不上触发出现页面卡顿、假死的现象。再如搜索时,绑定onkeyup事件会发请求,用户输入过程中会导致发出大量的请求。

如果事件触发是高频但是有停顿的,可以选择使用防抖

事件连续不断高频触发的时候,只能使用节流,因为防抖可能会导致动作只被执行一次,界面出现跳跃。

防抖(debounce),简单说就是防止抖动

策略是当事件被触发的时候,设定一个周期延迟执行动作,如果期间又被触发,则重新设定周期,直到周期结束,执行动作。特点是当事件快速连续不断触发的时候,动作只会执行一次。

const debounce = (fn, wait, immediate=false) => {
  let timer, timeStemp = 0
  let context, args
  const run = (timerInterval) => {
    timer = setTimeout(_ => {
      let now = new Date().getTime()
      let interval= now - startTimeStamp
      if(interval < timerInterval){
        startTimeStamp = now
        run(wait - interval)
      }else{
        if(!immediate){
          fn.apply(context,args)
        }
        clearTimeout(timer)
        timer = null
      }
    }, timerInterval)
  }
  return function () {
    context = this
    args = arguments
    let now = new Date().getTime()
    startTimeStamp = now
    if (!timer) {
      if (immediate) {
        fn.apply(context, args)
      }
      run(wait)
    }
  }
}
function sayHi (e) {
  console.log(e.target.innerWidth)
}
window.addEventListener('resize', debounce(sayHi, 1000))
节流

节流的策略是固定周期内,只执行一次动作,如果有新事件触发,不执行。周期结束后,又有事件触发,开始新的周期。节流特点:在连续高频触发事件的时候,动作会被定期执行,响应平滑

const throttle = (fn, wait, immediate) => {
  let timer, timeStamp=0
  let context, args
  let run = () => {
    timer = setTimeout(()=>{
      if(!immediate){
        fn.apply(context, args)
      }
      clearTimeout(timer)
      timer = null
    }, wait)
  }
  return function () {
    context = this
    args = arguments
    if(!timer){
      if(immediate){
        fn.apply(context, args)
      }
      run()
    }
  }
}
function sayHi (e) {
  console.log(e.target.innerWidth)
}
window.addEventListener('resize', throttle(sayHi, 1000))

相关文章

网友评论

      本文标题:面试题【Day12】

      本文链接:https://www.haomeiwen.com/subject/qcypwltx.html