博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
悄悄地说一个bug
阅读量:6189 次
发布时间:2019-06-21

本文共 5650 字,大约阅读时间需要 18 分钟。

前言

underscore.js源码分析第四篇,前三篇地址分别是,如果你对这个系列感兴趣,欢迎点击watch,随时关注动态。

逗我呢?哥!你要说什么bug,什么bug,什么bug,我最讨厌bug。去他妹的bug。

客观别急,今天真的是要说一个bug,也许你早已知晓,也许你时常躺枪于他手,悄悄地,我们慢慢开始。

for in 遍历对象属性时存在bug

for in 遍历对象属性时存在bug

for in 遍历对象属性时存在bug

使用for in去遍历一个对象俺们再熟悉不过了,经常干这种事,那他到底可以遍历一个对象哪些类型的属性呢? 长得帅的还是看起来美美的,瞎说,它能够遍历的是对象身上那些可枚举标志([[Enumerable]])为true的属性。

  1. 对于通过直接的赋值和属性初始化的属性,该标识值默认为即为 true

  2. 对于通过 Object.defineProperty 等定义的属性,该标识值默认为 false

举个例子哪些属性可以被枚举

let Person = function (name, sex) {  this.name = name  this.sex = sex}Person.prototype = {  constructor: Person,  showName () {    console.log(this.name)  },  showSex () {    console.log(this.sex)  }}Person.wrap = {  sayHi () {    console.log('hi')  }}var p1 = new Person('qianlongo', 'sex')p1.sayBye = () => {  console.log('bye')}p1.toString = () => {  console.log('string')}Object.defineProperty(p1, 'info', {  enumerable: false,  configurable: false,  writable: false,  value: 'feDev'});Ïfor (var key in p1) {  console.log(key)}// name// sex// sayBye// constructor// showName// showSex// toString
  1. 可以看到我们手动地用defineProperty,给某个对象设置属性时,enumerable为false此时该属性是不可枚举的

  2. Person继承自Object构造函数,但是for in并没有枚举出Object原型上的一些方法

  3. 手动地覆盖对象原型上面的方法toString也是可枚举的

如何判断一个对象的属性是可枚举的

方式其实很简单,使用原生js提供的Object.propertyIsEnumerable来判断

let obj = {  name: 'qianlongo'}let obj2 = {  name: 'qianlongo2',  toString () {    return this.name  }}obj.propertyIsEnumerable('name') // trueobj.propertyIsEnumerable('toString') // falseobj2.propertyIsEnumerable('name') // trueobj2.propertyIsEnumerable('toString') // true

为什么obj判断toString为不可枚举属性,而obj2就是可枚举的了呢?原因很简单,obj2将toString重写了,而一个对象自身直接赋值的属性是可被枚举的

说了这么多,接下来我们来看一下下划线中涉及到遍历的部分对象方法,come on!!!

_.has(object, key)

判断对象obejct是否包含key属性

平时你可能经常这样去判断一个对象是否包含某个属性

