js陷阱

Posted by Qz on February 24, 2018

“Yeah It’s on. ”

正文

网页链接

数字排序

Javascript 的sort()函数在默认情况下使用字母数字(字符串Unicode码点)排序。

所以[1,2,5,10].sort() 会输出 [1, 10, 2, 5]. 要正确的排序一个数组, 你可以用 [1,2,5,10].sort((a, b) => a — b) 很简单的解决方案, 前提是你得知道有这么个坑

new Date()

  • 没有参数: 返回当前时间
  • 一个参数 x: 返回1970年1月1日 + x 毫秒。 了解 Unix 的人知道为什么。
  • new Date(1, 1, 1) 返回 1901, 二月 , 1号。因为,第一个参数表示1900年加1年,第二个参数表示这一年的第二个月(因此是二月) — 脑回路正常的人会从1开始索引 — ,第三个参数很明显是这个月的第一天
  • new Date(2016, 1, 1) 不会给1900年加上2016。它仅代表2016年。

Date.now() // 获取当前时间的毫秒数

  • var dt = new Date() dt.getTime() // 获取毫秒数
  • dt.getFullYear() // 年
  • dt.getMonth() // 月 (0-11) 特别注意这里 实际应用的时候记得+1
  • dt.getDate() //日 (0 - 31 )
  • dt.getHours() //小时(0 - 23)
  • dt.getMinutes() //分钟(0 - 59)
  • dt.getSeconds() //秒(0 - 59)

Replace

let s = 'bob'
const replaced = s.replace('b','l')
console.log(replaced === 'lob') //true
console.log(s === 'bob')  //true

replace 只会替换第一个匹配的字符串

如果你想替换所有匹配的字符串,你可以使用带/g标志的正则表达式 :

“bob”.replace(/b/g, ‘l’) === ‘lol’ // 替换所有匹配的字符串

比较的时候要注意

// These are ok
'abc' === 'abc' // true
1 === 1         // true
// These are not
[1,2,3] === [1,2,3] // false
{a: 1} === {a: 1}   // false
{} === {}           // false

闭包

const Greeters = []
for (var i = 0; i < 10; i++) {
    Greeters.push(function () {
        return console.log(i)
    })
}
Greeters[0]() // 10
Greeters[1]() // 10
Greeters[2]() // 10

原因: i一直被引用 Greeters[0]只是一个函数

function (){
				return console.log(i)
			}

Greeters0这时候才会调用,此时i已经变为10了

怎样修改让它输出 0, 1, 2… ?

用let替代var解决 let和var的不同在于作用域。var的作用域是最近的函数块,let的作用域是最近的封闭块,封闭块可以小于函数块(如果不在任何块中,则let和var都是全局的)。

替代方法: 用 bind: Greeters.push(console.log.bind(null, i))

hasOwnProperty

var item
for(item in f){
  // 高级浏览器已经在 for in 中屏蔽了来自原型的属性
  // 但是为了程序的健壮性 建议加上
  if(f.hasOwnProperty(item)) {
     console.log(item)
  }
}

Function.apply.bind()与Function.apply.bind()

https://blog.csdn.net/weixin_37787381/article/details/81509361

Promise.resolve([10,20]).then(Function.apply.bind(function(x, y){
   console.log(x, y);
}, null));     // 10,20

Function.apply.bind(…)是个什么操作?

var sum = function(x, y) {
   console.log(x, y);
}
var foo = Function.apply.bind(sum, null);
foo([10, 20]);   // 10, 20

这里我们有一个函数sum,通过Function.apply.bind(sum, null)我们创建了一个新的函数foo(…)。

我们一步步分析Function.apply.bind(sum, null)这段代码。 sum.apply(null, [10, 20])这句代码将第一个参数置为null,第二个参数是一个数组,用于拆开后作为sum的最终参数。

对象引用的一个注意点

  let option = {
    aaa: {
      a: "a",
      b: "b"
    },
    bbb: "bbb"
  }


  let origin = option["aaa"] || {}
  origin.a = "ccc"
  console.log(origin, option)
  
  
  // 输出结果
   {a: "ccc", b: "b"}
   
   {
    aaa: {a: "ccc", b: "b"}
    bbb: "bbb"
  }

这里origin能正确引用到option[“aaa”]所以没有问题

当option.aaa 不存在时

  let option = {
    aaa: {
      a: "a",
      b: "b"
    },
    bbb: "bbb"
  }


  let origin = option["aaa"] || {}
  origin.a = "ccc"
  console.log(origin, option)
  
  
  // 输出结果
   {a: "ccc"} 
   
   {bbb: "bbb"}

origin无法正确引用到option[“aaa”],这是一个很严重的问题

那么,如何在option.aaa为空的情况下,使得origin可以正确引用到option[“aaa”]

有一个高级的写法,经常出现在框架源码中

  let option = {
    bbb: "bbb"
  }
  // 技巧:
  let origin = option["aaa"] || (option["aaa"] = {})
  origin.a = "ccc"
  console.log(origin, option)
  
  // 输出结果
  {a: "ccc"}
  {
    aaa: {a: "ccc"}
    bbb: "bbb"
  }

实现一个简单watch

最近写小程序框架需要实现一个watch,于是就开始了手撸Object.defineProperty

function defineReactive(obj, key, customSetter) {
  let val = obj[key]
  Object.defineProperty(obj, key, {
    set(newVal) {
      // eslint-disable-line
      if (newVal === val || (newVal !== newVal && val !== val)) {
        return
      }
      customSetter(newVal, val)
    },
  })
}
watch: {
    bannerList(newVal, oldVal) {
      console.warn("111", "banner 111 watch", this, newVal, oldVal)
    },
  },

这里customSetter就是watch里的bannerList

存在一个非常严重的问题bannerList(newVal, oldVal)的oldVal一直不变

那么如何一个完善的watch要可以同时监听到旧值(oldVal)和新值(newVal)呢?

只需要增加一行代码。

function defineReactive(obj, key, customSetter) {
  let val = obj[key]
  Object.defineProperty(obj, key, {
    set(newVal) {
      // eslint-disable-line
      if (newVal === val || (newVal !== newVal && val !== val)) {
        return
      }
      customSetter(newVal, val)
      val = newVal
    },
  })
}

val = newVal

也是一个小技巧,记录一下

条件中的递减

const _retry = (info, num) => {
  if (!num) {
    return reject(info);
  }

  setTimeout(() => {
    fn(...args).then(
      (res) => resolve(res),
      // (rej) => _retry(rej, num--) 这种写法传进_retry的num还是原来的num
      (rej) => _retry(rej, --num) // 正确写法
    );
  }, interval);
};

总结:在递归函数中递减 正确写法是 –num 而不是 num–

offsetWidth

offsetWidth实际获取的是盒模型(width+border + padding)

offsetX 触发到子元素

应该怎么解决?

解决方案1

在事件捕获阶段处理,阻止冒泡。 e.stopPropagation(); e.preventDefault();


解决方案2

判断元素 e.target === 父元素 时候获取


解决方案3(最好)

不使用offsetX

用event.pageX - xxx.getBoundingClientRect().left

在jquery中可以用event.pageX - xxx.offset().left