本篇绪论
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))
网友评论