if (obj && obj.key) {  // xxx}

但是这样做有缺陷,比如某个属性其对应的值为0,null,false,''空字符串呢?这样明明obj有以下对应的属性,却因为属性值为而通过不了验证

let obj = {  name: '',  sex: 0,  handsomeBoy: false,  timer: null}

所以我们可以采用下划线中的这种方式

源码

var hasOwnProperty = ObjProto.hasOwnProperty;_.has = function(obj, key) {  return obj != null && hasOwnProperty.call(obj, key);};

_.keys(object)

获取object对象所有的属性名称。

使用示例

let obj = {  name: 'qianlongo',  sex: 'boy'}let keys = _.keys(obj)// ["name", "sex"]

源码

_.keys = function(obj) {  // 如果obj不是object类型直接返回空数组  if (!_.isObject(obj)) return [];  // 如果浏览器支持原生的keys方法,则使用原生的keys  if (nativeKeys) return nativeKeys(obj);  var keys = [];  // 注意这里1、for in会遍历原型上的键,所以用_.has来确保读取的只是对象本身的属性  for (var key in obj) if (_.has(obj, key)) keys.push(key);  // Ahem, IE < 9.  // 这里主要处理ie9以下的浏览器的bug,会将对象上一些本该枚举的属性认为不可枚举,详细可以看collectNonEnumProps分析  if (hasEnumBug) collectNonEnumProps(obj, keys);   return keys;};

collectNonEnumProps函数分析

该函数为下划线中的内部函数一枚,专门处理ie9以下的枚举bug问题,for in到底有啥bug,终于可以说出来了。

简单地说就是如果对象将其原型上的类似toString的方法覆盖了的话,那么我们认为toString就是可枚举的了,但是在ie9以下的浏览器中还是认为是不可以枚举的,又是万恶的ie

源码

// 判断浏览器是否存在枚举bug,如果有,在取反操作前会返回falsevar hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); // 所有需要处理的可能存在枚举问题的属性var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',                    'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; // 处理ie9以下的一个枚举bug                      function collectNonEnumProps(obj, keys) {  var nonEnumIdx = nonEnumerableProps.length;  var constructor = obj.constructor;  // 读取obj的原型  var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;    // 这里我有个疑问,对于constructor属性为什么要单独处理?  // Constructor is a special case.  var prop = 'constructor';   if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);  while (nonEnumIdx--) {    prop = nonEnumerableProps[nonEnumIdx];    // nonEnumerableProps中的属性出现在obj中,并且和原型中的同名方法不等,再者keys中不存在该属性,就添加进去    if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {      keys.push(prop);    }  }}

代码看起来并不复杂,但是有一个小疑问,对于constructor属性为什么要单独处理呢?各个看官,如果知晓,请教我啊

_.allKeys(object)

获取object中所有的属性,包括原型上的。

举个简单的例子说明

let Person = function (name, sex) {  this.name = name  this.sex = sex}Person.prototype = {  constructor: Person,  showName () {    console.log(this.name)  }}let p = new Person('qianlongo', 'boy')_.keys(p)// ["name", "sex"] 只包括自身的属性_.allKeys(p)// ["name", "sex", "constructor", "showName"] 还包括原型上的属性

接下来看下源码是怎么干的

源码

// 获取对象obj的所有的键// 与keys不同,这里包括继承来的key// Retrieve all the property names of an object._.allKeys = function(obj) {  if (!_.isObject(obj)) return [];  var keys = [];  // 直接读遍历取到的key,包括原型上的  for (var key in obj) keys.push(key);   // Ahem, IE < 9.  if (hasEnumBug) collectNonEnumProps(obj, keys); // 同样处理一下有枚举问题的浏览器  return keys;};

可以看到和_.keys的唯一的不同就在于遍历obj的时候有没有用hasOwnProperty去判断

_.values()

返回object对象所有的属性值。

使用案例

let obj = {  name: 'qianlongo',  sex: 'boy'}_.values(obj)// ["qianlongo", "boy"]

源码

// Retrieve the values of an object's properties._.values = function(obj) {  // 用到了前面已经写好的keys函数,所以values认为获取的属性值,不包括原型  var keys = _.keys(obj);  var length = keys.length;  var values = Array(length);  for (var i = 0; i < length; i++) {    values[i] = obj[keys[i]];  }  return values;};

_.invert(object)

返回一个object副本,使其键(keys)和值(values)对换。

使用案例

let obj = {  name: 'qianlongo',  secName: 'qianlongo',  age: 100}_.invert(obj)// {100: "age", qianlongo: "secName"}

注意哟,如果对象中有些属性值是相等的,那么翻转过来的对象其key取最后一个

源码

_.invert = function(obj) {  var result = {};  // 所以也只是取对象本身的属性  var keys = _.keys(obj);   for (var i = 0, length = keys.length; i < length; i++) {    // 值为key,key为值,如果有值相等,后面的覆盖前面的    result[obj[keys[i]]] = keys[i];   }  return result;};

_.functions(object)

返回一个对象里所有的方法名, 而且是已经排序的(注意这里包括原型上的属性)

源码

_.functions = _.methods = function(obj) {  var names = [];  for (var key in obj) {    // 是函数,就装载进去    if (_.isFunction(obj[key])) names.push(key);  }  return names.sort(); // 最后返回经过排序的数组};

结尾

夜深人静,悄悄地说一个bug这个鬼故事讲完了,各位good night。

转载地址:http://ygoda.baihongyu.com/

你可能感兴趣的文章
车辆横向约束
查看>>
树形DP水题集合 6/18
查看>>
Java:泛型擦除
查看>>
Windows连接Oracle数据库
查看>>
Python 语法外的常用操作
查看>>
Java API 设计模式之迭代器(Iterator)
查看>>
DNN 安装
查看>>
【JAVA】while 循环要注意的点
查看>>
EntityFramework 6.x和EntityFramework Core插入数据探讨
查看>>
有哪些LabVIEW快捷键让你相见恨晚
查看>>
mysql数据库查询语句
查看>>
scrapy 学习
查看>>
spring国际化,基于数据库的信息配置
查看>>
R语言基于Keras的小数据集图像分类
查看>>
Jboss7 部署EJB3 简明教程
查看>>
Software Engineering: 2. Project management
查看>>
登陆界面
查看>>
返回数据到前一个activity
查看>>
防御性编程技术 (转)
查看>>
Android获取网络数据进行GZIP解压
查看>>