GithubHelp home page GithubHelp logo

blog's People

Contributors

zhaozy93 avatar

Stargazers

 avatar

Watchers

 avatar  avatar

blog's Issues

width、clientWidth、offsetHeight、

简单谈一谈在页面中盒模型的各种宽度和高度,有不足或理解不准确的地方还请见谅。

盒模型的设置过程

css方式

  • 1、 外部样式文件
  • 2、 行内style属性
  • 3、 写在style标签内

js代码动态设置

获取元素的盒模型

常用的四种过去元素盒模型宽高的方法。

div.style.width

没有找到具体解释,但它只指代元素的行内css属性

div.clientWidth

The Element.clientWidth property is zero for elements with no CSS or inline layout boxes, otherwise it's the inner width of an element in pixels. It includes padding but not the vertical scrollbar (if present, if rendered), border or margin.

元素的宽度包含padding,但不包含滚动轴、边框和margin

div.offsetWidth

The HTMLElement.offsetWidth read-only property returns the layout width of an element. Typically, an element's offsetWidth is a measurement which includes the element borders, the element horizontal padding, the element vertical scrollbar (if present, if rendered) and the element CSS width.

只读属性,包含边框、padding、滚动轴和width

window.getComputedStyle(withScale).width

The Window.getComputedStyle() method gives the values of all the CSS properties of an element after applying the active stylesheets and resolving any basic computation those values may contain.

方法会在一个元素应用完有效样式且计算完所有属性的基本值之后给出所有 CSS 属性的值。

测试各种结果

如果麻烦可以直接看最后的Conclusion部分

通过在页面设置一个div,我们来不断改变他的css来查看各项方法获取的最终结果。

然后通过下面这段代码来查看各项方法的结果

`width:${withScale.style.width}, clientWidth: ${withScale.clientWidth}, offsetWidth: ${withScale.offsetWidth}, getcomputed: ${window.getComputedStyle(withScale).width}`

step1 width

.div{
  width:100px;
  height:100px;
  background:red;
}
chrome: width:, clientWidth: 100, offsetWidth: 100, getcomputed: 100px
firefox: width:, clientWidth: 100, offsetWidth: 100, getcomputed: 100px

step2 width + border

.div{
  width:100px;
  height:100px;
  background:red;
  border:1px solid green;
}
chrome: width:, clientWidth: 100, offsetWidth: 102, getcomputed: 100px
firefox: width:, clientWidth: 100, offsetWidth: 102, getcomputed: 100px

step3 width + padding

.div{
  width:100px;
  height:100px;
  background:red;
  padding: 10px;
}
chrome: width:, clientWidth: 120, offsetWidth: 120, getcomputed: 100px
firefox: width:, clientWidth: 120, offsetWidth: 120, getcomputed: 100px

step4 width + margin

.div{
  width:100px;
  height:100px;
  background:red;
  margin: 10px;
}
chrome: width:, clientWidth: 100, offsetWidth: 100, getcomputed: 100px
firfox: width:, clientWidth: 100, offsetWidth: 100, getcomputed: 100px

step5 width + border + border-box

.div{
  width:100px;
  height:100px;
  background:red;
  border: 1px solid green;
  box-sizing: border-box;
}
chrome: width:, clientWidth: 98, offsetWidth: 100, getcomputed: 100px
firefox: width:, clientWidth: 98, offsetWidth: 100, getcomputed: 100px

step6 width + padding + border-box

.div{
  width:100px;
  height:100px;
  background:red;
  padding: 10px;
  box-sizing: border-box;
}
chrome: width:, clientWidth: 100, offsetWidth: 100, getcomputed: 100px
firefox: width:, clientWidth: 100, offsetWidth: 100, getcomputed: 100px

step7 width + margin + border-box

.div{
  width:100px;
  height:100px;
  background:red;
  margin: 10px;
  box-sizing: border-box;
}
chrome: width:, clientWidth: 100, offsetWidth: 100, getcomputed: 100px
firefox: width:, clientWidth: 100, offsetWidth: 100, getcomputed: 100px

step8 inlineWidth

.div{
  width:100px;
  height:100px;
  background:red;
}
<div style="width:200px">
chrome: width:200px, clientWidth: 200, offsetWidth: 200, getcomputed: 200px
firefox: width:200px, clientWidth: 200, offsetWidth: 200, getcomputed: 200px

summary 1

从上面8个测试中可以发现

1、div.style.width只能识别元素行内的style属性,并无法识别外部的style文件与style标签内的属性设置

2、clientWidth会包含real-width + padding,但会受到box-sizing的影响,因为border-content时,width属性为 real-width + padding + border。

3、offsetWidth包含了 real-width + padding + border, 因此不受box-sizing的影响

4、getcomputed获取后的属性目前没有发现什么规律,只是与最终生效的那行width设置值相同而已。

5、style.width 和 getcomputed会带有px单位,其与两个方法返回数值。

step9 width + zoom

.div{
  width:100px;
  height:100px;
  background:red;
  zoom:200%;
}
chrome: width:, clientWidth: 100, offsetWidth: 100, getcomputed: 100px 
firefoe: firefox不支持zoom属性

step10 width + border + padding + margin + zoom

.div{
  width:100px;
  height:100px;
  background:red;
  border: 1px solid green;
  padding: 10px;
  margin: 10px;
  zoom: 200%;
}
chrome: width:, clientWidth: 120, offsetWidth: 122, getcomputed: 100px
firefoe: firefox不支持zoom属性

summary 2

6、 zoom对于获取盒模型计算的宽高无任何影响
7、 同理也可以证明 transform: scale(2) 对盒模型计算的宽高也是没有任何影响的

step11 width + scrollWidth

.div{
  width:100px;
  height:100px;
  background:red;
  overflow:auto;
}
chrome: width:, clientWidth: 85, offsetWidth: 100, getcomputed: 85px
firefox: width:, clientWidth: 85, offsetWidth: 100, getcomputed: 100px

step11 width + padding + scrollWidth

.div{
  width:100px;
  height:100px;
  background:red;
  overflow:auto;
  padding: 10px;
}
chrome: width:, clientWidth: 105, offsetWidth: 120, getcomputed: 85px
firefox: width:, clientWidth: 105, offsetWidth: 120, getcomputed: 100px

step12 width + border + scrollWidth

.div{
  width:100px;
  height:100px;
  background:red;
  overflow:auto;
  border: 1px solid red;
}
chrome: width:, clientWidth: 85, offsetWidth: 102, getcomputed: 85px
firefox: width:, clientWidth: 85, offsetWidth: 102, getcomputed: 100px

step13 width + padding + border-box + scrollWidth

.div{
  width:100px;
  height:100px;
  background:red;
  overflow:auto;
  border: 1px solid red;
  box-sizing: border-box;
}
chrome: width:, clientWidth: 83, offsetWidth: 100, getcomputed: 100px
firefox: width:, clientWidth: 83, offsetWidth: 100, getcomputed: 100px

step14 inlineWidth + scrollWidth

.div{
  width:100px;
  height:100px;
  background:red;
  overflow:auto;
}
<div style='width:100px'>
chrome: width:100px, clientWidth: 85, offsetWidth: 100, getcomputed: 85px
firefox: width:100px, clientWidth: 85, offsetWidth: 100, getcomputed: 100px

summary 3

8、 当存在滚动条的时候,clientWidth的宽度都会自动剪掉滚动轴所占的宽度,chrome和firefox下滚动轴都为15px
9、 当有滚动轴时getcomputed所计算的结果firfox不受影响,chrome则为生效的width距离渐去滚动轴宽度

Conclusion

1、style.width 只会得出行内style属性设置的宽度,带有单位(不一定是px,看设置)

2、clientWidth 为content-width + padding的和,受box-sizing影响

3、offsetWidth 为 content-width + padding + scrollBarWidth + border的和,也受box-sizing影响

4、getcomputed 在chrome和firefox下计算方式不同。

  • firefox: getcomputed与最终生效的那行设置width的代码值相同
  • chrome: 在无scrollBar的情况下,规则与firefox相同,

    在有scrollBar的情况下,box-sizing为border-box时 规则也与firfox相同,

    在有scrollBar的情况下,且默认box-sizing值,则为最终生效的那行设置width值的css代码减去滚动轴的宽度

5、因此如果想得知一个元素的宽度与高度推荐使用offsetWidth,但实际的宽高还需要再去查看是否有变形,因为变形影响不体现在方法中。

6、还有另外一个方法推荐使用既是HTMLElement.getBoundingClientRect(), 此方法会返回元素在浏览器窗口的具体位置,和元素的最终宽高,包含content-width, padding, border和,且是变化后的宽高。

7、任何一个方法都无法获取包含margin的宽高,需要单独去计算

更好的管理你的ajax

更好的管理你的ajax

ajax 应该是web史上最具有革命性的发明之一,它让web变成了一个优美的界面,同时ajax也遍布前端的任何一个js文件(有点夸张),尽管这两年势头正旺的fetch大有取代之势,但这并不是我要讲的(自己都受不了这生硬的转折了)。

痛点

  • ajax执行顺序不可控
  • ajax无法主动停止

1、不可控
我们可以控制合适发送ajax,但我们不能控制它什么时间回来。。。
可能这正是异步编程的优点,可能现在也还没意识到这有什么问题, 恰巧最近遇到了一个例子。

最近有个页面有一个搜索框,当搜索 cold 的时候数据返回的很快,但如果搜 1, 那家伙,等到地老天荒。于是问题出现了。。。。。用户先搜了1, 又搜了cold, 由于cold的结果先回来,回调函数中的this.setState({})把界面更新为cold的结果,但是!!!!1 缓缓来迟,界面又被更新了。。。。悲剧。尽管内容大不相同,内部工具可以人为识别出差距来。但是上面搜索框内容是cold, 结果内容是1的结果,这怎么能容忍。

这时候最简单的操作可能是增加一个loading动画,或者将搜索按钮暂时disabled掉不就可以了, 但这都不合理啊, 交互上感觉不好,如果用户恰巧搜错了内容,就是想放弃这次搜索怎么办!

2、无法主动停止
单页应用有个特点就是整体页面不变,更改内部页面结构。 好像这和ajax没啥关系啊。恰巧本人最近又碰到一个问题。

由于公司是react框架工作的,那么每次ajax的回调函数内几乎都会关联到this.setState({}), 要不你拉回数据又不更新界面,数据有p用。 到这里逻辑也挺正常的,但是联想到单页应用。 ajax总要有发送者吧,回调函数里肯定绑定了this,可是当发送完ajax, 页面更换了,原本的那个组件都被componentUnmount掉了,那还有this,于是浏览器显示了一条红色的语句cannot xxxx of undefined(详细内容记不住了,现在是在纯手敲,没有具体代码)。 我的天呐,竟然报错了。是error

上面两个案例其实就是想证明一个问题,我们需要可控的管理我们ajax,规定她们的执行顺序,甚至主动终止掉ajax。 但仔细想一想好像ajax并不能主动停止,新增的fetch也不能主动停止掉,那怎么办呢,办法还是有滴。

思路

不用说如果实现这个问题肯定是要自己封装ajax请求。 先放上当前项目前辈封装的ajax请求(不喜勿喷)。
又要解决问题,但是还不能让原代码失效报错,这确实是一门学问。

function defaultErrorCallback(error){
  console.log('httpDefaultError');
}
let GETService = function(url, token, params, callback, errorCallback = defaultErrorCallback){
  url += url.endsWith('?') ? '' : '?' + Object.keys(params).map((key)=>{ return `${key}=${params[key]}`;}).join('&');
  if (token){
    url += url.indexOf('?') > 0 ? `&token=${getToken()}` : `?token=${getToken()}`;
  }
  let result = fetch(url, {
    method: 'GET',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    }
  });
  result.then(function(string){
    let text = JSON.parse(string);
    callback(text);
  }).catch(function(error){
  });
};

可以看到原本的这个封装

  • 参数格式不能变
  • 没有return 也没有保存,只是简单的请求成功、失败执行回调而已

那修改起来其实只要保证这个文件抛出去的变量名不变,参数格式不变即可。 那怎么来解决这个问题呢。大概有两个需求

  • 可以中止当前ajax

  • 可以判断当前ajax状态
    解决方案

  • 之前也讲到了ajax不可能被中止,那我们怎么达到这个效果呢。 如果ajax无论成功或者失败之后,没有回调函数我们是不是也可以认为它被中止了呢, 似乎可以这样勉强的认为,最起码达到我们要的效果了。

  • 判断当前ajax的状态, 直观点最起码要return一个对象回去啊,这个对象里面又一个ajax状态的属性,幸运的是,之前的封装没有return,现在增加return之前的也不需要有任何修改

实现

有了痛点、有了思路、又有了需求,那就得实现它了。怎么实现它呢。 前两周看到了jQuery对队列的实现,这里借助一点队列当时看到的知识。

function GETService(url, token, params, callback, errorCallback){
  return new _GETService(url, token, params, callback, errorCallback);
}
function _GETService(url, token, params, callback, errorCallback){
  this.succQueue = [];
  this.errorQueue = [];
  this.status = 'pending';
  // send ajax
}

首先上面这个结构就基本保证之前的代码不会失效, 同时呢保证了新的部分可以获取到ajax的状态。 接下来看如何实现ajax中止的问题: 对回调函数做手脚,也就是两个队列做手脚。

function _GETService(url, token, params, callback, errorCallback){
  this.succQueue = [];
  this.errorQueue = [];
  this.status = 'pending';
  fetch.then(function(res){
    self.status = 'success';
    self.succQueue.map(item=>{
      item(res);
    });
  }).catch((error)=>{
    self.status = 'failed';
    self.errorQueue.map(item=>{
      item(error);
    });
  });
}
_GETService.prototype.stop = function(){
  this.succQueue = [];
  this.errorQueue = [];
}

其实呢这样我们就实现了在不改变原逻辑的情况下,实现了新的ajax请求,可以主动停止、可以查看状态的ajax。当然这只是一个框架, 里面还有工作需要做,比如原来传入的回调是单个函数,不是函数数组,现在需要做判断,修正参数为数组。 甚至我们可以为原本多增加一个stop回调函数,放在最后一位,毕竟多一位原本代码不会出问题

  this.succQueue = typeof succArr === 'function' ? [succArr] : succArr;
  this.errorQueue = typeof errorArr === 'function' ? [errorArr] : errorArr;
  this.stopQueue = typeof stopArr === 'function' ? [stopArr] : stopArr ? stopArr : [];

同样 由于我们采用了队列,也可以优化一下原本的回调函数,现在可以传入多个回调函数了,而不是原本的单个回调。

// 以成功部分简单示意
  fetch.then(function(res){
    self.status = 'success';
    let temp_return;
    self.succQueue.map(item=>{
      temp_return = item(res, temp_return);
    });
  })

经过修改后的回调队列,每一个回调函数会有两个参数,一个为ajax传回的原本参数,另一个为上一个回调函数执行后的结果。 再仔细考虑一下,是不是再引入一个Immutable的概念来确保res即ajax返回的原始址不应该被修改。 或者直接干脆一点 Object.freeze(res)呢。

总结

记录这个主要是给自己提个醒,之后有时间了把公司现有的这套ajax封装优化一下。甚至重写一套,毕竟之后的项目总会用到ajax。 最近太忙了,是在没有时间来完善这套ajax, 毕竟现在无用武之地,不能随意改动之前的代码。

这里也没有给出一套完整的ajax封装代码,只提供一个思路。

jQuery chapter11 Style操作 ----jQuery源码分析系列

chapter11 Style操作

一般我们不太建议直接对元素的style进行操作,建议通过更改类的方式来实现更改css的目的,在chapter8中也提到过如何对类进行增、删、交替更换等操作。但有时还是免不了需要直接和属性打交道,在很多时候都要知道用户的鼠标路径、某个盒子的大小尺寸等信息。

jQuery中大概将样式部分分为了 css样式设置计算、类样式、坐标、尺寸这几类,类样式之前已经介绍过。不可否认的是在样式部分,各浏览器的兼容尤其是低版本的IE简直惨不忍睹,阅读一下早期版本的jQuery还是有不少帮助的。

css样式设置计算

// 定义了一堆变量
var ralpha = /alpha\([^)]*\)/i,
  ropacity = /opacity=([^)]*)/,
  // fixed for IE9, see #8346
  rupper = /([A-Z]|^ms)/g,
  rnumpx = /^-?\d+(?:px)?$/i,
  rnum = /^-?\d/,
  rrelNum = /^([\-+])=([\-+.\de]+)/,
  cssShow = { position: "absolute", visibility: "hidden", display: "block" },
  cssWidth = [ "Left", "Right" ],
  cssHeight = [ "Top", "Bottom" ],
  curCSS,
  getComputedStyle,
  currentStyle;

jQuery.fn.css = function( name, value ) {
  // Setting 'undefined' is a no-op
  // 设置undefine给某一个属性是没意义的, 不执行任何操作
  if ( arguments.length === 2 && value === undefined ) {
    return this;
  }

  // 遍历所有当前元素,执行回调函数
  // 如果传入了value就是设置  jQuery.style( elem, name, value )
  // 没有value就是读取 jQuery.css( elem, name )
  return jQuery.access( this, name, value, true, function( elem, name, value ) {
    return value !== undefined ?
      jQuery.style( elem, name, value ) :
      jQuery.css( elem, name );
  });
};

jQuery.extend({
  // Add in style property hooks for overriding the default
  // behavior of getting and setting a style property
  cssHooks: {
    // 这便是默认的支持opacity浏览器的对opacity的hook
    // 确保返回的一定是一个数字
    opacity: {
      get: function( elem, computed ) {
        if ( computed ) {
          // We should always get a number back from opacity
          var ret = curCSS( elem, "opacity", "opacity" );
          return ret === "" ? "1" : ret;

        } else {
          return elem.style.opacity;
        }
      }
    }
  },

  // Exclude the following css properties to add px
  cssNumber: {
    "fillOpacity": true,
    "fontWeight": true,
    "lineHeight": true,
    "opacity": true,
    "orphans": true,
    "widows": true,
    "zIndex": true,
    "zoom": true
  },

  // Add in properties whose names you wish to fix before
  // setting or getting the value
  cssProps: {
    // normalize float css property
    "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
  },

  // Get and set the style property on a DOM Node
  // 在jQuery.fn.css中提到用style方法设置属性
  style: function( elem, name, value, extra ) {
    // Don't set styles on text and comment nodes
    // 不对文本节点和注释节点操作, 也不对不支持样式的节点进行操作
    if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
      return;
    }

    // Make sure that we're working with the right name
    // 第一步就是将名字转为驼峰写法
    var ret, type, origName = jQuery.camelCase( name ),
      style = elem.style, hooks = jQuery.cssHooks[ origName ];

    // 继续修正属性名
    // float名字比较特殊,ie下面是styleFloat,火狐chrome则是cssFloat
    name = jQuery.cssProps[ origName ] || origName;

    // Check if we're setting a value
    // 如果是设置属性
    if ( value !== undefined ) {
      type = typeof value;

      // convert relative number strings (+= or -=) to relative numbers. #7345
      // rrelNum = /^([\-+])=([\-+.\de]+)/
      // 如果value是字符串的情况下,我们检测他是不是 += 或者-=, 这种情况我们需要先进行计算
      if ( type === "string" && (ret = rrelNum.exec( value )) ) {
        value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
        // Fixes bug #9237
        type = "number";
      }

      // Make sure that NaN and null values aren't set. See: #7116
      if ( value == null || type === "number" && isNaN( value ) ) {
        return;
      }

      // If a number was passed in, add 'px' to the (except for certain CSS properties)
      // 修正value值,是否需要增加 px后缀
      if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
        value += "px";
      }

      // If a hook was provided, use that value, otherwise just set the specified value
      // 如果有hooks传入,则优先使用hooks.set方法进行属性设置
      // 最后再调用elem.style进行属性设置。
      if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
        // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
        // Fixes bug #5509
        try {
          style[ name ] = value;
        } catch(e) {}
      }

    } else {
      // 如果是读取属性
      // 现场时用hooks.get读取\再使用elem.style读取,不过这样读出来的仅仅是内连样式哦哦哦哦!!!!!!
      // If a hook was provided get the non-computed value from there
      if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
        return ret;
      }

      // Otherwise just get the value from the style object
      return style[ name ];
    }
  },

  // 在jQuery.fn.css中提到用css方法读取属性
  // 与style方法中读取属性基本一致,只是最后是使用curCss来读取计算后的最终属性,而非内连属性
  // curCSS = getComputedStyle || currentStyle;
  // 而非jQuery.curCSS
  css: function( elem, name, extra ) {
    var ret, hooks;

    // Make sure that we're working with the right name
    name = jQuery.camelCase( name );
    hooks = jQuery.cssHooks[ name ];
    name = jQuery.cssProps[ name ] || name;

    // cssFloat needs a special treatment
    if ( name === "cssFloat" ) {
      name = "float";
    }

    // If a hook was provided get the computed value from there
    if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
      return ret;

    // Otherwise, if a way to get the computed value exists, use that
    } else if ( curCSS ) {
      return curCSS( elem, name );
    }
  },
   
  // 为某些情况提前准备环境,
  // 先将options的属性复制给elem, 然后执行callback
  // 最后再将原本覆盖的属性恢复回去
  // A method for quickly swapping in/out CSS properties to get correct calculations
  swap: function( elem, options, callback ) {
    var old = {};

    // Remember the old values, and insert the new ones
    // 先记录原本的属性值
    // 然后将options中的属性值附上去
    for ( var name in options ) {
      old[ name ] = elem.style[ name ];
      elem.style[ name ] = options[ name ];
    }
    // 执行callback
    callback.call( elem );

    // 再还原属性值
    // Revert the old values
    for ( name in options ) {
      elem.style[ name ] = old[ name ];
    }
  }
});

// DEPRECATED, Use jQuery.css() instead
jQuery.curCSS = jQuery.css;

jQuery.each(["height", "width"], function( i, name ) {
  jQuery.cssHooks[ name ] = {
    get: function( elem, computed, extra ) {
      var val;
      // 只支持读取计算样式,那么css调用该方法会得到undefined
      if ( computed ) {
        // 如果该元素可见,那么调用getWH, 否则调用swap方法
        if ( elem.offsetWidth !== 0 ) {
          return getWH( elem, name, extra );
        } else {
          // cssShow = { position: "absolute", visibility: "hidden", display: "block" },
          jQuery.swap( elem, cssShow, function() {
            val = getWH( elem, name, extra );
          });
        }

        return val;
      }
    },

    set: function( elem, value ) {
      // rnumpx来检测属性是不是不是以px结尾
      // rnumpx = /^-?\d+(?:px)?$/i,
      // 只有纯数字或者以px结尾可以通过这个测试
      // parseFloat 则可以忽略px后缀,只返回带符号的数字部分
      if ( rnumpx.test( value ) ) {
        // ignore negative width and height values #1599
        value = parseFloat( value );

        if ( value >= 0 ) {
          return value + "px";
        }

      } else {
        return value;
      }
    }
  };
});

// 这里是对浏览器不支持opacity的hook
// 浏览器不支持opacity的时候需要使用filter来代替
if ( !jQuery.support.opacity ) {
  jQuery.cssHooks.opacity = {
    get: function( elem, computed ) {
      // IE uses filters for opacity
      // computed为真时使用计算样式、否则使用内连样式, 使用ropacity检测是否设置透明度
      // 设置了则返回百分比之后的值、否则计算的话显示1, 内敛的返回''空字符串
      // ropacity = /opacity=([^)]*)/,
      return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
        ( parseFloat( RegExp.$1 ) / 100 ) + "" :
        computed ? "1" : "";
    },

    set: function( elem, value ) {
      var style = elem.style,
        currentStyle = elem.currentStyle,
        opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
        filter = currentStyle && currentStyle.filter || style.filter || "";

      // IE has trouble with opacity if it does not have layout
      // Force it by setting the zoom level
      style.zoom = 1;

      // 如果是设置filter为1, 那么直接移除元素属性filter就好
      // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
      if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {

        // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
        // if "filter:" is present at all, clearType is disabled, we want to avoid this
        // style.removeAttribute is IE Only, but so apparently is this code path...
        style.removeAttribute( "filter" );
        // 如果成功移除了 return即可
        // if there there is no filter style applied in a css rule, we are done
        if ( currentStyle && !currentStyle.filter ) {
          return;
        }
      }

      // 乖乖的设置filter属性
      // otherwise, set new filter values
      // ralpha = /alpha\([^)]*\)/i, 
      style.filter = ralpha.test( filter ) ?
        filter.replace( ralpha, opacity ) :
        filter + " " + opacity;
    }
  };
}

jQuery(function() {
  // This hook cannot be added until DOM ready because the support test
  // for it is not run until after DOM ready
  if ( !jQuery.support.reliableMarginRight ) {
    jQuery.cssHooks.marginRight = {
      // 对于margin-right紊乱的情况,先讲属性设置为inline-block即可正确获取它的marinRight
      get: function( elem, computed ) {
        // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
        // Work around by temporarily setting element display to inline-block
        var ret;
        jQuery.swap( elem, { "display": "inline-block" }, function() {
          if ( computed ) {
            ret = curCSS( elem, "margin-right", "marginRight" );
          } else {
            ret = elem.style.marginRight;
          }
        });
        return ret;
      }
    };
  }
});

// 封装getComputedStyle方法
if ( document.defaultView && document.defaultView.getComputedStyle ) {
  getComputedStyle = function( elem, name ) {
    var ret, defaultView, computedStyle;

    // rupper = /([A-Z]|^ms)/g,
    name = name.replace( rupper, "-$1" ).toLowerCase();

    if ( (defaultView = elem.ownerDocument.defaultView) &&
        (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
      ret = computedStyle.getPropertyValue( name );
      if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
        // computedStyle没有计算到元素则尝试读取内连属性
        ret = jQuery.style( elem, name );
      }
    }

    return ret;
  };
}

if ( document.documentElement.currentStyle ) {
  currentStyle = function( elem, name ) {
    var left, rsLeft, uncomputed,
      ret = elem.currentStyle && elem.currentStyle[ name ],
      style = elem.style;

    // Avoid setting ret to empty string here
    // so we don't default to auto
    // 如果currentStyle没有拿到属性,则尝试使用elem.style读取内连属性
    if ( ret === null && style && (uncomputed = style[ name ]) ) {
      ret = uncomputed;
    }

    // From the awesome hack by Dean Edwards
    // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291

    // If we're not dealing with a regular pixel number
    // but a number that has a weird ending, we need to convert it to pixels
    // rnumpx = /^-?\d+(?:px)?$/i,
    // rnum = /^-?\d/,
    // rnumpx来检测属性是不是不是以px结尾,  rnum测试属性是不是数字
    // 如果是这样的话例如 1rem、1em、65%, 我们需要将它们转为px单位
    // 先记录属性原本的left值。 利用runtimeStyle、pixelLeft来计算百分比, 最后再恢复原本的left值
    if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {

      // Remember the original values
      left = style.left;
      rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;

      // Put in the new values to get a computed value out
      if ( rsLeft ) {
        elem.runtimeStyle.left = elem.currentStyle.left;
      }
      style.left = name === "fontSize" ? "1em" : ( ret || 0 );
      ret = style.pixelLeft + "px";

      // Revert the changed values
      style.left = left;
      if ( rsLeft ) {
        elem.runtimeStyle.left = rsLeft;
      }
    }

    return ret === "" ? "auto" : ret;
  };
}

curCSS = getComputedStyle || currentStyle;

坐标系统计算

var rtable = /^t(?:able|d|h)$/i,
  rroot = /^(?:body|html)$/i;

// jQuery.fn.offset在支持getBoundingClientRect情况下调用getBoundingClientRect方法,
// 同时也为不支持getBoundingClientRect方法的做了兼容处理
// 其实IE678910全部支持getBoundingClientRect方法,并不知道要兼容哪些浏览器
if ( "getBoundingClientRect" in document.documentElement ) {
  jQuery.fn.offset = function( options ) {
    var elem = this[0], box;

    if ( options ) {
      // 如果有options表示为每个元素设置位置
      return this.each(function( i ) {
        jQuery.offset.setOffset( this, options, i );
      });
    }

    if ( !elem || !elem.ownerDocument ) {
      return null;
    }
    // body的位置需要单独计算
    if ( elem === elem.ownerDocument.body ) {
      return jQuery.offset.bodyOffset( elem );
    }

    try {
      box = elem.getBoundingClientRect();
    } catch(e) {}

    var doc = elem.ownerDocument,
      docElem = doc.documentElement;

    // Make sure we're not dealing with a disconnected DOM node
    // 如果当前元素不在文档中,返回{0,0}
    if ( !box || !jQuery.contains( docElem, elem ) ) {
      return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
    }

    // 距离文档上标 = 距窗口上标 + 垂直滚动偏移 -文档左上边框厚度
    // 距离文档左标 = 距窗口左标 + 水平滚动便宜 -文档左边框厚度
    var body = doc.body,
      win = getWindow(doc),
      clientTop  = docElem.clientTop  || body.clientTop  || 0,
      clientLeft = docElem.clientLeft || body.clientLeft || 0,
      scrollTop  = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop,
      scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
      top  = box.top  + scrollTop  - clientTop,
      left = box.left + scrollLeft - clientLeft;

    return { top: top, left: left };
  };

} else {
  // 在不支持getBoundingClientRect的浏览器中,利用offsetParent来一直循环计算top、left数值
  jQuery.fn.offset = function( options ) {
    var elem = this[0];

    if ( options ) {
      return this.each(function( i ) {
        jQuery.offset.setOffset( this, options, i );
      });
    }

    if ( !elem || !elem.ownerDocument ) {
      return null;
    }

    if ( elem === elem.ownerDocument.body ) {
      return jQuery.offset.bodyOffset( elem );
    }

    var computedStyle,
      offsetParent = elem.offsetParent,
      prevOffsetParent = elem,
      doc = elem.ownerDocument,
      docElem = doc.documentElement,
      body = doc.body,
      defaultView = doc.defaultView,
      prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
      top = elem.offsetTop,
      left = elem.offsetLeft;

    while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
      if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
        break;
      }

      computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
      top  -= elem.scrollTop;
      left -= elem.scrollLeft;

      if ( elem === offsetParent ) {
        top  += elem.offsetTop;
        left += elem.offsetLeft;

        if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
          top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
          left += parseFloat( computedStyle.borderLeftWidth ) || 0;
        }

        prevOffsetParent = offsetParent;
        offsetParent = elem.offsetParent;
      }

      if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
        top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
        left += parseFloat( computedStyle.borderLeftWidth ) || 0;
      }

      prevComputedStyle = computedStyle;
    }

    if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
      top  += body.offsetTop;
      left += body.offsetLeft;
    }

    if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
      top  += Math.max( docElem.scrollTop, body.scrollTop );
      left += Math.max( docElem.scrollLeft, body.scrollLeft );
    }

    return { top: top, left: left };
  };
}

jQuery.offset = {
  // 计算body的文档坐标
  bodyOffset: function( body ) {
    var top = body.offsetTop,
      left = body.offsetLeft;

    if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
      top  += parseFloat( jQuery.css(body, "marginTop") ) || 0;
      left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
    }

    return { top: top, left: left };
  },

  setOffset: function( elem, options, i ) {
    var position = jQuery.css( elem, "position" );

    // set position first, in-case top/left are set even on static elem
    // 如果当前position为static,则设置为relative,否则不起效
    if ( position === "static" ) {
      elem.style.position = "relative";
    }

    var curElem = jQuery( elem ),
      curOffset = curElem.offset(), // 当前元素的文档坐标
      curCSSTop = jQuery.css( elem, "top" ),  // 当前元素的top
      curCSSLeft = jQuery.css( elem, "left" ),  // 当前元素的left值
      calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
      props = {}, curPosition = {}, curTop, curLeft;

    // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
    // 继续修正参数
    if ( calculatePosition ) {
      curPosition = curElem.position();
      curTop = curPosition.top;
      curLeft = curPosition.left;
    } else {
      curTop = parseFloat( curCSSTop ) || 0;
      curLeft = parseFloat( curCSSLeft ) || 0;
    }

    if ( jQuery.isFunction( options ) ) {
      options = options.call( elem, i, curOffset );
    }

    // 内连top = 目标文档坐标top - 当前文档坐标top + 计算样式top
    // 内连left = 目标文档坐标left - 当前文档坐标left + 计算样式left
    if ( options.top != null ) {
      props.top = ( options.top - curOffset.top ) + curTop;
    }
    if ( options.left != null ) {
      props.left = ( options.left - curOffset.left ) + curLeft;
    }

    if ( "using" in options ) {
      options.using.call( elem, props );
    } else {
      curElem.css( props );
    }
  }
};


jQuery.fn.extend({
  // 寻找第一个元素相对于定位祖先元素的位置
  // 利用元素本身的offset和定位元素的offset
  // 但是元素本身需要减去对应的margin、 父元素需要增加border的宽度
  position: function() {
    if ( !this[0] ) {
      return null;
    }

    var elem = this[0],

    // Get *real* offsetParent
    offsetParent = this.offsetParent(),

    // Get correct offsets
    offset       = this.offset(),
    parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();

    // Subtract element margins
    // note: when an element has margin: auto the offsetLeft and marginLeft
    // are the same in Safari causing offset.left to incorrectly be 0
    offset.top  -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
    offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;

    // Add offsetParent borders
    parentOffset.top  += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
    parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;

    // Subtract the two offsets
    return {
      top:  offset.top  - parentOffset.top,
      left: offset.left - parentOffset.left
    };
  },

  offsetParent: function() {
    // 循环寻找定位祖先元素
    // 两个退出条件、定位祖先不能是static、或者祖先是body
    return this.map(function() {
      var offsetParent = this.offsetParent || document.body;
      while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
        offsetParent = offsetParent.offsetParent;
      }
      return offsetParent;
    });
  }
});


// Create scrollLeft and scrollTop methods
jQuery.each( ["Left", "Top"], function( i, name ) {
  var method = "scroll" + name;

  jQuery.fn[ method ] = function( val ) {
    var elem, win;
    // 读取第一个元素的Scroll值
    if ( val === undefined ) {
      elem = this[ 0 ];

      if ( !elem ) {
        return null;
      }

      win = getWindow( elem );

      // Return the scroll offset
      return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
        jQuery.support.boxModel && win.document.documentElement[ method ] ||
          win.document.body[ method ] :
        elem[ method ];
    }

    // Set the scroll offset
    // 为每个元素设置scroll值
    return this.each(function() {
      win = getWindow( this );

      if ( win ) {
        win.scrollTo(
          !i ? val : jQuery( win ).scrollLeft(),
           i ? val : jQuery( win ).scrollTop()
        );

      } else {
        this[ method ] = val;
      }
    });
  };
});

尺寸计算

// getWH是整个尺寸计算的基础函数
function getWH( elem, name, extra ) {

  // Start with offset property
  // cssWidth = [ "Left", "Right" ],
  // cssHeight = [ "Top", "Bottom" ],
  // offset包含content、padding、border、但是不包含margin
  var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
    which = name === "width" ? cssWidth : cssHeight,
    i = 0,
    len = which.length;
  // 表示元素可见
  if ( val > 0 ) {
    if ( extra !== "border" ) {
      for ( ; i < len; i++ ) {
        // 没有extra表示只计算content, 需要先减去padding,再减去border
        if ( !extra ) {
          val -= parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0;
        }
        // 如果extra是border,则加上border的宽度
        if ( extra === "margin" ) {
          val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0;
        } else {
          val -= parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0;
        }
      }
    }
    // 如果extra是border则刚好不需要任何修复
    return val + "px";
  }

  // Fall back to computed then uncomputed css if necessary
  // 先计算样式再内连样式,再根绝extra修正
  val = curCSS( elem, name, name );
  if ( val < 0 || val == null ) {
    val = elem.style[ name ] || 0;
  }
  // Normalize "", auto, and prepare for extra
  val = parseFloat( val ) || 0;

  // Add padding, border, margin
  if ( extra ) {
    for ( ; i < len; i++ ) {
      val += parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0;
      if ( extra !== "padding" ) {
        val += parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0;
      }
      if ( extra === "margin" ) {
        val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0;
      }
    }
  }

  return val + "px";
}

// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
jQuery.each([ "Height", "Width" ], function( i, name ) {

  var type = name.toLowerCase();

  // innerHeight and innerWidth
  // 获取第一个元素的inner属性
  // 没有元素返回null
  // 有元素有style属性,返回parseFloat( jQuery.css( elem, type, "padding" ) )
  // 有元素没style返回elem.width()
  // jQuery.css( elem, type, "padding" ) 其实还是最终调用hooks--> getWH
  //
  jQuery.fn[ "inner" + name ] = function() {
    var elem = this[0];
    return elem ?
      elem.style ?
      parseFloat( jQuery.css( elem, type, "padding" ) ) :
      this[ type ]() :
      null;
  };

  // outerHeight and outerWidth
  jQuery.fn[ "outer" + name ] = function( margin ) {
    var elem = this[0];
    return elem ?
      elem.style ?
      parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
      this[ type ]() :
      null;
  };

  jQuery.fn[ type ] = function( size ) {
    // Get window width or height
    var elem = this[0];
    if ( !elem ) {
      return size == null ? null : this;
    }

    if ( jQuery.isFunction( size ) ) {
      return this.each(function( i ) {
        var self = jQuery( this );
        self[ type ]( size.call( this, i, self[ type ]() ) );
      });
    }
    // 计算window元素的width和height
    if ( jQuery.isWindow( elem ) ) {
      // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
      // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
      var docElemProp = elem.document.documentElement[ "client" + name ],
        body = elem.document.body;
      return elem.document.compatMode === "CSS1Compat" && docElemProp ||
        body && body[ "client" + name ] || docElemProp;

    // document对象的width和height
    } else if ( elem.nodeType === 9 ) {
      // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
      return Math.max(
        elem.documentElement["client" + name],
        elem.body["scroll" + name], elem.documentElement["scroll" + name],
        elem.body["offset" + name], elem.documentElement["offset" + name]
      );

    // Get or set width or height on the element
    // 直接css读取witdh和heigt对于普通元素
    } else if ( size === undefined ) {
      var orig = jQuery.css( elem, type ),
        ret = parseFloat( orig );

      return jQuery.isNumeric( ret ) ? ret : orig;

    // Set the width or height on the element (default to pixels if value is unitless)
    } else {
      // 为普通元素添加width和 height属性
      return this.css( type, typeof size === "string" ? size : size + "px" );
    }
  };

});

jQuery chapter2 Sizzle----jQuery源码分析系列

chapter2

chapter1中对jquery整体结构和最基本的部分有一个简单的认识了。 现在来看jquery是如何帮我们查找dom元素的。

Sizzle

Sizzle既是一个单独的用js实现的css选择器包,也是jquery不可分割的一部分。在低级浏览器中jquery正是靠着Sizzle才能简单的稳定的帮我们获取到想要的dom元素集合。

我们都知道css的查询是从右向左查即div > p 先查所有的p再筛选出满足父元素为div的p。 关于css左查和右查的规则以及优劣可以看blogsegmentfault

在Sizzle中基本也是与css规则相同即从右向左查询,进行逐步筛选,逐步缩小的方法。除了有伪类存在时是从左向右查。原因是 div button:first 是查找div下面的第一个button,位置伪类过滤的是 div button而不是button。关于这一点之后慢慢分析。

Sizzle in jQuery

在jQuery中的Sizzle位置大概在 3859-5302 行之间,大约1500行左右。 由一个立即执行函数包裹,在内部最后通过把一些方法挂载到jQuery变量上面来实现让jQuery的访问与使用,可以看到jQuery.find其实就是Sizzle的主入口。 立即执行函数的好处就不再叙述了,与jQuery的整体**一致。 内部实现方法、定义变量,最终将接口抛出来:jQuery挂在window上,Sizzle挂在jQuery上。

(function(){
........
Sizzle.attr = jQuery.attr;
Sizzle.selectors.attrMap = {};
jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.filters;
jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;
})();

源码

chunker

在Sizzle整个代码的第一行是一个非常长的正则表达式--chunker, 此行正则表达式竟然有150个字符,简直是一个逆天的正则。那就从它开始看。

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,

首先拆分一下正则,利用()分为三部分

1: ((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])
2: (\s*,\s*)?   // \s表示一个单个的空白符, 因此匹配的就是单个,(逗号),甚至可以不匹配 css选择器表达式中逗号,表示并列选择器
3: ((?:.|\r|\n)*)    // ,后面的并列选择器的文本

第一部分还是太长 继续拆分,利用 |

1—1: (?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,\[\\]+)+
1-2: [>+~]

还是太长 继续拆分利用 | 和 ()

1_1_1: \((?:\([^()]+\)|[^()]+)+\)     // 匹配 ((tag))或者(tag)  并且tag必须存在
1_1_2: \[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]  // 匹配 [[]] 或者[""]、[''] 或者 [tag]
1_1_3: \\.
1_1_4: [^ >+~,(\[\\]+  // 不包含关系符号、属性符号[、伪类(和转义符\,可以匹配.class、 #id
1_1_5: [>+~]  // 匹配层级选择器 > + ~

因此整个chunker的第一部分就是选择器表达式,第二部分表示并列选择器表达式的flag,第三部分表示并列表达式。第一部分的内部又分为伪类()部分、属性[]部分、引号包裹部分、简单选择器部分。 当然这样简单的处理理解肯定是不足的。具体的之后再继续深入,先知道chunker大概。

Sizzle入口

parts.1

在立即执行函数内定义一个Sizzle函数变量,最后挂在jQuery上,作为Sizzle的入口函数。

Sizzle接受4个参数,selector选择器字符串、context上下文、results接收结果的容器、seed结果将从该元素中进行过滤

var Sizzle = function( selector, context, results, seed ) {
  // 常规的默认参数修正
  results = results || [];
  context = context || document;
  // 记录原上下文
  var origContext = context;

  if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
    return [];
  }
  
  if ( !selector || typeof selector !== "string" ) {
    return results;
  }

  var m, set, checkSet, extra, ret, cur, pop, i,
    prune = true,
    contextXML = Sizzle.isXML( context ),
    parts = [],
    soFar = selector;
  // 采用do-while的形式循环匹配出所有的 选择表达式
  do {
    // 执行一次正则,但永远不会得到匹配,这样的作用是chunker.lastIndex = 0
    // 因为带g的正则都有lastIndex属性,每次匹配的时候都是从这个位置开始,
    // 关于lastIndex可以看[blog](http://www.cnblogs.com/sniper007/archive/2011/12/20/2295044.html),但这篇文章Sizzle解析的部分个人感觉是错误的
    chunker.exec( "" );
    // 真正执行一次匹配
    m = chunker.exec( soFar );
    

    // 如果解析成功,那么将这次匹配到的内容压入parts数组,并且如果有,这个flag则更改将多余的部分付给extra
    if ( m ) {
      soFar = m[3];
    
      parts.push( m[1] );
      // 在什么情况下m[2]会存在,什么情况下会不存在呢?
      // m[2]存在比较好理解,就是包含 , 的字符串
      // m[2]不存在的情况就是 div > p这样提取出了m[1] = div, m[2]=undeifned, m[3]=' > p'
      if ( m[2] ) {
        extra = m[3];
        break;
      }
    }
  } while ( m );
  .....

对这段代码单独提取一下并且测试一下会发现,并且输出parts数组, 这段代码直接帮我们把一个选择器表达式进行拆解了, 同时关系也比较明确

exec('div:children > div , div')     // ["div:children", ">", "div"]
exec('div div')   // ["div", "div"]

parts.2

继续看代码

这一次稍长一点 是一个if-else,条件是parts长度大于1并且有伪类存在选择器中。
origPos = /:(nth|eq|gt|lt|first|last|even|odd)(?:((\d*)))?(?=[^\-]|$)/
因为前面也说过了伪类选择器是Sizzle决定左查还是右查的一个重要指标

  // 根据parts长度和是否有伪类选择器决定左查或者右查
  if ( parts.length > 1 && origPOS.exec( selector ) ) {
    // 满足条件则是从左向右查找
    // 如果parts长度为2并且第一个为关系符 ? > ~ 则直接调用posProcess方法
    // 说明这是一个比较简单的 其实只有一个选择器的查询
    if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
      set = posProcess( parts[0] + parts[1], context, seed );
    } else {
      // 否则的话再判断第一位是不是关系符决定set的初始值
      set = Expr.relative[ parts[0] ] ?
        [ context ] :
        Sizzle( parts.shift(), context );
      // 依旧用while循环来依次取出parts的首位并且调用posProcess方法,直到parts长度为0
      while ( parts.length ) {
        selector = parts.shift();

        if ( Expr.relative[ selector ] ) {
          selector += parts.shift();
        }
        
        set = posProcess( selector, set, seed );
      }
    }
    // 在这个if中可以看出来posProcess才是真正干活的, 每次传进去一个选择器selector,然后再传入set,set就是上一次得出的结果, 可以理解为上下文吧,然后每次进行缩小操作, 从左向右嘛,一直在做减法
  } else {
    // 上面是从左向右, 这里就是从右向左了
    // 满足一系列条件的话就修正context和初始化ret
    // parts长度大于1, 首个元素是id, 最后一个不是id
    if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
        Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
      ret = Sizzle.find( parts.shift(), context, contextXML );
      context = ret.expr ?
        Sizzle.filter( ret.expr, ret.set )[0] :
        ret.set[0];
    }

    // 如果前一个if不满足条件的话context在这里一定会有值
    // 只有前一个if执行了,但是根据第一个id选择器没有找到元素,修正context失败了, 那么也意味着没有符合条件的元素,之内else返回[]
    if ( context ) {
      // 初始化参数, 如果有seed则直接食用seed作为候选集,否则调用最后一个parts进行候选集初始化
      ret = seed ?
        { expr: parts.pop(), set: makeArray(seed) } :
        Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
      // 元素的候选集,上下文,从右向左是一直做减法
      set = ret.expr ?
        Sizzle.filter( ret.expr, ret.set ) :
        ret.set;

      // 如果parts还有元素就对set做个备份, 否则的话prune设为false
      if ( parts.length > 0 ) {
        checkSet = makeArray( set );
      } else {
        prune = false;
      }

      // 相同的把戏,这次是pop即parts从右向左依次推出,来慢慢缩小查找范围
      while ( parts.length ) {
        // 取出最后一个元素
        cur = parts.pop();
        pop = cur;
        
        // 如果最后一个元素不是关系符, 则把cur设为 '', pop则重新在parts中取出最后一个
        // 这样确保pop为选择器, cur为选择符号
        if ( !Expr.relative[ cur ] ) {
          cur = "";
        } else {
          pop = parts.pop();
        }
        // 当发现pop为null时即上一步pop时已经是首位了,[0]之前没有了, 则直接把context设为pop
        if ( pop == null ) {
          pop = context;
        }
        // 然后在利用关系符、checkSet、pop来缩小查找范围,结果通过checkSet来传递
        Expr.relative[ cur ]( checkSet, pop, contextXML );
      }

    } else {
      checkSet = parts = [];
    }
  }

parts.3

最后进行统一的处理

  // 如果没有checkSet,则把set值赋给checkSet,因为从左向右查没有set,从右向左查并且只有一个表达式时也没有checkSet
  if ( !checkSet ) {
    checkSet = set;
  }

  if ( !checkSet ) {
    Sizzle.error( cur || selector );
  }
  // 如果checkSet也就是前面的结果是数组 再决定要不要筛选,最终将结果打入results中,最后返回results
  if ( toString.call(checkSet) === "[object Array]" ) {
    if ( !prune ) {
      results.push.apply( results, checkSet );

    } else if ( context && context.nodeType === 1 ) {
      for ( i = 0; checkSet[i] != null; i++ ) {
        if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
          results.push( set[i] );
        }
      }

    } else {
      for ( i = 0; checkSet[i] != null; i++ ) {
        if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
          results.push( set[i] );
        }
      }
    }

  } else {
    makeArray( checkSet, results );
  }

  // 如果还有extra,则继续调用Sizzle入口函数,并将results和OrigContext传入,然后再筛选结果。
  if ( extra ) {
    Sizzle( extra, origContext, results, seed );
    Sizzle.uniqueSort( results );
  }

  return results;

通过对Sizzle入口函数的解析,过程可归结如下
1、先对选择器整体的字符串进行拆解
2、无论是从左到右还是从右到左,一步步调用方法来不断减少范围
3、最后对两种方向得出的结果进行统一,最后返回结果。

里面真正做事情的函数不是特别多,大概有三个主要出现的。 posProcess, Sizzle.find,Expr.relative[ cur ]

Sizzle.find()

用于查找与表达式匹配的集合。

在Sizzle函数的解析中我们发现真正做事情的内容不多, Sizzle.find算一个。

Sizzle.find = function( expr, context, isXML ) {
  var set, i, len, match, type, left;
  if ( !expr ) {
    return [];
  }
  // Expr.order = [ "ID", "NAME", "TAG" ];
  // 依次检测id, name和tag
  for ( i = 0, len = Expr.order.length; i < len; i++ ) {
    type = Expr.order[i];
    // 利用leftMatch正则来依次检测这个选择器是否符合要求
    if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
      // 进入if则表示正则匹配成功了
      left = match[1];
      match.splice( 1, 1 );
      // 这里优点不明白的是为一个字符串执行substr(length-1)之后应该永远不可能等于'\\',只可能等于一个长度
      // 总之就是成功了就继续Expr.find来获取元素集合
      if ( left.substr( left.length - 1 ) !== "\\" ) {
        match[1] = (match[1] || "").replace( rBackslash, "" );
        set = Expr.find[ type ]( match, context, isXML );
        if ( set != null ) {
          // 如果元素集合获取成功了则把已经用过的expr部分删除掉
          expr = expr.replace( Expr.match[ type ], "" );
          break;
        }
      }
    }
  }

  if ( !set ) {
    // 走到这里说明没有匹配到任何元素,相对应expr也没有被删掉任何一部分,这样就获取当前元素的所有子元素作为集合,expr再还回去
    set = typeof context.getElementsByTagName !== "undefined" ?
      context.getElementsByTagName( "*" ) :
      [];
  }
  // set表示找到的所有元素集合, expr表示剩余的选择器部分。
  return { set: set, expr: expr };
};

这里面发现真正干活的是Expr.find

Sizzle.filter()

利用块表达式来过滤元素集合

Sizzle.filter = function( expr, set, inplace, not ) {
  var match, anyFound,
    type, found, item, filter, left,
    i, pass,
    old = expr,
    result = [],
    curLoop = set,
    isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );

  while ( expr && set.length ) {
    for ( type in Expr.filter ) {
      if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
        filter = Expr.filter[ type ];
        left = match[1];

        anyFound = false;

        match.splice(1,1);

        if ( left.substr( left.length - 1 ) === "\\" ) {
          continue;
        }

        if ( curLoop === result ) {
          result = [];
        }

        if ( Expr.preFilter[ type ] ) {
          match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );

          if ( !match ) {
            anyFound = found = true;

          } else if ( match === true ) {
            continue;
          }
        }

        if ( match ) {
          for ( i = 0; (item = curLoop[i]) != null; i++ ) {
            if ( item ) {
              found = filter( item, match, i, curLoop );
              pass = not ^ found;

              if ( inplace && found != null ) {
                if ( pass ) {
                  anyFound = true;

                } else {
                  curLoop[i] = false;
                }

              } else if ( pass ) {
                result.push( item );
                anyFound = true;
              }
            }
          }
        }

        if ( found !== undefined ) {
          if ( !inplace ) {
            curLoop = result;
          }

          expr = expr.replace( Expr.match[ type ], "" );

          if ( !anyFound ) {
            return [];
          }

          break;
        }
      }
    }

    // Improper expression
    if ( expr === old ) {
      if ( anyFound == null ) {
        Sizzle.error( expr );

      } else {
        break;
      }
    }

    old = expr;
  }

  return curLoop;
};

未完待续
Sizzle暂时不继续进行分析, 对js帮助并不是特别大。
先着重进行jQuery内容的分析

jQuery chapter6 兼容性测试 ----jQuery源码分析系列

chapter6 浏览器测试

浏览器兼容永远是前端说不完的痛,尤其对于2C的客户,需要面对各种浏览器,尤其是低版本的IE,尽管现在大家基本已经放弃了IE67,但IE8仍然不是一个标准的W3C浏览器。仍然一堆兼容性需要处理,例如最基本的document.attachEvent

测试策略

浏览器功能性检测大概可以分为两类:

  • 检测navigator.userAgent,用户代理检测法
  • 检测浏览器的功能特性,即功能特性检测法
  1. 用户代理检测方法主要依靠依据的是大家的工作经验,比如大家知道IE绑定事件是document.attachEvent,因此我们可以这样写
if ( IE ){
    document.attachEvent(.....)
} else {
    document.addEventListener
}

但是这样的兼容性我们可能会忽略或者不全面。 比如一个小众的浏览器,我们可能不会那么准确的通过userAgent来判断他是不是IE还是W3C标准浏览器。

关于userAgent,这里有一片不错的文章介绍它的乱七八糟的混乱,但是userAgent仍是后台来简单区分用户浏览器的一个不错的选择。

  1. 功能特征检测法则依据浏览器是否支持某项特定的功能特性,来决定程序的执行分支。还是刚刚的例子,这一次的判断条件不再是IE。
if (document.attachEvent ){
    document.attachEvent(.....)
} else if ( document.addEventListener ){
    document.addEventListener(...)
} else {
    return new Error()
}

这一次我们不在关心页面运行在哪个浏览器,也不关心浏览器版本,我们知道有两个方法都可以完成我们的任务, 目标浏览器支持哪个就运行哪个分支,这样确保程序可以正常运行,如果发现两个方法都不被目标浏览器支持,那么不好意思了,目前没有可以替代的第三种方案,那只能报错了。。。

综合上面两种方案,使用功能特性检测法可能更符合我们的希望目标,毕竟有一点很重要,我们不可能穷尽所有厂商各版本的浏览器特征,所以我们可以只检测我们用到的特性方法。jQuery也是采用的第二种功能特性检测发来实现的浏览器测试部分。

源码

这一次直接将全部代码一次性作为整体,不再分割成多块。

jQuery.support = (function() {
  // 定义一堆变量以后使用, 新建一个div元素作为容器,同时获取html元素的引用
  var support,
    all,
    a,
    select,
    opt,
    input,
    marginDiv,
    fragment,
    tds,
    events,
    eventName,
    i,
    isSupported,
    div = document.createElement( "div" ),
    documentElement = document.documentElement;

  // 初步检测
  div.setAttribute("className", "t");
  div.innerHTML = "   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";

  all = div.getElementsByTagName( "*" );
  a = div.getElementsByTagName( "a" )[ 0 ];

  // 如果连innerHTML、getElementsByTagName、getElementsByTagName任意一个都失败,那么support就没意义了
  if ( !all || !all.length || !a ) {
    return {};
  }

  // 新建select和option以及获取input引用
  select = document.createElement( "select" );
  opt = select.appendChild( document.createElement("option") );
  input = div.getElementsByTagName( "input" )[ 0 ];

  support = {
    // 刚才innerHTML那段字符串是以空格开头的, 测试浏览器是否会保留innerHTML的前导空格
    // 如果保留空格符的话 那么第一个子元素将是文本node, nodetype为3, 普通dom元素nodetype是1
    // 保留签到空格为true 
    leadingWhitespace: ( div.firstChild.nodeType === 3 ),

    // 测试浏览器会不会自动为空table插入tbody元素, 通过测试刚才那段innerHTML里有没有自动生成tbody即可
    // 不自动插入为true
    tbody: !div.getElementsByTagName("tbody").length,

    // 测试能不能正确的序列化link标签
    // 低版本ie如果设置innerHTML='<link/>'会直接把link吃掉,
    // 解决办法是加一个'div<div><link/></div>',然后再一层层取出来link元素  不知道为什么。
    // 能保留link则为true
    htmlSerialize: !!div.getElementsByTagName("link").length,

    // 测试能不能通过getAttribute获取内连style信息
		// ie678需要使用a.style.cssText来获取内连style
    // getAttribute成功则为true
    style: /top/.test( a.getAttribute("style") ),

    // 测试a标签的URL会不会被改为全路径
    // 如果不更改则为true,
    hrefNormalized: ( a.getAttribute("href") === "/a" ),

    // Make sure that element opacity exists
    // (IE uses filter instead)
    // Use a regex to work around a WebKit issue. See #5145
    // 测试浏览器支持opacity属性与否
    // 在ie8中实测会返回.55,这样测试就不会通过,因为支持opacity的浏览器会解析.55为0.55
    // 但需要注意的是正则里面的.不是小数点的点,而是匹配通配符
    opacity: /^0.55/.test( a.style.opacity ),

    // 测试是否支持cssFloat属性,如果不支持 后续需要使用styleFloat来修正
    // 支持cssFloat则为true
    cssFloat: !!a.style.cssFloat,

    // 测试默认的<input type='checkbox'/>的value值
    // 不过实测safari、chrome、ie8全为on,可能是老版本的safari会是默认空字符串
    checkOn: ( input.value === "on" ),

    //   select = document.createElement( "select" );
    // opt = select.appendChild( document.createElement("option") );
    // 测试option默认是否选中,ie8是false, safari、chrome、firefox为true
    optSelected: opt.selected,

    // div.setAttribute("className", "t");
    // 测试能否在setAttribute需要传入的是dom属性名还是html属性名
    // 同样的方法还有getAttribute和removeAttribute
    // class这个属性是html属性, className是dom属性
    // 需要传入html属性是true, dom属性是false
    getSetAttribute: div.className !== "t",

    // enctype 属性规定在发送到服务器之前应该如何对表单数据进行编码
    // 直接测试form元素有没有这个属性即可
    enctype: !!document.createElement("form").enctype,

    // 测试浏览器能否正确的复制html5的元素
    // 以nav标签为测试例子
    // 这里如果outerHTML为undefined也是认为是可以正确复制html5元素的
    // 能正确复制为true
    html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",

    // 后续的几项在后面再进行测试
    submitBubbles: true,
    changeBubbles: true,
    focusinBubbles: false,
    deleteExpando: true,
    noCloneEvent: true,
    inlineBlockNeedsLayout: false,
    shrinkWrapBlocks: false,
    reliableMarginRight: true
  };

  // 测试能不能正确的复制input的checked值
  input.checked = true;
  support.noCloneChecked = input.cloneNode( true ).checked;

  // 当select设为disabled时,子元素option能否也自动设为disabled
  select.disabled = true;
  support.optDisabled = !opt.disabled;

  // 测试浏览器是否允许删除dom元素的属性
  // 这里使用了try因为直接报错容易崩溃
  // ie不允许删除dom元素的属性
  try {
    delete div.test;
  } catch( e ) {
    support.deleteExpando = false;
  }

	// ie在复制元素的时候会连同事件一起复制了,要测试是否存在这个兼容问题
	// 在没有addEventListener但有attachEvent的浏览器,即ie上测试
	// 先为div添加一个相应事件,on开通哦。 
	// 复制元素并且执行onclike, 如果执行了,那么noCloneEvent就为false了。
	// 如果没有复制事件,那么也就不会执行,就会是默认true
  if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
    div.attachEvent( "onclick", function() {
      // Cloning a node shouldn't copy over any
      // bound event handlers (IE does this)
      support.noCloneEvent = false;
    });
    div.cloneNode( true ).fireEvent( "onclick" );
  }

  // 测试radio类型会不会让input的value丢失掉
  input = document.createElement("input");
  input.value = "t";
  input.setAttribute("type", "radio");
  support.radioValue = input.value === "t";


  // 测试浏览器能否在文档片段中正确的复制checked状态值
  input.setAttribute("checked", "checked");
  div.appendChild( input );
  fragment = document.createDocumentFragment();
  fragment.appendChild( div.lastChild );
  support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;

  // 测试将一个input插入到文档片段中,check属性是否保留
  support.appendChecked = input.checked;

  fragment.removeChild( input );
  fragment.appendChild( div );



  div.innerHTML = "";


  // 这是一个webkit的小bug https://bugs.webkit.org/show_bug.cgi?id=13343
  // 不会对普通block element返回margin-right值
  // 只要得出的结果不是0那么就是存在这个bug
  // 正确计算marginright值为true
  if ( window.getComputedStyle ) {
    marginDiv = document.createElement( "div" );
    marginDiv.style.width = "0";
    marginDiv.style.marginRight = "0";
    div.style.width = "2px";
    div.appendChild( marginDiv );
    support.reliableMarginRight =
      ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
  }

	// 只在ie上测试
	// 对submit、change、focusin循环处理
	// 如果div上面没有这个认为不支持
	// 如果不支持 再试试setAttribute能不能正确设置
	// 最后设置最终的是否支持
	// 通过名字来看是测试能否冒泡,但是没看见有冒泡的检测啊?
	// 原因在于 div本身不应该有submit、change、focus这类事件,如果有则表明支持冒泡
  if ( div.attachEvent ) {
    for( i in {
      submit: 1,
      change: 1,
      focusin: 1
    }) {
      eventName = "on" + i;
      isSupported = ( eventName in div );
      if ( !isSupported ) {
        div.setAttribute( eventName, "return;" );
        isSupported = ( typeof div[ eventName ] === "function" );
      }
      support[ i + "Bubbles" ] = isSupported;
    }
  }

  fragment.removeChild( div );

  // Null elements to avoid leaks in IE
  fragment = select = opt = marginDiv = div = input = null;

  // 后面的测试需要等到文档加载完,需要body元素
  jQuery(function() {
    var container, outer, inner, table, td, offsetSupport,
      conMarginTop, ptlm, vb, style, html,
      body = document.getElementsByTagName("body")[0];

    // 没有body 直接return掉
    if ( !body ) {
      return;
    }

    conMarginTop = 1;
    ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;";
    vb = "visibility:hidden;border:0;";
    style = "style='" + ptlm + "border:5px solid #000;padding:0;'";
    html = "<div " + style + "><div></div></div>" +
      "<table " + style + " cellpadding='0' cellspacing='0'>" +
      "<tr><td></td></tr></table>";

    container = document.createElement("div");
    container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
    body.insertBefore( container, body.firstChild );

    // Construct the test element
    div = document.createElement("div");
    container.appendChild( div );

    // https://github.com/jquery/jquery/pull/35
		// 构建了一个table表格,一行内第一个td无内容,第二个td有内容
		// 测试这种情况下第一个td无论display是否为none都应该高度为0
		// 空td的offsetheight为0 则true
    div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
    tds = div.getElementsByTagName( "td" );
    isSupported = ( tds[ 0 ].offsetHeight === 0 );

    tds[ 0 ].style.display = "";
    tds[ 1 ].style.display = "none";

    support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );

    // 通过检测offsetWidth来判断是不是支持标准盒模型
    div.innerHTML = "";
    div.style.width = div.style.paddingLeft = "1px";
    jQuery.boxModel = support.boxModel = div.offsetWidth === 2;


    if ( typeof div.style.zoom !== "undefined" ) {
			// 测试对于inline的元素是不是正常显示
			// 通过设置div display: inline zoom: 1
			// 再去查看它的offsetWidth,正常应该为0
			// 如果inline之后还有offsetWidth则 inlineBlockNeedsLayout为true
      div.style.display = "inline";
			// 设置zoom主要是在ie环境下为元素出发hasLayout属性
      div.style.zoom = 1;
      support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 );

			// 构建宽度大于父元素的子元素,然后测试父元素的可见宽度
			// 如果父元素可见宽度增大 那么就是shrinkWrapBlock为true
      div.style.display = "";
      div.innerHTML = "<div style='width:4px;'></div>";
      support.shrinkWrapBlocks = ( div.offsetWidth !== 2 );
    }

		// ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;";
    vb = "visibility:hidden;border:0;";
    style = "style='" + ptlm + "border:5px solid #000;padding:0;'";
    html = "<div " + style + "><div></div></div>" +
      "<table " + style + " cellpadding='0' cellspacing='0'>" +
      "<tr><td></td></tr></table>";

    div.style.cssText = ptlm + vb;
    div.innerHTML = html;

    outer = div.firstChild;
    inner = outer.firstChild;
    td = outer.nextSibling.firstChild.firstChild;

    offsetSupport = {
			// ie8下offsetTop会包含父元素的上border宽度
      doesNotAddBorder: ( inner.offsetTop !== 5 ),
			// IE FireFox中td距离tr的offsetTop会包含table的上边框宽度
      doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
    };

    inner.style.position = "fixed";
    inner.style.top = "20px";

    // safari subtracts parent border width here which is 5px
		// 判断能否正确的计算fixed元素的位置
    offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
    inner.style.position = inner.style.top = "";

    outer.style.overflow = "hidden";
    outer.style.position = "relative";
		// hidden的父元素,子元素在计算offseTop时会不会减去父元素的变宽
		// 主流浏览器都是false
    offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
    // 测试body元素的offsetTop包含不包含body的外边距
		// 可是没看懂为什么和一个固定值比较
		offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );

    body.removeChild( container );
    div  = container = null;

    jQuery.extend( support, offsetSupport );
  });

  return support;
})();

总结

其实写到这里我都不知道自己写了些什么,而且这里面很多兼容性测试都是第一次听说,甚至连onfocusin都是第一次见,可能工作时间短、再加上不是to c的原因吧,对于兼容性的知识确实了解不多。这就正需要补习这方面知识了,前端创造者为了提供更完美的效果也应该使页面尽可能的兼容更多的平台。 最后再总体总结一下这里面的兼容性测试项目。至于兼容性测试之后对应的解决方案,会在之后用到的时候再分析。

测试项目 含义
dom测试
leadingWhitespace innerHTML会不会保留前导空白符
tbody 会不会为空的table增加tbody元素
htmlSerialize innerHTML能否正确序列化link标签
hrefNormalized a标签的href会不会被补为绝对路径
checkOn checkbox的value是否为on
getSetAttribute 测试getAttribute、setAttribute能否正常工作
enctype 表单是否支持enctype
html5Clone 浏览器能否正确的复制html5元素
deleteExpando 能否删除DOM元素上的属性
optSelected option默认是否选中
noCloneChecked 复制元素时、是否会复制check状态
optDisabled disable select标签时、内部option会不会一起被禁
radioValue radio的input会不会丢失value
checkClone 文档片段中能否正确复制check状态
appendChecked 已选中的check在添加到文档时,能否正确的保留状态
css测试
style inline style能不能直接element.style访问
opacity 浏览器是否支持opacity
cssFloat 能否支持style.cssFloat
盒模型测试
inlineBlockNeedsLayout display:inline; zoom: 1之后能否按照inline-block来显示
shrinkWrapBlocks ie中、拥有haslayout的固定width、height元素会不会被子元素撑开
reliableMarginRight 能否返回正确的右边距,仅chrome某个小版本有问题
reliableHiddenOffsets 空单元格可见高度是否为0
boxModel 是不是标准W3C盒子模型
doesNotAddBorder 子元素距父元素的offsetTop是否包含父元素的border-top-width
doesAddBorderForTableAndCells td距tr的offset是否包含table的border-top-width
fixedPosition 正确返回fixed元素的窗口坐标
subtractsBorderForOverflowNotVisible 父元素overflow:hidden,子元素的offsetTop减去父元素的border-width
doesNotIncludeMarginInBodyOffset body距html边框的距离是否包括body的margin
事件测试
noCloneEvent 复制元素时是否复制事件
submitBubbles submit事件能正常冒泡
changeBubbles change事件能正常冒泡
focusinBubbles focusin事件能正常冒泡

未来还有很长的路要走,兼容是不可回避的问题

关于new的认识、附带lazyman和currying的理解

关于new的认识、附带lazyman和currying的理解

最近在知乎看到了关于this的一个讨论和一篇简短精炼的专栏文章,在这里面确实加深了对this的理解,也写点什么记录巩固一下。文章里面也附带了两个小案例,都是之前碰到了,也不知道怎么在看文章时就联想到了,就又拿出来翻了翻,自己也实现了一下。

new

但是记忆里好像也知道new是如何使用的,在学习原型链的时候也知道new会起一些作用,但是没有专门理解过new的具体做法。 于是借着这个引子就仔细的看了一下大家的答案和文章。

在js中new确实是一个神奇的存在,大家都知道js不是真正的面向对象,然而在java这类语言中,new是一个通用的用来实例对象的关键字。用new实例后产生的新对象会继承类的一些属性和方法。

js中(es6之前)压根也没有类的概念,那new到底做了些什么呢。其实我们用new的都知道,new在js中也有实现继承的概念。上代码

function f( ) {
	this.x = 100;
}
let _f = new f( )
_f.x // 100

这样看来,new也起到了类似继承的概念,而且我们实践中告诉我们,如果一个函数(构造函数)是用来被new的,内部是可以放心使用this的,但是普通执行函数我们是不会在内部使用this的。

但是这是为什么呢,为什么构造函数内部使用this就有意义,普通函数内部的this就没意义呢。下面内容辅以看到的文章,再加上一点自己的理解,如有错误,轻喷。

this在什么情况下可以使用呢,我们知道this指向一个对象。如果在浏览器直接输出this,那么就是Window。如果是一个普通函数执行过程中输出this,就是执行环境。 如下面代码。 相信在像我这样的初级选手都会踩到这个坑,在不同的情况下,函数内的this指向不同的对象,因此有时先辈们才会发明出用self、that临时保存this的方法。

obj = {
	a: function(){
		console.log(this);
	}
}
obj.a( )   // Object {a: function}

现在可以得到一个共识,也是后面内容的基础,那就是this指向的是一个对象, 可是我们在书写构造函数的时候,没有声明this啊,而且this也不指向当前的运行环境。于是我们有了下面一个测试。

window.test;     // undefined
function f ( ){
	window.test == null;
	window.test = this;
}
f( );
window.test ;  // Window

let a = new f( );
window.test  // { }
a // {}
a === window.test   // true

神奇吗, 这段代码我们就可以知道了, 如果对一个函数使用new时,内部的this将不再指向执行环境,而是执行了一个不知道哪里来的对象,而且!!!最后这个不知道哪里来的对象还被return出去了。 我们通过 === 可以得出这个结论。

在做下一个测试,关于继承的。我们都知道js的继承是靠原型链来继承。在chrome下查看函数的原型链是func.prototype,查看对象的原型链是obj.__proto__

function f(){
}
f.prototype.test = function( ) {
	return ‘test’;
}
let a = new f ( );
a.test( )   // ‘test’
a.__proto__    // Object {test: function, constructor: function}
f.prototype == a.__proto__     // true

以上也证明了,在new过成功,f.prototype属性被赋给了 a.proto

因此 我们可以这样理解new
let a = new f( ) ===

let instance = {};
f.apply(instance);
instance.__proto__ = f.prototype;
a = instance;

这样看来也就能解释之前的两个问题?

  • this指向的这个对象不是飞来的,是new帮我们新建的,假设名为insatnce
  • 在执行构造函数时,this确实不指向运行环境,使用了类似于bind、apply、call的方法使其运行上下文变为刚刚新建的instance
  • 另一方面new将构造函数的原型链赋值给了新建的instance,主意是引用赋值哦

这样一看好像new所做的事情其实就是帮我们简写了代码量。 很不错,可能我们自己也能实现一个new关键字。嘻嘻。

new注意事项

  • 在zhihu中还看到了对new Array(1, 2)和Array(1, 2)的讨论,翻了一下es标准

    The Array constructor is the %Array% intrinsic object and the initial value of the Array property of the global object. When called as a constructor it creates and initializes a new exotic Array object. When Array is called as a function rather than as a constructor, it also creates and initializes a new Array object. Thus the function call Array(…) is equivalent to the object creation expression new Array(…) with the same arguments.

    原文大概意思就是对于Array这个方法来说,new和直接调用做的事情其实都是new, 神奇吧。如果自己实现一个这样的方法也其实很简单啊,

    // 真正做事情的其实是_Array而已
    function Array(){
    	return new _Array(arguments)
    }
    
  • 构造函数内部有返回值的情况
    这个可以测试一下

     // 证明构造函数返回值为引用类型时,直接返回函数的引用值,不会为其增加原型链信息。然而,window.self对象上面还是会有原型链的属性,也就是说刚才那个模拟的过程在return时候出来那么一点点小问题。
     function ref(){
     	window.self = this;
     	return { a: 1}
     }
     ref.prototype.test = 2
     let a = new ref( ); 
    
     // 证明构造函数返回值为原始类型时,这个return可以忽略,最开始的模拟过程还是无懈可击。
     function ref(){
     	window.self = this;
     	return 100
     }
     ref.prototype.test = 2
     let a = new ref( ); 
    

new的最终模拟过程

function newLike( fn, arguments ){
	let instance = {}; // 新建模拟的this对象
	instance.__proto__ = fn.prototype; // 为新建的模拟this对象设置原型链
	let temp = fn.apply(instance, Array.prototype.slice(arguments)); 
	if( !temp){  // 如果fn没有返回值
		return instance
	} else if( typeof temp === ‘object’) {  // 如果是引用类型
		return temp
	} else {   // 原始类型 返回刚刚模拟的this
		return instance
	}
}

优化一下

// 当然最后这个if可以更精简
if (typeof temp === ‘object’){
	return temp
   }
 return instance

new结束语

new其实做的事情我们自己也可以做出来哦,是不是感觉很赞,哪有什么结束语,不看内容看毛线?

lazyman

反正就突然想到了之前看过的这个题目。

实现一个LazyMan,可以按照以下方式调用:
LazyMan(“Hank”)输出:
Hi! This is Hank!
LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
Hi! This is Hank!
//等待10秒.
Wake up after 10
Eat dinner~

分析

这个题目很像jQuery,链式写法。ps:不知道为啥最近看啥都能想到jQuery,可能是在看源码吧,也可能jQuery对前端影响确实很深。这一点就告诉我们每次返回this大概能实现这个写法。然后这里面有一个很重要的内容就是sleep(10)要停顿10秒,js里面关于时间的就俩函数setTimeoutsetInterval,可能是setTimeout喽。也就是说要有一个方法能让她在setTimeout结束后再执行下一个任务,类似于一个总调度师。
于是大概就能知道了。

代码

最近的学习确实没白瞎,第一时间就想到了lazyMan只是一个表面函数,内部需要用new来实现,用一个任务管理器tasks来维护所有任务,在每个任务结束时执行next方法,所有方法都是通过new来实现继承的。最后链接中给出的答案可能更优,用的方法更简单比如 Array.prototype.shfit,日常不太常用,没想到。

function lazyMan(name){
  return new _lazyMan(name);
}
function _lazyMan(name){
  this.tasks = [];
  console.log(`hello ${name}`);
  setTimeout(()=>{
	this.next();
  },0)
}
_lazyMan.prototype.sleep =function(times){
  let that = this;
  let fn = function(){
	setTimeout(function(){
	  console.log(`walk up after ${times}s`);
	  that.next();
	}, times * 1000);
  }
  this.tasks.push(fn)
  return this;
}
_lazyMan.prototype.eat =function(food){
  let that = this;
  let fn = function(){
   console.log(`eat ${food}`);
   that.next();
  }
  this.tasks.push(fn)
  return this;
}
_lazyMan.prototype.next = function(){
  let task = this.tasks[0];
  if (task){
	this.tasks.splice(0,1);
	task();
  }
}
lazyMan('w').eat('a').sleep(10).eat('s')

柯理化

柯理化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

其实这次要讲的也不是柯理化,只是恰巧算是柯理化而已。

实现一个函数,按要求实现调用
f(1, 2) // 3
f(1, 2)(2) // 5
f(2)(2, 3)(3) //10

乍一看和上面lazyMan有点类似,也是一直调用。但有本质的区别,这一次是函数的一直调用,而不再是对象方法的一直调用,直观一点就是这一次是一直()()(),lazyMan是obj.a().b().c(), 因此lazyMan每次返回的是对象,这次肯定是返回function喽,否则肯定报错, error: xxx is not a function

而且要解决一个问题就是在最后一次调用完我们要将它输出累加的结果,而不能再返回一个function。还是一步一步来看,先实现每次都能返回一个function再说。

代码

function f(){
  let all = Array.prototype.slice.apply(arguments);
  function _f(){
	all = all.concat(Array.prototype.slice.apply(arguments));
	return _f
  }
  return _f
}

其实搞定一直循环return同一个function并没有啥难度,难的是最后那个数字是什么算出来的,也就是说最后怎么触发的。 其实这里是运用了valueOf 或者 toString两个函数。
然后添加_f的代码

_f.toString = function(){
	let sum = all.reduce(function(acc, val) {
	  return acc + val;
	}, 0);
	return sum.toString();
  }
f.valueOf = function(){
	let sum = all.reduce(function(acc, val) {
	  return acc + val;
	}, 0);
	return sum;
  }

这两个函数会在类型隐式转换时调用。以完成获取累加的效果。

references

zhihu: 关于JavaScript new 的一些疑问?
zhihu专栏:JS 的 new 到底是干什么的?
lazyMan:如何实现一个 LazyMan?

curring:掌握 JavaScript 函数的柯里化

jQuery chapter8 属性操作 ----jQuery源码分析系列

chapter8 属性操作

对dom的操作才是jQuery 最擅长的事情,那么dom属性肯定是其中必不可少的一环。 jQuery中属性操作主要分为了4个部分

  • HTML属性操作
  • DOM属性操作
  • 类操作

其中属性操作部分的接口也符合jQuery的一贯思路,同一个方法既是读又是取甚至可以删。

在这里jQuery做的工作并不多,主要是针对一些特殊的属性值做了某些兼容性封装处理,绝大多数还是依靠setAttribute、getAttribute、elem.key来完成工作。 不过这里面也有不少值得学习的地方,比如~运算符和ttributeNode的运用。同时也是对HTML属性和DOM属性区分的加深。

源码也分析了这么多了,可以看出一个规律,jQuery中被我们调用的接口或者说方法、API大都是添加到jQuery.fn上面的,通过jQuery.fn.extend方法,但真正做事情的要去jQuery上面找大致同名或相似名的方法。 其实这也就是利用继承的关系,因为jQuery.fn是继承的原型链的一环。

jQuery.fn.extend({
  attr: function( name, value ) {
    // access函数比较简单,做分发操作为多个接口提供支持
    // 当没有value时,返回第一个元素的属性值
    // 当有value时,循环为每个元素都设置属性值
    return jQuery.access( this, name, value, true, jQuery.attr );
  },

  removeAttr: function( name ) {
    return this.each(function() {
      jQuery.removeAttr( this, name );
    });
  },

  prop: function( name, value ) {
    return jQuery.access( this, name, value, true, jQuery.prop );
  },

  removeProp: function( name ) {
    name = jQuery.propFix[ name ] || name;
    return this.each(function() {
      // try/catch handles cases where IE balks (such as removing a property on window)
      // ie在do元素上删除属性会报错 所以先设undefined再删除
      try {
        this[ name ] = undefined;
        delete this[ name ];
      } catch( e ) {}
    });
  },

  // addClass逻辑简单,代码也比较简单
  // 如果原本没有类,则直接设置新类
  // 如果原来有 则匹配新的类,有就忽略,没有就添加到最后
  // 里面用到了 !~ 操作符合
  // !就是简单的取反
  // ~是先变换符号再减1
  addClass: function( value ) {
    var classNames, i, l, elem,
      setClass, c, cl;

    // 如果是传入的时方法
    // 为每个元素调用方法之后再执行addClass
    if ( jQuery.isFunction( value ) ) {
      return this.each(function( j ) {
        jQuery( this ).addClass( value.call(this, j, this.className) );
      });
    }

    if ( value && typeof value === "string" ) {
      classNames = value.split( rspace );

      for ( i = 0, l = this.length; i < l; i++ ) {
        elem = this[ i ];

        if ( elem.nodeType === 1 ) {
          if ( !elem.className && classNames.length === 1 ) {
            elem.className = value;

          } else {
            setClass = " " + elem.className + " ";

            for ( c = 0, cl = classNames.length; c < cl; c++ ) {
              if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
                setClass += classNames[ c ] + " ";
              }
            }
            elem.className = jQuery.trim( setClass );
          }
        }
      }
    }

    return this;
  },

  // 内部用正则的方式去替换需要删掉的类名
  removeClass: function( value ) {
    var classNames, i, l, elem, className, c, cl;

    if ( jQuery.isFunction( value ) ) {
      return this.each(function( j ) {
        jQuery( this ).removeClass( value.call(this, j, this.className) );
      });
    }

    if ( (value && typeof value === "string") || value === undefined ) {
      classNames = ( value || "" ).split( rspace );

      for ( i = 0, l = this.length; i < l; i++ ) {
        elem = this[ i ];

        if ( elem.nodeType === 1 && elem.className ) {
          if ( value ) {
            className = (" " + elem.className + " ").replace( rclass, " " );
            for ( c = 0, cl = classNames.length; c < cl; c++ ) {
              className = className.replace(" " + classNames[ c ] + " ", " ");
            }
            elem.className = jQuery.trim( className );

          } else {
            elem.className = "";
          }
        }
      }
    }

    return this;
  },

  // 顾名思义, 切换类名
  // 最常用在动画中 比如展开某动画的时候需要添加类 .slider-show
  // 关闭的时候需要类名 .slider-hidden  两者应该只保留一个 也就是一直在切换
  toggleClass: function( value, stateVal ) {
    var type = typeof value,
      isBool = typeof stateVal === "boolean";

    if ( jQuery.isFunction( value ) ) {
      return this.each(function( i ) {
        jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
      });
    }

    return this.each(function() {
      if ( type === "string" ) {
        // toggle individual class names
        var className,
          i = 0,
          self = jQuery( this ),
          state = stateVal,
          classNames = value.split( rspace );
        
        // 原本有则删除、原本没有则添加
        while ( (className = classNames[ i++ ]) ) {
          // check each className given, space seperated list
          state = isBool ? state : !self.hasClass( className );
          self[ state ? "addClass" : "removeClass" ]( className );
        }

      } else if ( type === "undefined" || type === "boolean" ) {
        // 需要借助缓存系统来实现缓存, 在全部替换的时候再取出来
        if ( this.className ) {
          // store className if set
          jQuery._data( this, "__className__", this.className );
        }

        // toggle whole className
        this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
      }
    });
  },
  
  // 判断一个元素是否含有某个类名
  hasClass: function( selector ) {
    var className = " " + selector + " ",
      i = 0,
      l = this.length;
    for ( ; i < l; i++ ) {
      if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
        return true;
      }
    }

    return false;
  },

  // 对elem.value的一个封装
  val: function( value ) {
    var hooks, ret, isFunction,
      elem = this[0];
    // 没有argumentts表示获取第一个元素的value值
    // 有hook时优先使用hook的get方法,没有再普通的elem.value
    if ( !arguments.length ) {
      if ( elem ) {
        hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];

        if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
          return ret;
        }

        ret = elem.value;

        return typeof ret === "string" ?
          // handle most common string cases
          ret.replace(rreturn, "") :
          // handle cases where value is null/undef or number
          ret == null ? "" : ret;
      }

      return;
    }

    isFunction = jQuery.isFunction( value );
    // 先修正参数, 保证是字符串
    // 再hook。set 最后 elem.value 
    return this.each(function( i ) {
      var self = jQuery(this), val;

      if ( this.nodeType !== 1 ) {
        return;
      }

      if ( isFunction ) {
        val = value.call( this, i, self.val() );
      } else {
        val = value;
      }

      // Treat null/undefined as ""; convert numbers to string
      if ( val == null ) {
        val = "";
      } else if ( typeof val === "number" ) {
        val += "";
      } else if ( jQuery.isArray( val ) ) {
        val = jQuery.map(val, function ( value ) {
          return value == null ? "" : value + "";
        });
      }

      hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];

      // If set returns undefined, fall back to normal setting
      if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
        this.value = val;
      }
    });
  }
});

// 
jQuery.extend({
  valHooks: {
    // option元素的兼容性修复
    option: {
      get: function( elem ) {
        // attributes.value is undefined in Blackberry 4.7 but
        // uses .value. See #6932
        var val = elem.attributes.value;
        return !val || val.specified ? elem.value : elem.text;
      }
    },
    select: {
      // select有单选、多选
      // 内部option还有被disabled的情况,需要单独处理, 逻辑不复杂 代码也简单
      get: function( elem ) {
        var value, i, max, option,
          index = elem.selectedIndex,
          values = [],
          options = elem.options,
          one = elem.type === "select-one";

        // Nothing was selected
        if ( index < 0 ) {
          return null;
        }

        // Loop through all the selected options
        i = one ? index : 0;
        max = one ? index + 1 : options.length;
        for ( ; i < max; i++ ) {
          option = options[ i ];

          // Don't return options that are disabled or in a disabled optgroup
          if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
              (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {

            // Get the specific value for the option
            value = jQuery( option ).val();

            // We don't need an array for one selects
            if ( one ) {
              return value;
            }

            // Multi-Selects return an array
            values.push( value );
          }
        }

        // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
        if ( one && !values.length && options.length ) {
          return jQuery( options[ index ] ).val();
        }

        return values;
      },
      // 把value组成一个数组
      // 循环所有option逐个修改selected值
      // 如果没有value则设置selectedIndex为-1
      set: function( elem, value ) {
        var values = jQuery.makeArray( value );

        jQuery(elem).find("option").each(function() {
          this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
        });

        if ( !values.length ) {
          elem.selectedIndex = -1;
        }
        return values;
      }
    }
  },

  attrFn: {
    val: true,
    css: true,
    html: true,
    text: true,
    data: true,
    width: true,
    height: true,
    offset: true
  },

  attr: function( elem, name, value, pass ) {
    var ret, hooks, notxml,
      nType = elem.nodeType;

    // 不是真正的dom元素 直接返回
    // 不知道为什么不直接判断nType === 1\9\11
    // 其余的nodetype 并不常用,不太熟悉
    if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
      return;
    }
    // 如果是几个特殊的属性值,那么直接调用对应的方法设置,有专用的方法
    // 这几个属性分别是val、css、html、text、da、ta、width、height、offset
    if ( pass && name in jQuery.attrFn ) {
      return jQuery( elem )[ name ]( value );
    }

    // 如果不支持getAttribute属性,则调用以一种思路
    // jQuery.prop
    if ( typeof elem.getAttribute === "undefined" ) {
      return jQuery.prop( elem, name, value );
    }

    // 如果元素不是最普通的dom元素 或者是当前是html环境
    notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

    // 属性值必须替换为小写,因为html属性不支持大写
    // 又是一个修正部分,稍后解释
    if ( notxml ) {
      name = name.toLowerCase();
      hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
    }

    // value不是undefined表明不是读取
    if ( value !== undefined ) {

      // value是null表示移除属性
      if ( value === null ) {
        jQuery.removeAttr( elem, name );
        return;
        // 如果有hooks并且hooks有set方法 并且设置后返回值不是undefined 则表示设置成功啦
      } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
        return ret;

      } else {
        // 最普通的也是最后的选项就是setAttribute设置属性
        elem.setAttribute( name, "" + value );
        return value;
      }
      // 一样的表示读取属性
    } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
      return ret;

    } else {
      // 最终的读取属性getAttribute
      ret = elem.getAttribute( name );

      // 读取属性我们不返回null,返回undefined
      return ret === null ?
        undefined :
        ret;
    }
  },

  removeAttr: function( elem, value ) {
    var propName, attrNames, name, l,
      i = 0;

    if ( value && elem.nodeType === 1 ) {
      // 	rspace = /\s+/,
      // 按照空格开划分, 支持一次移除多个属性
      attrNames = value.toLowerCase().split( rspace );
      l = attrNames.length;

      for ( ; i < l; i++ ) {
        name = attrNames[ i ];

        if ( name ) {
          propName = jQuery.propFix[ name ] || name;

          // 在移除属性之前,先将属性设置为空字符串 解决webkit无法移除style的问题
          // See #9699 for explanation of this approach (setting first, then removal)
          jQuery.attr( elem, name, "" );
          elem.removeAttribute( getSetAttribute ? name : propName );

          // Set corresponding property to false for boolean attributes
          if ( rboolean.test( name ) && propName in elem ) {
            elem[ propName ] = false;
          }
        }
      }
    }
  },

  // 这里的attrHooks并不完整
  // 还包括 tabIndex、width、height、contenteditable
  // 主要实现针对几个特殊html属性值的读取和设置,因为特殊
  // 并且每个属性都有set和get对应方法
  attrHooks: {
    type: {
      set: function( elem, value ) {
        // We can't allow the type property to be changed (since it causes problems in IE)
        // 在ie9以下如果检测到要修改的事input或者button并且还有父元素,那么不允许修改type
        // rtype = /^(?:button|input)$/i, 
        if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
          jQuery.error( "type property can't be changed" );
        } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
          //  之前jQuery.support.radioValue如果为false表示设置type为radio会让value丢失,那么就应该先备份,再设置tyeop,最后再恢复value
          // Setting the type on a radio button after the value resets the value in IE6-9
          // Reset value to it's default in case type is set after value
          // This is for element creation
          var val = elem.value;
          elem.setAttribute( "type", value );
          if ( val ) {
            elem.value = val;
          }
          return value;
        }
      }
    },
    // Use the value property for back compat
    // Use the nodeHook for button elements in IE6/7 (#1954)
    // 在这里优先使用nodeHook来进行设置或读取
    value: {
      get: function( elem, name ) {
        if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
          return nodeHook.get( elem, name );
        }
        return name in elem ?
          elem.value :
          null;
      },
      set: function( elem, value, name ) {
        if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
          return nodeHook.set( elem, value, name );
        }
        // Does not return so that setAttribute is also used
        elem.value = value;
      }
    }
  },

  propFix: {
    tabindex: "tabIndex",
    readonly: "readOnly",
    "for": "htmlFor",
    "class": "className",
    maxlength: "maxLength",
    cellspacing: "cellSpacing",
    cellpadding: "cellPadding",
    rowspan: "rowSpan",
    colspan: "colSpan",
    usemap: "useMap",
    frameborder: "frameBorder",
    contenteditable: "contentEditable"
  },

  // dom属性处理与html属性处理非常类似
  // 某些类型节点不处理
  // 优先采用有hook的方法,再采用通用方法
  prop: function( elem, name, value ) {
    var ret, hooks, notxml,
      nType = elem.nodeType;

    // don't get/set properties on text, comment and attribute nodes
    // 与html属性处理一致,忽略注释、文本、属性节点
    if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
      return;
    }

    notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

    if ( notxml ) {
      // Fix name and attach hooks
      name = jQuery.propFix[ name ] || name;
      hooks = jQuery.propHooks[ name ];
    }

    if ( value !== undefined ) {
      if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
        return ret;

      } else {
        return ( elem[ name ] = value );
      }

    } else {
      if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
        return ret;

      } else {
        return elem[ name ];
      }
    }
  },

  propHooks: {
    tabIndex: {
      get: function( elem ) {
        // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
        // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
        // 上面的链接说明了直接读取tabIndex并不能保证返回正确值,需要使用属性节点来保证正确性
        // 如果有返回值则以10进制形式返回,否则再检测元素是不是可点击或者可聚焦的元素,如果满足条件则默认其tabIndex为0, 其余的应该是undefined
        var attributeNode = elem.getAttributeNode("tabindex");
        // 	rfocusable = /^(?:button|input|object|select|textarea)$/i,
        // 	rclickable = /^a(?:rea)?$/i,
        return attributeNode && attributeNode.specified ?
          parseInt( attributeNode.value, 10 ) :
          rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
            0 :
            undefined;
      }
    }
  }
});

几个辅助函数

// 用于检测属性值是不是属于boolean类型的
rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i;
// Hook for boolean attributes
boolHook = {
  get: function( elem, name ) {
    // 先利用jQuery.prop去读取属性值, 入锅味true 直接返回属性名称
    // 否则再利用getAttributeNode方法去读取属性值
    // 辱国属性是true则返回属性名,否则返回undefined
    var attrNode,
      property = jQuery.prop( elem, name );
    return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
      name.toLowerCase() :
      undefined;
  },
  set: function( elem, value, name ) {
    var propName;
    if ( value === false ) {
      // Remove boolean attributes when set to false
      jQuery.removeAttr( elem, name );
    } else {
      // 如果属性是某些特殊属性,还需要设置其相关属性一起为true
      // value is true since we know at this point it's type boolean and not false
      // Set boolean attributes to the same name and set the DOM property
      propName = jQuery.propFix[ name ] || name;
      if ( propName in elem ) {
        // Only set the IDL specifically if it already exists on the element
        elem[ propName ] = true;
      }

      elem.setAttribute( name, name.toLowerCase() );
    }
    return name;
  }
};


// jquery初始化时,如果setAttribute为false,则会保留nodeHook
// 目的就是来替代setAttribute和getAttribute、removeAttribute
// 主要是通过setAttributeNode与getAttributeNode的封装来达到setAttribute与getAttribute的效果
nodeHook = jQuery.valHooks.button = {
  get: function( elem, name ) {
    var ret;
    ret = elem.getAttributeNode( name );
    return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
      ret.nodeValue :
      undefined;
  },
  set: function( elem, value, name ) {
    // Set the existing or create a new attribute node
    var ret = elem.getAttributeNode( name );
    if ( !ret ) {
      ret = document.createAttribute( name );
      elem.setAttributeNode( ret );
    }
    return ( ret.nodeValue = value + "" );
  }
};

jQuery chapter7 事件系统 ----jQuery源码分析系列

jQuery事件系统

jQuery的事件系统以之前分析过的数据缓存系统为基础。之前也分析过,数据缓存部分可以为每个DOM元素、甚至普通的JS对象都增加缓存属性,缓存属性中只有一个区域存放自定义的数据,而系统内置的缺占据了很多缓存区域。 事件系统就是一个占据区域的大户。

当我们把事件添加到某个DOM元素上面的时候,例如click事件,真正被添加到系统addeventListener的不是我们传入的那个函数,而是jQuery帮我们代理的一个handler函数。而且我们在使用过程中,也不再使用系统的event对象,而是使用jQuery封装过的jQuery.event对象,这个对象抹平了很多浏览器之间的差异。

当事件触发的时候,我们可以看到我们注册到dom元素的那个事件其实是一个dispatch事件---也就是做事件分发的工作,它的任务就是在缓存数据中,找到对应类型的缓存事件数组,然后依次执行。这样看来,dom元素上面,同一类型的事件只会被绑定一个函数,也就是dispatch方法,当事件被触发,就去数据里面找相对应的回调函数队列。

事件主动触发又做了哪些工作呢。事件在主动触发的时候,jQuery还模拟了事件冒泡的过程,毕竟这在浏览器中还是一个不小的需求,事件代理不就是那么完成的吗。事件冒泡思路并不难,把所有父元素一直记录下来组成冒泡路径,然后依次检测是不是有同类型的监听事件,有的话执行,没有的话也没办法喽。

事件移除这时候也就能大概想明白了,应该也不会是真正的移除某个dom监听,肯定也是移除的缓存数据中的那个监听函数,当然如果缓存对象为空了,可以相对应的移除掉某事件的真正绑定的dispatch函数,甚至整个dom对应的缓存数据。

这里就有了一个问题,平时如果我们自定义事件,触发它是一件麻烦的事件,因为你要new Event('custome'); dispatch('custome'),但是在jQuerry这里没有这个概念了,因为事件不是真正绑定在DOM上面,而是放在了缓存数据里面,自定义的事件,你只能主动触发,主动触发的时候dom就只是一座桥梁,以寻找对应的缓存数据而已,找到数据 则就是纯js的函数执行问题了。

在整个jQuery事件系统中, jqeury帮我们封装了一个jQuey.Event对象,这个对象磨平了很多浏览器之间的差异,也相当于做了一个统一规范,这样既帮助于内部个函数、接口直接的掉哟过,也有助于我们更好的拿到更统一的数据。

我们还可以发现ready方法竟然不在event部分代码里面,ready又单独拎出来了。
ready那边其实挺神奇的就是在监听了一个DOMContentReady事件,所以他才会比我们平时使用的load事件快,因为load是要等各类资源加载完毕才会触发,如果是淘宝页面,那图片加载的酸爽。 ready部分的代码注释的相当完备,也比较简单。

/*
 * Helper functions for managing events -- not part of the public interface.
 * Props to Dean Edwards' addEvent library for many of the ideas.
 */
jQuery.event = {

	add: function( elem, types, handler, data, selector ) {

		var elemData, eventHandle, events,
			t, tns, type, namespaces, handleObj,
			handleObjIn, quick, handlers, special;

		// 不为某些不支持的元素增加响应事件,参数不完整也不增加事件
		if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
			return;
		}

		// Caller can pass in an object of custom data in lieu of the handler
    // 处理一种特殊情况的监听函数
    // 它本身是一个自定义监听对象, 用两个变量来标示一下
		if ( handler.handler ) {
			handleObjIn = handler;
			handler = handleObjIn.handler;
		}

		// handler没有guid就增加一个
		if ( !handler.guid ) {
			handler.guid = jQuery.guid++;
		}

		// 取出或初始化dom元素的 events 数据缓存结构
		events = elemData.events;
		if ( !events ) {
			elemData.events = events = {};
		}
    // 取出或初始化dom元素的主监听函数 handle
		eventHandle = elemData.handle;
		if ( !eventHandle ) {
      // 看得出来这个 主监听函数其实是一个 简单的对jQuery.event.dispatch的封装
			elemData.handle = eventHandle = function( e ) {
				// Discard the second event of a jQuery.event.trigger() and
				// when an event is called after a page has unloaded
				return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
					undefined;
			};
			// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
			eventHandle.elem = elem;
		}

		// 处理多个事件类型 空格 隔开的绑定情况
		types = jQuery.trim( hoverHack(types) ).split( " " );
		for ( t = 0; t < types.length; t++ ) {

			tns = rtypenamespace.exec( types[t] ) || [];
			type = tns[1];
			namespaces = ( tns[2] || "" ).split( "." ).sort();

			// If event changes its type, use the special event handlers for the changed type
			special = jQuery.event.special[ type ] || {};

			// If selector defined, determine special event api type, otherwise given type
			type = ( selector ? special.delegateType : special.bindType ) || type;

			// Update special based on newly reset type
			special = jQuery.event.special[ type ] || {};

			// 把监听函数封装成了监听对象
			handleObj = jQuery.extend({
				type: type,
				origType: tns[1],
				data: data,
				handler: handler,
				guid: handler.guid,
				selector: selector,
				quick: quickParse( selector ),
				namespace: namespaces.join(".")
			}, handleObjIn );

			// 如果当前 dom数据缓存中没有对应的 事件类型,则初始化
			handlers = events[ type ];
			if ( !handlers ) {
				handlers = events[ type ] = [];
				handlers.delegateCount = 0;

				// 当没有特殊情况的时候,设置当前type事件的监听函数为 主监听函数handle
				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
					// Bind the global event handler to the element
					if ( elem.addEventListener ) {
						elem.addEventListener( type, eventHandle, false );

					} else if ( elem.attachEvent ) {
						elem.attachEvent( "on" + type, eventHandle );
					}
				}
			}

			if ( special.add ) {
				special.add.call( elem, handleObj );

				if ( !handleObj.handler.guid ) {
					handleObj.handler.guid = handler.guid;
				}
			}

			// 把真正的 监听函数 压入队列之中
			if ( selector ) {
				handlers.splice( handlers.delegateCount++, 0, handleObj );
			} else {
				handlers.push( handleObj );
			}

			// Keep track of which events have ever been used, for event optimization
			jQuery.event.global[ type ] = true;
		}

		// Nullify elem to prevent memory leaks in IE
		elem = null;
	},

	global: {},

	// Detach an event or set of events from an element
	remove: function( elem, types, handler, selector, mappedTypes ) {

		var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
			t, tns, type, origType, namespaces, origCount,
			j, events, special, handle, eventType, handleObj;

    // 当前dom元素上没有任何关联的缓存数据,也就没有任何缓存事件, 那么直接return
		if ( !elemData || !(events = elemData.events) ) {
			return;
		}

		// Once for each type.namespace in types; type may be omitted
    // 拆解types, 因为支持命名空间和一次移除多个事件类型
		types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
		for ( t = 0; t < types.length; t++ ) {
      // 拆解命名空间
			tns = rtypenamespace.exec( types[t] ) || [];
			type = origType = tns[1];
			namespaces = tns[2];

			// Unbind all events (on this namespace, if provided) for the element
      // 如果没有type 则表示需要移除所有事件类型
			if ( !type ) {
				for ( type in events ) {
					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
				}
				continue;
			}

			special = jQuery.event.special[ type ] || {};
			type = ( selector? special.delegateType : special.bindType ) || type;
			eventType = events[ type ] || [];
			origCount = eventType.length;
			namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;

			// Remove matching events
      // 循环匹配找哪个事件应该被删除
			for ( j = 0; j < eventType.length; j++ ) {
				handleObj = eventType[ j ];

				if ( ( mappedTypes || origType === handleObj.origType ) &&
					 ( !handler || handler.guid === handleObj.guid ) &&
					 ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
					 ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
					eventType.splice( j--, 1 );

					if ( handleObj.selector ) {
						eventType.delegateCount--;
					}
					if ( special.remove ) {
						special.remove.call( elem, handleObj );
					}
				}
			}

			// Remove generic event handler if we removed something and no more handlers exist
			// (avoids potential for endless recursion during removal of special event handlers)
      // 如果监听对象数组唯恐,则移除主监听函数
			if ( eventType.length === 0 && origCount !== eventType.length ) {
				if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
					jQuery.removeEvent( elem, type, elemData.handle );
				}

				delete events[ type ];
			}
		}

    // 如果没有任何事件了,那么直接删除缓存数据
		// Remove the expando if it's no longer used
		if ( jQuery.isEmptyObject( events ) ) {
			handle = elemData.handle;
			if ( handle ) {
				handle.elem = null;
			}

			// removeData also checks for emptiness and clears the expando if empty
			// so use it instead of delete
			jQuery.removeData( elem, [ "events", "handle" ], true );
		}
	},

	// Events that are safe to short-circuit if no handlers are attached.
	// Native DOM events should not be added, they may have inline handlers.
	customEvent: {
		"getData": true,
		"setData": true,
		"changeData": true
	},

	trigger: function( event, data, elem, onlyHandlers ) {
		// Don't do events on text and comment nodes
    // 过滤不正确的参数情况
		if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
			return;
		}

		// Event object or event type
		var type = event.type || event,
			namespaces = [],
			cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;

		// focus/blur morphs to focusin/out; ensure we're not firing them right now
    // 如果正在处罚focus\blur事件默认行为,浏览器应该自动触发focusin\focusout
    // 这里先过滤掉 待会再统一补齐
		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
			return;
		}

    // 修正参数  命名空间和!的传入
		if ( type.indexOf( "!" ) >= 0 ) {
			// Exclusive events trigger only for the exact event (no namespaces)
			type = type.slice(0, -1);
			exclusive = true;
		}

		if ( type.indexOf( "." ) >= 0 ) {
			// Namespaced trigger; create a regexp to match event type in handle()
			namespaces = type.split(".");
			type = namespaces.shift();
			namespaces.sort();
		}

    // 阻止两类不对的触发事件
		if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
			// No jQuery handlers for this event type, and it can't have inline handlers
			return;
		}

    // 修正为jquery.Event对象
		// Caller can pass in an Event, Object, or just an event type string
		event = typeof event === "object" ?
			// jQuery.Event object
			event[ jQuery.expando ] ? event :
			// Object literal
			new jQuery.Event( type, event ) :
			// Just the event type (string)
			new jQuery.Event( type );

		event.type = type;
		event.isTrigger = true;
		event.exclusive = exclusive;
		event.namespace = namespaces.join( "." );
		event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
		ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";

		// Handle a global trigger
    // 如果没有elem标示触发所有同类事件
    // 只能循环缓存数据对象,逐个触发
		if ( !elem ) {

			// TODO: Stop taunting the data cache; remove global events and always attach to document
			cache = jQuery.cache;
			for ( i in cache ) {
				if ( cache[ i ].events && cache[ i ].events[ type ] ) {
					jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
				}
			}
			return;
		}

    // 继续休整几个参数
		// Clean up the event in case it is being reused
		event.result = undefined;
		if ( !event.target ) {
			event.target = elem;
		}

		// Clone any incoming data and prepend the event, creating the handler arg list
		data = data != null ? jQuery.makeArray( data ) : [];
		data.unshift( event );

		// Allow special events to draw outside the lines
		special = jQuery.event.special[ type ] || {};
		if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
			return;
		}

		// Determine event propagation path in advance, per W3C events spec (#9951)
		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
    // 因为模拟触发事件, 是需要模拟冒泡过程的,但是jQuery这个不会有自动的冒泡
    // 冒泡也比较容易模拟, 就一直找父元素就好了,父元素在查看有没有缓存数据
		eventPath = [[ elem, special.bindType || type ]];
		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {

			bubbleType = special.delegateType || type;
			cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
			old = null;
			for ( ; cur; cur = cur.parentNode ) {
				eventPath.push([ cur, bubbleType ]);
				old = cur;
			}

			// Only add window if we got to document (e.g., not plain obj or detached DOM)
			if ( old && old === elem.ownerDocument ) {
				eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
			}
		}


    // 利用刚刚构建好的冒泡路径 再逐一的触发元素上面的绑定事件 如果有的话,并且不能是阻止冒泡了的
		// Fire handlers on the event path
		for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {

			cur = eventPath[i][0];
			event.type = eventPath[i][1];

			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
			if ( handle ) {
				handle.apply( cur, data );
			}
			// Note that this is a bare JS function and not a jQuery handler
			handle = ontype && cur[ ontype ];
			if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
				event.preventDefault();
			}
		}
		event.type = type;

		// If nobody prevented the default action, do it now
    // 如果没有阻止默认行为,则最后执行默认行为 
    // 默认行为都是几个比较特殊的
		if ( !onlyHandlers && !event.isDefaultPrevented() ) {

			if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
				!(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {

				// Call a native DOM method on the target with the same name name as the event.
				// Can't use an .isFunction() check here because IE6/7 fails that test.
				// Don't do default actions on window, that's where global variables be (#6170)
				// IE<9 dies on focus/blur to hidden element (#1486)
				if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {

					// Don't re-trigger an onFOO event when we call its FOO() method
					old = elem[ ontype ];

					if ( old ) {
						elem[ ontype ] = null;
					}

					// Prevent re-triggering of the same event, since we already bubbled it above
					jQuery.event.triggered = type;
					elem[ type ]();
					jQuery.event.triggered = undefined;

					if ( old ) {
						elem[ ontype ] = old;
					}
				}
			}
		}

		return event.result;
	},

	dispatch: function( event ) {

		// Make a writable jQuery.Event from the native event object
    // 包装一个jQuery.event, 如果已经是jQuery.event则不需要包装
		event = jQuery.event.fix( event || window.event );
    // 拿到当前事件类型对应的监听对象数组
		var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
			delegateCount = handlers.delegateCount,
			args = [].slice.call( arguments, 0 ),
			run_all = !event.exclusive && !event.namespace,
			handlerQueue = [],
			i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;

		// Use the fix-ed jQuery.Event rather than the (read-only) native event
		args[0] = event;
		event.delegateTarget = this;

		// Determine handlers that should run if there are delegated events
		// Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861)
		if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) {

			// Pregenerate a single jQuery object for reuse with .is()
			jqcur = jQuery(this);
			jqcur.context = this.ownerDocument || this;

			for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
				selMatch = {};
				matches = [];
				jqcur[0] = cur;
				for ( i = 0; i < delegateCount; i++ ) {
					handleObj = handlers[ i ];
					sel = handleObj.selector;

					if ( selMatch[ sel ] === undefined ) {
						selMatch[ sel ] = (
							handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
						);
					}
					if ( selMatch[ sel ] ) {
						matches.push( handleObj );
					}
				}
				if ( matches.length ) {
					handlerQueue.push({ elem: cur, matches: matches });
				}
			}
		}

		// Add the remaining (directly-bound) handlers
		if ( handlers.length > delegateCount ) {
			handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
		}

		// Run delegates first; they may want to stop propagation beneath us
		for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
			matched = handlerQueue[ i ];
			event.currentTarget = matched.elem;

			for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
				handleObj = matched.matches[ j ];

				// Triggered event must either 1) be non-exclusive and have no namespace, or
				// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
				if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {

					event.data = handleObj.data;
					event.handleObj = handleObj;

					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
							.apply( matched.elem, args );

					if ( ret !== undefined ) {
						event.result = ret;
						if ( ret === false ) {
							event.preventDefault();
							event.stopPropagation();
						}
					}
				}
			}
		}

		return event.result;
	},

	// Includes some event props shared by KeyEvent and MouseEvent
	// *** attrChange attrName relatedNode srcElement  are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
	props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),

	fixHooks: {},

	keyHooks: {
		props: "char charCode key keyCode".split(" "),
		filter: function( event, original ) {

			// 没有which,则依次想办法拿到一个值
			if ( event.which == null ) {
				event.which = original.charCode != null ? original.charCode : original.keyCode;
			}

			return event;
		}
	},

	mouseHooks: {
		props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
		filter: function( event, original ) {
			var eventDoc, doc, body,
				button = original.button,
				fromElement = original.fromElement;

			// Calculate pageX/Y if missing and clientX/Y available
			if ( event.pageX == null && original.clientX != null ) {
				eventDoc = event.target.ownerDocument || document;
				doc = eventDoc.documentElement;
				body = eventDoc.body;

				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
			}

			// Add relatedTarget, if necessary
			if ( !event.relatedTarget && fromElement ) {
				event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
			}

			// Add which for click: 1 === left; 2 === middle; 3 === right
			// Note: button is not normalized, so don't use it
			if ( !event.which && button !== undefined ) {
				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
			}

			return event;
		}
	},

  // 用于讲原生事件对象封装为jQuery事件对象
	fix: function( event ) {
    // 如果有标示表明已经是jQuery事件对象,没必要继续封装了
		if ( event[ jQuery.expando ] ) {
			return event;
		}

		// Create a writable copy of the event object and normalize some properties
		var i, prop,
			originalEvent = event,
      // fixHook 要不然就是鼠标事件mouseHooks 要不然就是 键盘事件keyHooks
			fixHook = jQuery.event.fixHooks[ event.type ] || {},
			copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;

		event = jQuery.Event( originalEvent );

    // 根据事件类型,将一些原生事件的属性赋值给jQuery事件
		for ( i = copy.length; i; ) {
			prop = copy[ --i ];
			event[ prop ] = originalEvent[ prop ];
		}

		// Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
    // event target 可能在某些版本有bug, 他还有另外一个地方可以查到就是event.srcElement
		if ( !event.target ) {
			event.target = originalEvent.srcElement || document;
		}

		// Target should not be a text node (#504, Safari)
    // 事件的target不应该是一个文本节点, 应该是dom 节点
		if ( event.target.nodeType === 3 ) {
			event.target = event.target.parentNode;
		}

		// For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)
    // metaKey 表示事件触发的时候,win/command键是否被按下。 如果没有的话就用ctrl键状态代替
		if ( event.metaKey === undefined ) {
			event.metaKey = event.ctrlKey;
		}
    // 这个ffixHook.filter可以看前面keyHooks和mouseHooks,其实就是继续修正某些转有bug
		return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
	},

	special: {
		ready: {
			// Make sure the ready event is setup
			setup: jQuery.bindReady
		},

		load: {
			// Prevent triggered image.load events from bubbling to window.load
			noBubble: true
		},

		focus: {
			delegateType: "focusin"
		},
		blur: {
			delegateType: "focusout"
		},

		beforeunload: {
			setup: function( data, namespaces, eventHandle ) {
				// We only want to do this special case on windows
				if ( jQuery.isWindow( this ) ) {
					this.onbeforeunload = eventHandle;
				}
			},

			teardown: function( namespaces, eventHandle ) {
				if ( this.onbeforeunload === eventHandle ) {
					this.onbeforeunload = null;
				}
			}
		}
	},

  // 模拟一个事件的调用 或者 冒泡过程
	simulate: function( type, elem, event, bubble ) {
		// Piggyback on a donor event to simulate a different one.
		// Fake originalEvent to avoid donor's stopPropagation, but if the
		// simulated event prevents default then we do the same on the donor.
		var e = jQuery.extend(
			new jQuery.Event(),
			event,
			{ type: type,
				isSimulated: true,
				originalEvent: {}
			}
		);
		if ( bubble ) {
			jQuery.event.trigger( e, null, elem );
		} else {
			jQuery.event.dispatch.call( elem, e );
		}
		if ( e.isDefaultPrevented() ) {
			event.preventDefault();
		}
	}
};

// Some plugins are using, but it's undocumented/deprecated and will be removed.
// The 1.7 special event interface should provide all the hooks needed now.
jQuery.event.handle = jQuery.event.dispatch;

jQuery.removeEvent = document.removeEventListener ?
	function( elem, type, handle ) {
		if ( elem.removeEventListener ) {
			elem.removeEventListener( type, handle, false );
		}
	} :
	function( elem, type, handle ) {
		if ( elem.detachEvent ) {
			elem.detachEvent( "on" + type, handle );
		}
	};

jQuery.Event = function( src, props ) {
	// Allow instantiation without the 'new' keyword
  // 这里可以省略new 一种新的写法
	if ( !(this instanceof jQuery.Event) ) {
		return new jQuery.Event( src, props );
	}

	// Event object
  // src有type属性表示传入的是原生事件类型
	if ( src && src.type ) {
    // 原生事件类型的话 做备份
		this.originalEvent = src;
		this.type = src.type;

		// isDefaultPrevented用来判断当前jQuery时间对象上是否调用过方法preventDefault
		this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
			src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;

	// Event type
	} else {
		this.type = src;
	}

	// Put explicitly provided properties onto the event object
	if ( props ) {
		jQuery.extend( this, props );
	}

	// 给新的事件增加一个时间戳
	this.timeStamp = src && src.timeStamp || jQuery.now();

	// 增加一个标记
	this[ jQuery.expando ] = true;
};

function returnFalse() {
	return false;
}
function returnTrue() {
	return true;
}

// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
  // 阻止默认行为
	preventDefault: function() {
    // 标示preventDefault函数被调用过了
		this.isDefaultPrevented = returnTrue;
    // 如果当前不是浏览器原生事件,那么不会有任何默认行为,直接return
		var e = this.originalEvent;
		if ( !e ) {
			return;
		}

    // ie9以下和标准的兼容处理,阻止默认行为
		// if preventDefault exists run it on the original event
		if ( e.preventDefault ) {
			e.preventDefault();

		// otherwise set the returnValue property of the original event to false (IE)
		} else {
			e.returnValue = false;
		}
	},
  // 阻止冒泡
	stopPropagation: function() {
    // 表示调用过阻止冒泡了
		this.isPropagationStopped = returnTrue;
    // 非原生事件不会有冒泡行为
    // 自定义事件不会进行传播
		var e = this.originalEvent;
		if ( !e ) {
			return;
		}
		// if stopPropagation exists run it on the original event
		if ( e.stopPropagation ) {
			e.stopPropagation();
		}
		// otherwise set the cancelBubble property of the original event to true (IE)
		e.cancelBubble = true;
	},
  // 模拟了原生的stopImmediatePropagation方法
  // stopImmediatePropagation的意思就是
  // 当一个元素某个事件有多个监听器时,触发后会按照先后顺序依次执行,但是如果设置了阻止冒泡,则祖先元素不会触发同类事件,但是原本的多个监听器还是会依次执行完毕。
  // 但是如果又一个监听器执行了 event.stopImmediatePropagation, 那么后续的监听器将不会继续执行
	stopImmediatePropagation: function() {
		this.isImmediatePropagationStopped = returnTrue;
		this.stopPropagation();
	},
	isDefaultPrevented: returnFalse,
	isPropagationStopped: returnFalse,
	isImmediatePropagationStopped: returnFalse
};

// Create mouseenter/leave events using mouseover/out and event-time checks
jQuery.each({
	mouseenter: "mouseover",
	mouseleave: "mouseout"
}, function( orig, fix ) {
	jQuery.event.special[ orig ] = {
		delegateType: fix,
		bindType: fix,

		handle: function( event ) {
			var target = this,
				related = event.relatedTarget,
				handleObj = event.handleObj,
				selector = handleObj.selector,
				ret;

			// For mousenter/leave call the handler if related is outside the target.
			// NB: No relatedTarget if the mouse left/entered the browser window
			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
				event.type = handleObj.origType;
				ret = handleObj.handler.apply( this, arguments );
				event.type = fix;
			}
			return ret;
		}
	};
});

// IE submit delegation
if ( !jQuery.support.submitBubbles ) {

	jQuery.event.special.submit = {
		setup: function() {
			// Only need this for delegated form submit events
			if ( jQuery.nodeName( this, "form" ) ) {
				return false;
			}

			// Lazy-add a submit handler when a descendant form may potentially be submitted
			jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
				// Node name check avoids a VML-related crash in IE (#9807)
				var elem = e.target,
					form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
				if ( form && !form._submit_attached ) {
					jQuery.event.add( form, "submit._submit", function( event ) {
						// If form was submitted by the user, bubble the event up the tree
						if ( this.parentNode && !event.isTrigger ) {
							jQuery.event.simulate( "submit", this.parentNode, event, true );
						}
					});
					form._submit_attached = true;
				}
			});
			// return undefined since we don't need an event listener
		},

		teardown: function() {
			// Only need this for delegated form submit events
			if ( jQuery.nodeName( this, "form" ) ) {
				return false;
			}

			// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
			jQuery.event.remove( this, "._submit" );
		}
	};
}

// IE change delegation and checkbox/radio fix
if ( !jQuery.support.changeBubbles ) {

	jQuery.event.special.change = {

		setup: function() {

			if ( rformElems.test( this.nodeName ) ) {
				// IE doesn't fire change on a check/radio until blur; trigger it on click
				// after a propertychange. Eat the blur-change in special.change.handle.
				// This still fires onchange a second time for check/radio after blur.
				if ( this.type === "checkbox" || this.type === "radio" ) {
					jQuery.event.add( this, "propertychange._change", function( event ) {
						if ( event.originalEvent.propertyName === "checked" ) {
							this._just_changed = true;
						}
					});
					jQuery.event.add( this, "click._change", function( event ) {
						if ( this._just_changed && !event.isTrigger ) {
							this._just_changed = false;
							jQuery.event.simulate( "change", this, event, true );
						}
					});
				}
				return false;
			}
			// Delegated event; lazy-add a change handler on descendant inputs
			jQuery.event.add( this, "beforeactivate._change", function( e ) {
				var elem = e.target;

				if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {
					jQuery.event.add( elem, "change._change", function( event ) {
						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
							jQuery.event.simulate( "change", this.parentNode, event, true );
						}
					});
					elem._change_attached = true;
				}
			});
		},

		handle: function( event ) {
			var elem = event.target;

			// Swallow native change events from checkbox/radio, we already triggered them above
			if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
				return event.handleObj.handler.apply( this, arguments );
			}
		},

		teardown: function() {
			jQuery.event.remove( this, "._change" );

			return rformElems.test( this.nodeName );
		}
	};
}

// Create "bubbling" focus and blur events
if ( !jQuery.support.focusinBubbles ) {
	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {

		// Attach a single capturing handler while someone wants focusin/focusout
		var attaches = 0,
			handler = function( event ) {
				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
			};

		jQuery.event.special[ fix ] = {
			setup: function() {
				if ( attaches++ === 0 ) {
					document.addEventListener( orig, handler, true );
				}
			},
			teardown: function() {
				if ( --attaches === 0 ) {
					document.removeEventListener( orig, handler, true );
				}
			}
		};
	});
}

jQuery.fn.extend({

	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
		var origFn, type;

    // 修正参数 start
    // on(types, fn) 
    // on(types, selector, fn)
    // on(types, data, fn)
		// Types can be a map of types/handlers
		if ( typeof types === "object" ) {
			// ( types-Object, selector, data )
			if ( typeof selector !== "string" ) {
				// ( types-Object, data )
				data = selector;
				selector = undefined;
			}
			for ( type in types ) {
				this.on( type, selector, data, types[ type ], one );
			}
			return this;
		}

		if ( data == null && fn == null ) {
			// ( types, fn )
			fn = selector;
			data = selector = undefined;
		} else if ( fn == null ) {
			if ( typeof selector === "string" ) {
				// ( types, selector, fn )
				fn = data;
				data = undefined;
			} else {
				// ( types, data, fn )
				fn = data;
				data = selector;
				selector = undefined;
			}
		}
		if ( fn === false ) {
			fn = returnFalse;
		} else if ( !fn ) {
			return this;
		}
    // 修正参数 end
    // 如果有one是1, 表示监听函数只能被执行一次
		if ( one === 1 ) {
      // 重新设置fn为一个 执行时先移除监听事件再执行函数的方法
			origFn = fn;
			fn = function( event ) {
				jQuery().off( event );
				return origFn.apply( this, arguments );
			};
			// 设置两者为相同的guid,方便移除的时候操作
			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
		}
		return this.each( function() {
			jQuery.event.add( this, types, fn, data, selector );
		});
	},
	one: function( types, selector, data, fn ) {
		return this.on.call( this, types, selector, data, fn, 1 );
	},
	off: function( types, selector, fn ) {
    // 有这两个属性表示 types是一个jQuery.event对象
    // 内部移除当前正在执行的事件
		if ( types && types.preventDefault && types.handleObj ) {
			// ( event )  dispatched jQuery.Event
			var handleObj = types.handleObj;
			jQuery( types.delegateTarget ).off(
				handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type,
				handleObj.selector,
				handleObj.handler
			);
			return this;
		}
    // 如果是一个数组则循环调用off, 移除相对应的监听事件
		if ( typeof types === "object" ) {
			// ( types-object [, selector] )
			for ( var type in types ) {
				this.off( type, selector, types[ type ] );
			}
			return this;
		}
    // 没有selector, 修正参数
		if ( selector === false || typeof selector === "function" ) {
			// ( types [, fn] )
			fn = selector;
			selector = undefined;
		}
		if ( fn === false ) {
			fn = returnFalse;
		}
    // 执行真正的监听事件移除的方法
		return this.each(function() {
			jQuery.event.remove( this, types, fn, selector );
		});
	},

	bind: function( types, data, fn ) {
		return this.on( types, null, data, fn );
	},
	unbind: function( types, fn ) {
		return this.off( types, null, fn );
	},

	live: function( types, data, fn ) {
		jQuery( this.context ).on( types, this.selector, data, fn );
		return this;
	},
	die: function( types, fn ) {
		jQuery( this.context ).off( types, this.selector || "**", fn );
		return this;
	},

	delegate: function( selector, types, data, fn ) {
		return this.on( types, selector, data, fn );
	},
	undelegate: function( selector, types, fn ) {
		// ( namespace ) or ( selector, types [, fn] )
		return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
	},

  // 主动去触发事件
	trigger: function( type, data ) {
		return this.each(function() {
			jQuery.event.trigger( type, data, this );
		});
	},
	triggerHandler: function( type, data ) {
		if ( this[0] ) {
			return jQuery.event.trigger( type, data, this[0], true );
		}
	},

  // 一个快速设置点击事件 响应多个对调函数的方法,  
  // 其实所有函数被作为一个闭包加到缓存数据中,他们可以一起被移除
	toggle: function( fn ) {
		// Save reference to arguments for access in closure
		var args = arguments,
			guid = fn.guid || jQuery.guid++,
			i = 0,
			toggler = function( event ) {
				// Figure out which function to execute
				var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
				jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );

				// Make sure that clicks stop
				event.preventDefault();

				// and execute the function
				return args[ lastToggle ].apply( this, arguments ) || false;
			};

		// link all the functions, so any of them can unbind this click handler
		toggler.guid = guid;
		while ( i < args.length ) {
			args[ i++ ].guid = guid;
		}

		return this.click( toggler );
	},

	hover: function( fnOver, fnOut ) {
		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
	}
});

jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {

	// Handle event binding
	jQuery.fn[ name ] = function( data, fn ) {
		if ( fn == null ) {
			fn = data;
			data = null;
		}

		return arguments.length > 0 ?
			this.on( name, null, data, fn ) :
			this.trigger( name );
	};

	if ( jQuery.attrFn ) {
		jQuery.attrFn[ name ] = true;
	}

	if ( rkeyEvent.test( name ) ) {
		jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
	}

	if ( rmouseEvent.test( name ) ) {
		jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
	}
});

ready部分

// 这是一个ready的主入口函数,
jQuqey.fn = {
  ready: function( fn ) {
    // Attach the listeners
    // 初始化rady、构建readyList、增加监听函数DOMCONTENTLOADED
    jQuery.bindReady();

    // Add the callback
    readyList.add( fn );

    return this;
  },
}

// 这是初始化DOMContentLoaded的代码,下面一段会用到
if ( document.addEventListener ) {
  DOMContentLoaded = function() {
    document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
    jQuery.ready();
  };

} else if ( document.attachEvent ) {
  DOMContentLoaded = function() {
    // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
    if ( document.readyState === "complete" ) {
      document.detachEvent( "onreadystatechange", DOMContentLoaded );
      jQuery.ready();
    }
  };
}

jQuery.extend({
  // 传入一个真值, 就会让readyWait这个flag增加1
  holdReady: function( hold ) {
    if ( hold ) {
      jQuery.readyWait++;
    } else {
      jQuery.ready( true );
    }
  },

  // 由于这里面要判断是不是isReady才会执行,所以当已经执行过ready之后再使用on.(ready)添加的方法不会被执行,但是直接添加到队列的会被执行,刚才讲过队列的属性了
  // Handle when the DOM is ready
  ready: function( wait ) {
    // 只有是hlodReady那边调用wait才有可能为true, 代表着readyWait减少1
    // Either a released hold or an DOMready/load event and not yet ready
    if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
      // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
      if ( !document.body ) {
        return setTimeout( jQuery.ready, 1 );
      }

      // Remember that the DOM is ready
      // 更改ready标记
      jQuery.isReady = true;

      // If a normal DOM Ready event fired, decrement, and wait if need be
      // 不满足条件证明ready事件被延迟了,需要继续等待ready事件被减少,就是holdReady(false)
      if ( wait !== true && --jQuery.readyWait > 0 ) {
        return;
      }

      // 执行队列
      // If there are functions bound, to execute
      readyList.fireWith( document, [ jQuery ] );
      // 绑定ready事件
      // Trigger any bound ready events
      if ( jQuery.fn.trigger ) {
        jQuery( document ).trigger( "ready" ).off( "ready" );
      }
    }
  },
  bindReady: function() {
    // readyList第一次定义为undefined
    if ( readyList ) {
      return;
    }

    // readyList定义为一个值执行一次的队列  memory表示之后添加的函数立即被调用,也就是说$.ready()之后还可以继续执行
    readyList = jQuery.Callbacks( "once memory" );

    // jquery加载的有点慢,这时候整个文档都已经加载完毕了,直接立即执行ready吧
    // Catch cases where $(document).ready() is called after the
    // browser event has already occurred.
    if ( document.readyState === "complete" ) {
      // Handle it asynchronously to allow scripts the opportunity to delay ready
      return setTimeout( jQuery.ready, 1 );
    }

    // Mozilla, Opera and webkit nightlies currently support this event
    if ( document.addEventListener ) {
      // Use the handy event callback
      document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );

      // A fallback to window.onload, that will always work
      window.addEventListener( "load", jQuery.ready, false );

    // If IE event model is used
    } else if ( document.attachEvent ) {
      // ensure firing before onload,
      // maybe late but safe also for iframes
      document.attachEvent( "onreadystatechange", DOMContentLoaded );

      // A fallback to window.onload, that will always work
      window.attachEvent( "onload", jQuery.ready );

      // If IE and not a frame
      // continually check to see if the document is ready
      var toplevel = false;

      // 如果食顶层 不是iframe 可以通过doScrollCheck来测试是不是文档已经准备好
      try {
        toplevel = window.frameElement == null;
      } catch(e) {}

      if ( document.documentElement.doScroll && toplevel ) {
        doScrollCheck();
      }
    }
  },
})

// 在ie下 document.documentElement.doScroll("left") 方法不报错,就证明页面dom加载好了
function doScrollCheck() {
  if ( jQuery.isReady ) {
    return;
  }
  try {
    // If IE is used, use the trick by Diego Perini
    // http://javascript.nwbox.com/IEContentLoaded/
    document.documentElement.doScroll("left");
  } catch(e) {
    setTimeout( doScrollCheck, 1 );
    return;
  }

  // and execute any waiting functions
  jQuery.ready();
}

jQuery chapter10 DOM操作 ----jQuery源码分析系列

chapter10 DOM操作

对DOM的操作在jQuery中有插入、删除、复制、替换、包裹元素五类,基于原生的insertBefore、appendChild、removeChild、cloneNode4个方法,其中replaceChild没有用到,替换元素则是基于已经实现的删除与插入来完成。

DOM操作部分代码并不是特别复杂,只有插入那块用到了之前分析过的一两段复杂的构建html的代码,复制部分需要对兼容性修正,其余部分都是利用原生方法封装为更方便易用的接口。

// 这段代码的方法是jQuery里面最基本的方法了。很熟悉的感觉
jQuery.fn.extend({
  text: function( text ) {
    // text为函数, 则each调用函数之后的值
    if ( jQuery.isFunction(text) ) {
      return this.each(function(i) {
        var self = jQuery( this );

        self.text( text.call(this, i, self.text()) );
      });
    }

    // 为元素插入文本节点
    if ( typeof text !== "object" && text !== undefined ) {
      return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
    }
    // 返回第一个元素的文本信息
    return jQuery.text( this );
  },

  wrapAll: function( html ) {
    // 传入方法则为每个元素用方法执行后的结果执行warpAll
    if ( jQuery.isFunction( html ) ) {
      return this.each(function(i) {
        jQuery(this).wrapAll( html.call(this, i) );
      });
    }

    if ( this[0] ) {
      // The elements to wrap the target around
      // 先获取包裹元素的副本
      var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);

      // 如果有第一个匹配元素有父元素,则将包裹元素插入到第一个元素的前面
      if ( this[0].parentNode ) {
        wrap.insertBefore( this[0] );
      }

      // 找到wrap最内层的元素,然后把当前元素插进去
      wrap.map(function() {
        var elem = this;

        while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
          elem = elem.firstChild;
        }

        return elem;
      }).append( this );
    }

    return this;
  },

  wrapInner: function( html ) {
    // 累
    if ( jQuery.isFunction( html ) ) {
      return this.each(function(i) {
        jQuery(this).wrapInner( html.call(this, i) );
      });
    }
    // 遍历当前元素
    // 如果当前元素有子元素则子元素调用wrapAll
    // 否则直接把wrap插入当前元素
    return this.each(function() {
      var self = jQuery( this ),
        contents = self.contents();

      if ( contents.length ) {
        contents.wrapAll( html );

      } else {
        self.append( html );
      }
    });
  },

  wrap: function( html ) {
    var isFunction = jQuery.isFunction( html );
    // 在匹配元素的每个元素执行wrapAll
    return this.each(function(i) {
      jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
    });
  },

  unwrap: function() {
    // 为每个匹配元素的父元素执行replaceWidth, 用子元素直接替代它,就相当于解除包裹了
    return this.parent().each(function() {
      if ( !jQuery.nodeName( this, "body" ) ) {
        jQuery( this ).replaceWith( this.childNodes );
      }
    }).end();
  },

  append: function() {
    // 调用domManip完成dom的转换,然后再执行appendChild完成插入操作
    return this.domManip(arguments, true, function( elem ) {
      if ( this.nodeType === 1 ) {
        this.appendChild( elem );
      }
    });
  },

  prepend: function() {
    // 完成在头部插入
    return this.domManip(arguments, true, function( elem ) {
      if ( this.nodeType === 1 ) {
        this.insertBefore( elem, this.firstChild );
      }
    });
  },

  before: function() {
    // 在指定元素前面插入元素
    if ( this[0] && this[0].parentNode ) {
      return this.domManip(arguments, false, function( elem ) {
        this.parentNode.insertBefore( elem, this );
      });
    } else if ( arguments.length ) {
      var set = jQuery.clean( arguments );
      set.push.apply( set, this.toArray() );
      return this.pushStack( set, "before", arguments );
    }
  },

  after: function() {
    // 在指定元素的后方插入元素
    if ( this[0] && this[0].parentNode ) {
      return this.domManip(arguments, false, function( elem ) {
        this.parentNode.insertBefore( elem, this.nextSibling );
      });
    } else if ( arguments.length ) {
      var set = this.pushStack( this, "after", arguments );
      set.push.apply( set, jQuery.clean(arguments) );
      return set;
    }
  },

  // keepData is for internal use only--do not document
  // 先移除后代元素和关联的数据和事件,以防止内存泄漏
  remove: function( selector, keepData ) {
    for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
      if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
        if ( !keepData && elem.nodeType === 1 ) {
          jQuery.cleanData( elem.getElementsByTagName("*") );
          jQuery.cleanData( [ elem ] );
        }

        if ( elem.parentNode ) {
          elem.parentNode.removeChild( elem );
        }
      }
    }

    return this;
  },

  // 移除所有后代
  // 也要先移除数据和事件
  // 不过最后有个监测措施
  empty: function() {
    for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
      // Remove element nodes and prevent memory leaks
      if ( elem.nodeType === 1 ) {
        jQuery.cleanData( elem.getElementsByTagName("*") );
      }

      // Remove any remaining nodes
      while ( elem.firstChild ) {
        elem.removeChild( elem.firstChild );
      }
    }

    return this;
  },
  // 负责决定两个参数,是否复制元素本身的数据和事件
  // 是否复制后代的元素和事件
  clone: function( dataAndEvents, deepDataAndEvents ) {
    dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
    deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

    return this.map( function () {
      return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
    });
  },

  // rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
  html: function( value ) {
    // 如果没有传入值,那么表示读取第一个元素innerHTML
    if ( value === undefined ) {
      return this[0] && this[0].nodeType === 1 ?
        this[0].innerHTML.replace(rinlinejQuery, "") :
        null;

    // See if we can take a shortcut and just use innerHTML
    // 在不需要任何修正的情况下
    } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
      (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
      !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {

      value = value.replace(rxhtmlTag, "<$1></$2>");

      try {
        for ( var i = 0, l = this.length; i < l; i++ ) {
          // Remove element nodes and prevent memory leaks
          // 遍历当前所有匹配元素,尝试把内部的所有元素的数据缓存都删掉,然后重设他们的innerHTML
          if ( this[i].nodeType === 1 ) {
            jQuery.cleanData( this[i].getElementsByTagName("*") );
            this[i].innerHTML = value;
          }
        }

      // If using innerHTML throws an exception, use the fallback method
      } catch(e) {
        // 前面出了问题,直接调用empty方法,然后再插入新值
        this.empty().append( value );
      }

    } else if ( jQuery.isFunction( value ) ) {
      // 如果发现value是函数,则each为每个匹配元素调用html方法
      this.each(function(i){
        var self = jQuery( this );

        self.html( value.call(this, i, self.html()) );
      });

    } else {
      this.empty().append( value );
    }

    return this;
  },
  // 替换元素
  replaceWith: function( value ) {
    // 当前有元素且元素有父元素
    if ( this[0] && this[0].parentNode ) {
      // Make sure that the elements are removed from the DOM before they are inserted
      // this can help fix replacing a parent with child elements
      // 如果value是函数 则便利当前匹配元素,继续用value执行后的结果调用replaceWith
      if ( jQuery.isFunction( value ) ) {
        return this.each(function(i) {
          var self = jQuery(this), old = self.html();
          self.replaceWith( value.call( this, i, old ) );
        });
      }
      // 如果value不是方法、不是字符串可能是dom活着jQuery对象,则先把value从文档中移除掉
      if ( typeof value !== "string" ) {
        value = jQuery( value ).detach();
      }
      
      // 先把元素移除掉,再用value插入、可能是通过后面兄弟前面插入、也可能是通过父亲直接最后插入
      return this.each(function() {
        var next = this.nextSibling,
          parent = this.parentNode;

        jQuery( this ).remove();

        if ( next ) {
          jQuery(next).before( value );
        } else {
          jQuery(parent).append( value );
        }
      });
    } else {
      // 有length表示有匹配元素,但第一个元素没有父元素,执行this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value )
      // 否则直接意味着当前没有匹配元素 直接返回this本身
      // 第一种情况,直接拿value构建一个新的jQuery对象返回
      return this.length ?
        this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
        this;
    }
  },

  detach: function( selector ) {
    return this.remove( selector, true );
  },

  // 被多个插入方法调用的基本方法
  // 就是转换dom元素,并且调用真正的回调函数,把转换后的dom元素插入进去
  // checked="checked" or checked
  // rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, 
  domManip: function( args, table, callback ) {
    var results, first, fragment, parent,
      value = args[0],
      scripts = [];

    // 浏览器能否正常复制含有checked参数的dom元素
    // 迭代执行来解决这个问题
    if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
      return this.each(function() {
        jQuery(this).domManip( args, table, callback, true );
      });
    }

    // 如果发现args数组内是函数,则遍历当前匹配的元素集合,将执行后的数据继续执行domManip
    if ( jQuery.isFunction(value) ) {
      return this.each(function(i) {
        var self = jQuery(this);
        args[0] = value.call(this, i, table ? self.html() : undefined);
        self.domManip( args, table, callback );
      });
    }

    if ( this[0] ) {
      parent = value && value.parentNode;

      // If we're in a fragment, just use that instead of building a new one
      // 这里有个坑, jQuery.support没有测试parentNode, 所以这个if永远不会执行
      if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
        results = { fragment: parent };

      } else {
        // 调用buildFragment将字符串转为dom元素
        // 顺便把script提取出来了
        results = jQuery.buildFragment( args, this, scripts );
      }

      fragment = results.fragment;

      if ( fragment.childNodes.length === 1 ) {
        first = fragment = fragment.firstChild;
      } else {
        first = fragment.firstChild;
      }

      if ( first ) {
        // 查看当前元素是不是tr
        table = table && jQuery.nodeName( first, "tr" );

        for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
          callback.call(
            table ?
              root(this[i], first) :
              this[i],
            // Make sure that we do not leak memory by inadvertently discarding
            // the original fragment (which might have attached data) instead of
            // using it; in addition, use the original fragment object for the last
            // item instead of first because it can end up being emptied incorrectly
            // in certain situations (Bug #8070).
            // Fragments from the fragment cache must always be cloned and never used
            // in place.
            // 如果该dom是可缓存的,则总是插入它的副本
            // 如果当前含有多个匹配的dom元素,则前面插入副本,最后一个插入本身
            results.cacheable || ( l > 1 && i < lastIndex ) ?
              jQuery.clone( fragment, true, true ) :
              fragment
          );
        }
      }
      // 如果有提取到script标签,则执行该标签
      if ( scripts.length ) {
        jQuery.each( scripts, evalScript );
      }
    }

    return this;
  }
});

// 修正table的tbody,
// 返回tbody元素
function root( elem, cur ) {
  return jQuery.nodeName(elem, "table") ?
    (elem.getElementsByTagName("tbody")[0] ||
    elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
    elem;
}
// appendTo\prependTo\insertBefore\insertAfter目的恰恰和前面不带To的相反,一个主动、一个被动的区别
jQuery.each({
  appendTo: "append",
  prependTo: "prepend",
  insertBefore: "before",
  insertAfter: "after",
  replaceAll: "replaceWith"
}, function( name, original ) {
  jQuery.fn[ name ] = function( selector ) {
    var ret = [],
      insert = jQuery( selector ),
      parent = this.length === 1 && this[0].parentNode;

    if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
      insert[ original ]( this[0] );
      return this;

    } else {
      for ( var i = 0, l = insert.length; i < l; i++ ) {
        var elems = ( i > 0 ? this.clone(true) : this ).get();
        jQuery( insert[i] )[ original ]( elems );
        ret = ret.concat( elems );
      }

      return this.pushStack( ret, name, insert.selector );
    }
  };
});
jQuery.extend({
  clone: function( elem, dataAndEvents, deepDataAndEvents ) {
    var srcElements,
      destElements,
      i,
      //   rnoshimcache = new RegExp("<(?:" + nodeNames + ")", "i"),
      // 浏览器支持html5元素 或者不包含html5元素 调用原生的cloneNode、否则调用shimCloneNode
      // IE<=8 does not properly clone detached, unknown element nodes
      clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ?
        elem.cloneNode( true ) :
        shimCloneNode( elem );

    // 如果浏览器不支持事件复制、或者不能正确复制checked状态
    // 则要借助cloneFixAttributes方法
    if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
        (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
      // IE copies events bound via attachEvent when using cloneNode.
      // Calling detachEvent on the clone will also remove the events
      // from the original. In order to get around this, we use some
      // proprietary methods to clear the events. Thanks to MooTools
      // guys for this hotness.

      cloneFixAttributes( elem, clone );

      // Using Sizzle here is crazy slow, so we use getElementsByTagName instead
      // getAll = getElementsByTagName || querySelectorAll
      srcElements = getAll( elem );
      destElements = getAll( clone );

      // Weird iteration because IE will replace the length property
      // with an element if you are cloning the body and one of the
      // elements on the page has a name or id of "length"
      // 为每一个字元素都执行cloneFixAttributes修正方法
      for ( i = 0; srcElements[i]; ++i ) {
        // Ensure that the destination node is not null; Fixes #9587
        if ( destElements[i] ) {
          cloneFixAttributes( srcElements[i], destElements[i] );
        }
      }
    }

    // Copy the events from the original to the clone
    // 复制事件 按需求 是否深层复制事件
    // cloneCopyEvent
    if ( dataAndEvents ) {
      cloneCopyEvent( elem, clone );

      if ( deepDataAndEvents ) {
        srcElements = getAll( elem );
        destElements = getAll( clone );

        for ( i = 0; srcElements[i]; ++i ) {
          cloneCopyEvent( srcElements[i], destElements[i] );
        }
      }
    }

    srcElements = destElements = null;

    // Return the cloned set
    return clone;
  },
});

cloneFixAttributes 方法

function cloneFixAttributes( src, dest ) {
  var nodeName;

  // We do not need to do anything for non-Elements
  if ( dest.nodeType !== 1 ) {
    return;
  }

  // clearAttributes\mergeAttributes仅在IE678下支持, 会清除掉元素的属性和attachEvent的事件,但mergeAttributes只会把属性复制,不会复制事件
  // clearAttributes removes the attributes, which we don't want,
  // but also removes the attachEvent events, which we *do* want
  if ( dest.clearAttributes ) {
    dest.clearAttributes();
  }

  // mergeAttributes, in contrast, only merges back on the
  // original attributes, not the events
  if ( dest.mergeAttributes ) {
    dest.mergeAttributes( src );
  }

  nodeName = dest.nodeName.toLowerCase();

  // IE6-8 fail to clone children inside object elements that use
  // the proprietary classid attribute value (rather than the type
  // attribute) to identify the type of content to display
  // 如果一个元素师object,那么直接原封的文本复制即可
  if ( nodeName === "object" ) {
    dest.outerHTML = src.outerHTML;

  } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
    // 如果是input、cehcekbox、radio则复制其checked、value
    // IE6-8 fails to persist the checked state of a cloned checkbox
    // or radio button. Worse, IE6-7 fail to give the cloned element
    // a checked appearance if the defaultChecked value isn't also set
    if ( src.checked ) {
      dest.defaultChecked = dest.checked = src.checked;
    }

    // IE6-7 get confused and end up setting the value of a cloned
    // checkbox/radio button to an empty string instead of "on"
    if ( dest.value !== src.value ) {
      dest.value = src.value;
    }

  // IE6-8 fails to return the selected option to the default selected
  // state when cloning options
  // 修正option的selected状态
  } else if ( nodeName === "option" ) {
    dest.selected = src.defaultSelected;

  // IE6-8 fails to set the defaultValue to the correct value when
  // cloning other types of input fields
  } else if ( nodeName === "input" || nodeName === "textarea" ) {
    dest.defaultValue = src.defaultValue;
  }

  // Event data gets referenced instead of copied if the expando
  // gets copied too
  dest.removeAttribute( jQuery.expando );
}

cloneCopyEvent 方法

function cloneCopyEvent( src, dest ) {
  // 检查src元素是否有事件
  if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
    return;
  }

  // 先将原始数据缓存复制到目标元素上
  var type, i, l,
    oldData = jQuery._data( src ),
    curData = jQuery._data( dest, oldData ),
    events = oldData.events;
  // 如果原始数据中有事件数据
  // 先将目标元素上面的事件数据清空掉
  if ( events ) {
    delete curData.handle;
    curData.events = {};
    // 然后将原始数据中的事件一个一个遍历添加到新的目标元素上
    for ( type in events ) {
      for ( i = 0, l = events[ type ].length; i < l; i++ ) {
        jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data );
      }
    }
  }

  // make the cloned public data object a copy from the original
  if ( curData.data ) {
    curData.data = jQuery.extend( {}, curData.data );
  }
}
// 获取元素的所有子节点元素
contents: function( elem ) {
  return jQuery.nodeName( elem, "iframe" ) ?
    elem.contentDocument || elem.contentWindow.document :
    jQuery.makeArray( elem.childNodes );
}

块级元素

块级元素

块级元素也是现在web定位的主要依赖,最基本就是div + css了。对它再熟悉不过。 块级元素一个最大的特征就是它要独占一行,不管内容够不够,反正就是任性。 我们一点点来看块级元素的一些规则。

包含块

每个块级元素在横轴上都有margin-left、border-left、padding-left、width、padding-right、border-right、margin-right这个几个部分构成。一般width指内容区域。
而每个html元素都是由一个外层元素包裹的、我们真正页面上的最外层也至少被body包裹,body也是一个巨大的块级元素。
外层包裹的那个元素其实就是内部元素的“布局上下文”,外层包裹元素就是内层元素的包含块。可能有点抽象

<div class=“wrapper”>
	<div class=“inner”>
		<p></p>
	</div>
</div>

在这个例子中,wrapper就是inner的包含块, inner就是p的包含块。
一般而言,元素都要在它的包含块内进行定位、显示、布局的。当然行内元素不考虑包含块。

水平—块级元素

我们知道一个块级元素 我们可以为其设置宽度,也可以不为其设置宽度。当不为其设置宽度时,它会自动铺满整行。其实这是有根据的:块级元素的宽度之和必须等于包含块的内容区域宽度(规则1,之后会提到) , 而块级元素的宽度之和是指块级元素内容区、左右内边距、左右边框和左右外边距的宽度之和
正是有这个规定,所以我们平时才可以使用margin: 0 auto来实现水平居中。也就是说有在我们普通计算margin-left、border-left、padding-left、width、padding-right、border-right、margin-right 之和不能达到包含块的内容区域宽度时,肯定有属性被浏览器默认更改了。
我们先来看一下各个值初始的默认情况:

margin: 0,  // 取值 具体值、百分比、auto
width: auto,  // 取值 具体值、百分比、auto 等
padding: 0  // 取值 具体值、百分比

我们来一点点测试。
html:

<div style=“width: 1000px; background: red”>
	<div class=“inner” style=“background: yellow”>1</div>
</div>

测试1

不附加任何css规则也就是全部都是默认值,结果发现黄色填满整行。
结论:在margin为0时(默认不进行设置),为满足刚才说的规则1, width会自动延展。
image1

测试2

只设置margin-left或者margin-right, 可以发现结果还是width进行延展。
结论:在只设置一个margin时,为满足刚才说的规则1, width会自动延展。

测试3

设置margin-left和margin-right, 可以发现结果还是width进行延展。
结论:在设置margin-left和margin-right,为满足刚才说的规则1, width会自动延展。
image1

测试4

只设置width时, 可以发现黄色部分尺寸固定,延展的是嘛margin-right。其实这个也可以理解,浏览器是从上到下,从左到右布局,不设置margin-left肯定是不太想有左边间隙,那扩展margin-right也无可厚非
结论:在仅设置width情况下,延展的是margin-right。
image1

测试5

设置width和margin-left时, 可以发现黄色部分尺寸固定,延展的是嘛margin-right。
结论:与测试4的结论一致。

测试6

设置width和margin-right时, 可以发现黄色部分尺寸固定,延展的还是margin-right。
这里比较奇怪,明明设置的margin-right,但是和没设置一样的,那是因为margin-left也有默认值0哦,所以可能没效果,或者认为left优先级比right高吧,至少在从左向右排列格式时。
结论:与测试4的结论一致。

测试7

设置width和margin-right时,同时设置margin-left:auto, 可以发现黄色部分尺寸固定,延展的还是margin-left。
这一次我们发现终于按照我们设想的margin-right体现出来了。
结论:在主动更改margin-left为auto后,width和margin-right会按原设置宽度。
image1

测试8

设置margin-left和width为auto,margin-right为初始值即0。结果与不进行任何设置一样。
结论:margin的auto优先级低于width的auto。
image1

测试9

设置margin-left、margin-right、 width全部为auto。结果与测试8测试1一样。
结论:与测试8的结论一致。

结论

  • 可以粗略的认为为了满足规则1, width、margin-left、margin-right一定会有一个值变为auto
  • width默认初始值就是auto,因此在不进行margin设置时,width会延展,同时width的auto会优先于margin的auto。
  • 有width值时,margin的auto才会起作用,但margin的默认值时0哦,因此不进行margin设置时,margin-right会被延展。只有在margin-left设置为auto时,margin-left才有可能被延展。

负margin

水平上负margin导致width总和可能会超出包含块内容区域。
一般为扩展宽度采用。

垂直—块级元素

在垂直方向上,其实一般不太关心,毕竟浏览器下滑已经是大家都习以为常的事情。 因此很多时候我们不会为块级元素设置高度。
默认情况下height: auto, 因此他会随着内部元素增加而自动拉伸,但如果显式的设置它的高度,他就不会再拉伸了。

为垂直方向设置margin: auto 0,其实是没有意义的,它会将margin-top和margin-bottom重置为0, 其实它就失去了上下外边距。当然定位和浮动元素不算。

外边距合并

在垂直外边距合并指的是,**当两个垂直外边距相遇时,它们将形成一个外边距。合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者。**但只有普通文档流中块框的垂直外边距才会发生外边距合并。行内框、浮动框或绝对定位之间的外边距不会合并。
下面是w3c官方的解释 为什么会出现垂直外边距折叠。

Two margins are adjoining if and only if:
• both belong to in-flow block-level boxes that participate in the same block formatting context
• no line boxes, no clearance, no padding and no border separate them (Note that certain zero-height line boxes (see 9.4.2) are ignored for this purpose.)
• both belong to vertically-adjacent box edges, i.e. form one of the following pairs:
◦ top margin of a box and top margin of its first in-flow child
◦ bottom margin of box and top margin of its next in-flow following sibling
◦ bottom margin of a last in-flow child and bottom margin of its parent if the parent has 'auto' computed height
◦ top and bottom margins of a box that does not establish a new block formatting context and that has zero computed 'min-height', zero or 'auto' computed 'height', and no in-flow children
这里面有一个很重要的条件就是两个折叠元素都是普通文档流in-flow,并且没有非空内容、padding、border 或 clear 分隔开, 同时最后几个小例子也说明外边距折叠不一定只发生在相邻兄弟元素,父级与子元素也可以。

按照规定来说我们如果要解决外边距折叠的问题可以有以下几条思路:

  • 不是块级元素 例如inline-block、table-cell、table-caption、inline-block
  • 脱离文档流 例如absolute、fixed、float
  • 在每两个margin之间不管是margin-top和margin-bottom还是margin-top和margin-top之间插入内容、padding或者border都可以。

然后我们在这里都没有发现任何BFC(格式化上下文)的影子, 那为什么经常能看到BFC来解决外边距折叠呢?
先来看如何触发BFC:

float(除了none)、overflow(除了visible)、display(table-cell/table-caption/inline-block)、position(除了static/relative)。
可以看出来解决外边距折叠是触发BFC机制的子集,言外之意是即使触发了BFC依旧可能发生外边距折叠,比如说设置overflow: hidden(仅不会与子元素合并,但依旧可能与相邻元素合并)。

看完上面的内容希望得出一个基本的结论: 外边距折叠是外边距折叠,BFC是BFC,两者还是要进行区别开来的。

  • 外边距合折叠发生时,应该考虑padding、margin是不是都正确搭配使用了,正确的使用可以避免90%以上的外边距合并。(比例是猜的)

总结

块级元素在CSS中其实不关心,CSS关心的是块级盒子,只是块级盒子是由块级元素产生的,这部分内容可以看另一篇BFC的文章。在块级盒子之上其实还有一个BFC的概念,BFC之内才是块级盒子自由的排版。但BFC不是一个特殊的元素,而是块级元素的某些特殊属性产生的。
上文仅仅介绍的是块级元素的一些特性,从水平和垂直两个切入点来理解块级元素的一些特性,也理解了一下外边距折叠的触发条件与解决思路。强烈推荐本文搭配BFC一起浏览。

underscore源码分析

Note

intro

void 0

underscore中使用void 0 来代替 undefined

  • 代码压缩上面字节更少
  • undefined 在低版本浏览器中是可以被更改的
  • void一直都属于关键字,不会被更改

apply 与 call

apply与call用法上的的区别就不再重复了,很基础。

分析一下为什么underscore不直接return要先进行switch

  var optimizeCb = function(func, context, argCount) {
    // 如果环境是undefined 则直接返回方法
    if (context === void 0) return func;
    // 其余的后面用到再来讲 现在用不到 理解不了
    // 其实主要也就是把返回的函数封装一层,增加几个参数 方便后面方法传参调用
    switch (argCount) {
      case 1: return function(value) {
        return func.call(context, value);
      };
      // The 2-parameter case has been omitted only because no current consumers
      // made use of it.
      case null:
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    // 这里我们发现一个奇怪的点, 下面这句使用apply直接return似乎完全可以替代上面的switch方法啊
    // 为什么还要switch
    // 原因在于apply的性能比call的性能低太多
    return function() {
      return func.apply(context, arguments);
    };
  };

表达式不能以 { 或者 function开头

很多时候我们会 [].length, 但是直接执行 {}.valueOf() 会报错, 原因就在于表达式不能以{ 或者 function开头, 但是在下面这行语句中不会报错 因为以!开头

var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');

类型判断

在underscore中通过这样的方法来批量产生类型检测方法。

_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
  _['is' + name] = function(obj) {
    return toString.call(obj) === '[object ' + name + ']';
  };
});

但后面还有很多额外的补充措施, 例如 Array、NaN、Finite等

_.isArray = Array.isArray || function(obj) {
  return toString.call(obj) === '[object Array]';
};

可枚举属性 bug

IE9以下且重写了['valueOf', 'isPrototypeOf', 'toString','propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']这些属性,那么这些属性是不会被for...in...正确返回的。

检测方法 var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');

如果有bug的话 那么在for...in...中计算keys就要单独处理了

原本的逻辑判断太复杂了, 简化一下其实就是判断直接读取到的和原型链上面的是不是相等的,如果相等那就是一样的,没有改写 那也就没必要算入keys里面了

// proto = (isFunction(obj.constructor) && obj.constructor.prototype) || Object.prototype
if (obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
      keys.push(prop);
    }

ES5 Object.defineProperty 与 ES6 Proxy

ES5 Object.defineProperty 与 ES6 Proxy

先来各自看一下基础知识

Object.defineProperty

Object.definePropertyMDN中这样介绍它

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
说了和没说一样, 了解一下就知道Object.defineProperty帮我们解决的事getter、setter的劫持问题,何为劫持呢?举例

let obj = {};  // 我们定义了一个obj对象
obj.name = 'hello'; // 我们调用setter为obj设置了name属性,且值为hello
console.log(obj.name); // 我们调用getter来获取obj的name属性对应的值

我们劫持了getter、setter,言外之意就是我们可以获得用户设置或者读取某个属性的事件(暂且理解为这是一种事件), 我们就可以在这个事件中作自己爱做的事情了。

let obj = {};
Object.defineProperty(obj, 'name', {
  get(){
    return this._name || 'empty_name';
  },
  set(newValue){
    if(newValue === this._name){
      return;
    }
    this._name = newValue;
  }
});
obj.name;
obj.name = 'hello';

img1
看chrome下的控制台可以看到,obj对象上面的name属性不是一个普通的字符串,当你点击三个点时,他就又变成字符串了,这个很神奇,可以认为通过defineProperty劫持后的属性是个懒属性吧,需要主动去触发。

关于Object.defineProperty参数尤其是第三个description参数的问题可以参考MDN、与其余几片文章。

Proxy

前面讲到了Object.defineProperty大概做的是劫持的事情,劫持对象的某个属性的读与写事件。那么我们就可以认为从ES5到ES6,JS的功力又大增了一次,潜心修炼N年。

Proxy可以劫持更多的事件了,也可以简单认为ProxyObject.defineProperty的升级加强版。

es6 javascript的Proxy实例的方法这篇文档对基本的Proxy用法进行了讲解。

还是拿劫持setter、getter来讲,在Proxy中,升级到一次劫持整个对象的getter、setter而不再是某个属性了。

var person = {  
    name: " 张三 "  
};
var proxy = new Proxy(person, {
  get: function(target, property) {
    if(property in target) {
      return target[property];
    } else {
      throw new ReferenceError("Property \"" + property + "\" does not exist.");
    }
  }
});
proxy.name // " 张三 "  
proxy.age //  抛出一个错误 

img2
这一次我们发现getter的劫持不再局限于某个属性,而是一次劫持整个对象。 同时在Object.defineProperty有一个缺陷就是 如果我们劫持了name这个属性的getter,那么我们在get方法内部时不能使用obj.name,聪明的你肯定想到了this.name,但是也不可以。 所以上面的例子才采用_name来真正存储数据。但是这一次在Proxy方法下,我们不再需要了_name这种迂回战术了,因为我们其实不是对target本身操作,而是对他的劫持器(Proxy)操作。

Proxy不仅能劫持最普通的getter、setter甚至还可以劫持函数方法的调用、new指令、删除属性,甚甚甚甚Proxy可以监控对象的defineProperty.....这就非常尴尬了,大哥直接监控了小弟。

Vue

在介绍 Object.defineProperty 的时候给了几个blog地址,发现它们的标题都和vue有关系,毕竟那么火, 原因就在于Vue作为一个MVVM框架,它最基本双向绑定就是通过Object.defineProperty来实现的。

<div>
  <p>你好,<span id='nickName'></span></p>
  <div id="introduce"></div>
</div>
//视图控制器
var userInfo = {};
Object.defineProperty(userInfo, "nickName", {
  get: function(){
    return document.getElementById('nickName').innerHTML;
  },
  set: function(nick){
    document.getElementById('nickName').innerHTML = nick;
  }
});
Object.defineProperty(userInfo, "introduce", {
  get: function(){
    return document.getElementById('introduce').innerHTML;
  },
  set: function(introduce){
    document.getElementById('introduce').innerHTML = introduce;
  }
})

通过上面两个最基本的Object.defineProperty 我们就实现了 只关心数据 不关心DOM元素。当我们更新数据的时候,其实我们时劫持了更新事件,然后我们用DOM元素来作为真正的数据容器,但是呢, 这好像不是双向绑定。 那怎么实现双向绑定呢?

<input id='input'/>
<span id='text'></span>
let userInput = {};
Object.defineProperty(userInput, 'text', {
  get(){
    return document.getElementById('text').innerHTML
  },
  set(newValue){
    document.getElementById('text').innerHTML = newValue;
    document.getElementById('input').value = newValue
  }
});
document.getElementById('input').addEventListener('input', function(E){
  userInput.text = E.target.value;
})

然后神奇的双向绑定就出现了。 当然Vue做的不仅仅是这些,毕竟日常工作中基本不接触vue,全铺在react上,这篇文档也算是对新事物的了解,以及为接下来react的分析开个头。。。 xx__xx

参考

ES6新特性:Proxy代理器

读写CSS

页面总离不开样式,样式又离不开css。尽管我们会尽可能的想到各种伪类、各种状态、各种类名切换来完成一些基本的交互样式修改,但总还是需要直接js来操作css,毕竟这样是最灵活的。

这一篇作为对之前css属性那篇的贴补。同时,本篇大部分内容取自于张鑫旭的获取元素CSS值之getComputedStyle方法熟悉, 不过好记性不如烂笔头,还是想自己再实际操作一遍。

获取 css 方法

  • element.offsetWidth/offsetHeight/clientWidth/clientHeight
  • window.getComputedStyle()
  • element.currentStyle()
  • getPropertyValue()
  • getAttribute()
  • getPropertyCSSValue()
  • element.style
  • element.getBoundingClientRect()

element.attrs

这是写法上最简单的,直接将dom元素当作对象,然后css属性作为对象的属性,直接取出来,但是限制特别大,只能取到有限的几个属性。
for in dom来查看,再各个浏览器上,属性的个数和具体属性可能不一致。文章最后列出了chrome和ie8下的属性列表。

这里面给出的信息也极为有限,可能关心的也就是offset和client开头的几个属性,还好这几个属性都被支持了。关于这几个属性的具体用法可以看这里

getComputedStyle

这个是重头戏。他告诉我们的是一个元素在浏览器当前状态下真正的、计算过的、被展示所用到的所有css属性。返回的是一个[object CSSStyleDeclaration]。
let style = window.getComputedStyle(element, [pseudoElt]);。不过真不幸,CSSStyleDeclaration这个对象在各个浏览器上面的属性也不一样,也就是各个浏览器对css属性支持的状况,最烂的肯定是IE,哈哈。同样 最后附录了IE8和Chrome的属性列表。 可惜的是CSSStyleDeclaration属性是只读的,意味着我们不能对其进行操作。

那么来看一下如何获取CSSStyleDeclaration对象,正确姿势是window.getComputedStyleMDN。为了兼容性,我们可以人为的设置第二个属性为null。但是jQuery确是document.defaultView.getComputedStyle,这是为了解决一个firefox的bug,但其实document.defaultView获取的也正是windowMDN(又不兼容IE8及其以下)。没有关系这不重要。更重要的是window.getComputedStyle也不被IE8兼容,但IE实现了element.currentStyle方法。

获取到了CSSStyleDeclaration对象,下一步是如何读取属性的时候了。 可以用CSSStyleDeclaration.cssText转成字符串,自己进去正则匹配,想法不错。也可以CSSStyleDeclaration.getPropertyValue(property),property需要传入非驼峰的写法即background-color。 那IE下怎么处理呢,CSSStyleDeclaration.getAttribute(property),不过有一点区别就是,property需要驼峰写法,IE8测试折现background-color没有通过,backgroundColor可行。

CSSStyleDeclaration.getAttribute(property)CSSStyleDeclaration.getPropertyValue(property)返回的数据格式也是不一样的,比如字体设置为2em,再chrome返回的是转换后px为单位,但IE直接返回2em。这里面的区别可能就要根据属性去慢慢辨别了。

element.style

The HTMLElement.style property is used to get as well as set the inline style of an element. While getting, it returns a CSSStyleDeclaration object that contains a list of all styles properties for that element with values assigned for the attributes that are defined in the element's inline style attribute. 这是MDN的定义,这个返回也是一个CSSStyleDeclaration对象,属性尽管没有减少,但发现大部分值都是空字符串,那是因为它只返回inline style,即行内style,而且真是生效的不一定是这里面的属性。更多内容看css

getBoundingClientRect

The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.。这个属性返回的是一个包含元素尺寸和位置信息的对象,没有更多的css信息,但因为它也包含了尺寸、位置信息,因此也算作获取css属性的一种方法。

写css方法

  • element.style.property = value
  • element.setAttribute('style','inline-style');
  • element.style.setProperty('color', 'green', 'important');
  • element.style.cssText ='color:red;line-height:30px';
  • element.style.removeProperty('color');
  • document.styleSheets[0].cssRules[0]
  • var style1 = document.createElement('style'); style1.innerHTML = 'body{color:red}#top:hover{background-color: red;color: white;}'; document.head.appendChild(style1);

element.style.prototype element.style.setProperty('color', 'green', 'important')、element.style.setAttribute('background', 'red')

这两个很类似啊, 一般都挺好用
setProperty最低支持IE版本为IE9, IE8及其以下需要使用setAttribute

element.setAttribute('style','inline-style')

这个的坏处是可能会覆盖原本的inline style
setAttribute查找资料显示IE6+以上都被支持,但document.getElementById('div').setAttribute("style","display:none")这个测试在IE8没通过。诶、不知道为什么诶

element.style.cssText ='color:red;line-height:30px'

这个尽量不要使用,会清空当前所有css属性, 但可以作为一个快速清空选项 element.style.cssText =''
但是这个兼容性不错,IE8也被支持

element.style.removeProperty('color')

仅仅作为一个删除某属性的方法

document.styleSheets[0].cssRules[0]

看都不想看的方法。。。。可能在做动画的时候会用到吧。 不太清楚

新建style标签

这个方法好厉害。

附录

element.attrs列表

chrome 59.0.3071.115下一共227个属性
align title lang translate dir dataset hidden tabIndex accessKey draggable spellcheck contentEditable isContentEditable offsetParent offsetTop offsetLeft offsetWidth offsetHeight style innerText outerText onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmousedown onmouseenter onmouseleave onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange onreset onresize onscroll onseeked onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate ontoggle onvolumechange onwaiting click focus blur onauxclick ongotpointercapture onlostpointercapture onpointercancel onpointerdown onpointerenter onpointerleave onpointermove onpointerout onpointerover onpointerup namespaceURI prefix localName tagName id className classList slot attributes shadowRoot assignedSlot innerHTML outerHTML scrollTop scrollLeft scrollWidth scrollHeight clientTop clientLeft clientWidth clientHeight onbeforecopy onbeforecut onbeforepaste oncopy oncut onpaste onsearch onselectstart onwheel onwebkitfullscreenchange onwebkitfullscreenerror previousElementSibling nextElementSibling children firstElementChild lastElementChild childElementCount hasAttributes getAttribute getAttributeNS setAttribute setAttributeNS removeAttribute removeAttributeNS hasAttribute hasAttributeNS getAttributeNode getAttributeNodeNS setAttributeNode setAttributeNodeNS removeAttributeNode closest matches webkitMatchesSelector attachShadow getElementsByTagName getElementsByTagNameNS getElementsByClassName insertAdjacentElement insertAdjacentText insertAdjacentHTML requestPointerLock getClientRects getBoundingClientRect scrollIntoView scrollIntoViewIfNeeded createShadowRoot getDestinationInsertionPoints animate remove webkitRequestFullScreen webkitRequestFullscreen querySelector querySelectorAll setPointerCapture releasePointerCapture hasPointerCapture before after replaceWith prepend append ELEMENT_NODE ATTRIBUTE_NODE TEXT_NODE CDATA_SECTION_NODE ENTITY_REFERENCE_NODE ENTITY_NODE PROCESSING_INSTRUCTION_NODE COMMENT_NODE DOCUMENT_NODE DOCUMENT_TYPE_NODE DOCUMENT_FRAGMENT_NODE NOTATION_NODE DOCUMENT_POSITION_DISCONNECTED DOCUMENT_POSITION_PRECEDING DOCUMENT_POSITION_FOLLOWING DOCUMENT_POSITION_CONTAINS DOCUMENT_POSITION_CONTAINED_BY DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC nodeType nodeName baseURI isConnected ownerDocument parentNode parentElement childNodes firstChild lastChild previousSibling nextSibling nodeValue textContent hasChildNodes getRootNode normalize cloneNode isEqualNode isSameNode compareDocumentPosition contains lookupPrefix lookupNamespaceURI isDefaultNamespace insertBefore appendChild replaceChild removeChild addEventListener removeEventListener dispatchEvent

IE8下 一共157个
nextSibling onresizeend onrowenter aria-haspopup childNodes ondragleave canHaveHTML onbeforepaste ondragover onbeforecopy aria-disabled onpage recordNumber previousSibling nodeName onbeforeactivate accessKey currentStyle scrollLeft onbeforeeditfocus oncontrolselect aria-hidden onblur hideFocus clientHeight style onbeforedeactivate dir aria-expanded onkeydown nodeType ondragstart onscroll onpropertychange ondragenter id aria-level onrowsinserted scopeName lang onmouseup aria-busy oncontextmenu language scrollTop offsetWidth onbeforeupdate onreadystatechange onmouseenter filters onresize isContentEditable aria-checked aria-readonly oncopy onselectstart scrollHeight onmove ondragend onrowexit lastChild aria-secret onactivate canHaveChildren onfocus onfocusin isMultiLine onmouseover offsetTop oncut parentNode tagName className onmousemove title role behaviorUrns onfocusout onfilterchange disabled parentTextEdit ownerDocument offsetParent aria-posinset ondrop ondblclick onrowsdelete tabIndex onkeypress aria-relevant onlosecapture innerText aria-live parentElement ondeactivate aria-labelledby aria-pressed children ondatasetchanged ondataavailable aria-invalid onafterupdate nodeValue onmousewheel onkeyup readyState onmovestart aria-valuenow aria-selected onmouseout aria-owns aria-valuemax onmoveend contentEditable document firstChild sourceIndex outerText isTextEdit isDisabled oncellchange runtimeStyle scrollWidth aria-valuemin onlayoutcomplete onhelp attributes offsetHeight onerrorupdate onmousedown clientTop aria-setsize clientWidth onpaste tagUrn onmouseleave onclick outerHTML ondrag aria-controls onresizestart aria-flowto ondatasetcomplete aria-required clientLeft aria-describedby all onbeforecut innerHTML aria-activedescendant aria-multiselectable offsetLeft align dataSrc dataFld noWrap dataFormatAs

CSSStyleDeclaration属性列表

chrome 59.0.3071.115下一共282个
animation-delay animation-direction animation-duration animation-fill-mode animation-iteration-count animation-name animation-play-state animation-timing-function background-attachment background-blend-mode background-clip background-color background-image background-origin background-position background-repeat background-size border-bottom-color border-bottom-left-radius border-bottom-right-radius border-bottom-style border-bottom-width border-collapse border-image-outset border-image-repeat border-image-slice border-image-source border-image-width border-left-color border-left-style border-left-width border-right-color border-right-style border-right-width border-top-color border-top-left-radius border-top-right-radius border-top-style border-top-width bottom box-shadow box-sizing break-after break-before break-inside caption-side clear clip color content cursor direction display empty-cells float font-family font-kerning font-size font-stretch font-style font-variant font-variant-ligatures font-variant-caps font-variant-numeric font-weight height image-rendering isolation justify-items justify-self left letter-spacing line-height list-style-image list-style-position list-style-type margin-bottom margin-left margin-right margin-top max-height max-width min-height min-width mix-blend-mode object-fit object-position offset-distance offset-path offset-rotate offset-rotation opacity orphans outline-color outline-offset outline-style outline-width overflow-anchor overflow-wrap overflow-x overflow-y padding-bottom padding-left padding-right padding-top pointer-events position resize right speak table-layout tab-size text-align text-align-last text-decoration text-decoration-line text-decoration-style text-decoration-color text-decoration-skip text-underline-position text-indent text-rendering text-shadow text-size-adjust text-overflow text-transform top touch-action transition-delay transition-duration transition-property transition-timing-function unicode-bidi vertical-align visibility white-space widows width will-change word-break word-spacing word-wrap z-index zoom -webkit-appearance backface-visibility -webkit-background-clip -webkit-background-origin -webkit-border-horizontal-spacing -webkit-border-image -webkit-border-vertical-spacing -webkit-box-align -webkit-box-decoration-break -webkit-box-direction -webkit-box-flex -webkit-box-flex-group -webkit-box-lines -webkit-box-ordinal-group -webkit-box-orient -webkit-box-pack -webkit-box-reflect column-count column-gap column-rule-color column-rule-style column-rule-width column-span column-width align-content align-items align-self flex-basis flex-grow flex-shrink flex-direction flex-wrap justify-content -webkit-font-smoothing grid-auto-columns grid-auto-flow grid-auto-rows grid-column-end grid-column-start grid-template-areas grid-template-columns grid-template-rows grid-row-end grid-row-start grid-column-gap grid-row-gap -webkit-highlight hyphens -webkit-hyphenate-character -webkit-line-break -webkit-line-clamp -webkit-locale -webkit-margin-before-collapse -webkit-margin-after-collapse -webkit-mask-box-image -webkit-mask-box-image-outset -webkit-mask-box-image-repeat -webkit-mask-box-image-slice -webkit-mask-box-image-source -webkit-mask-box-image-width -webkit-mask-clip -webkit-mask-composite -webkit-mask-image -webkit-mask-origin -webkit-mask-position -webkit-mask-repeat -webkit-mask-size order perspective perspective-origin -webkit-print-color-adjust -webkit-rtl-ordering shape-outside shape-image-threshold shape-margin -webkit-tap-highlight-color -webkit-text-combine -webkit-text-decorations-in-effect -webkit-text-emphasis-color -webkit-text-emphasis-position -webkit-text-emphasis-style -webkit-text-fill-color -webkit-text-orientation -webkit-text-security -webkit-text-stroke-color -webkit-text-stroke-width transform transform-origin transform-style -webkit-user-drag -webkit-user-modify user-select -webkit-writing-mode -webkit-app-region buffered-rendering clip-path clip-rule mask filter flood-color flood-opacity lighting-color stop-color stop-opacity color-interpolation color-interpolation-filters color-rendering fill fill-opacity fill-rule marker-end marker-mid marker-start mask-type shape-rendering stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width alignment-baseline baseline-shift dominant-baseline text-anchor writing-mode vector-effect paint-order d cx cy x y r rx ry caret-color

IE8下 一共127个
backgroundColor minWidth widows borderColor fontStyle styleFloat borderLeftColor scrollbarFaceColor unicodeBidi borderBottomWidth direction borderBottomStyle height layoutGridMode outlineColor listStylePosition textOverflow marginLeft letterSpacing backgroundPositionX backgroundPositionY maxHeight listStyleType outlineWidth borderRightStyle margin padding borderCollapse textJustifyTrim fontWeight msInterpolationMode emptyCells hasLayout paddingLeft lineHeight wordSpacing scrollbarTrackColor boxSizing marginTop textAlignLast imeMode zoom lineBreak verticalAlign wordWrap backgroundRepeat zIndex borderRightWidth bottom borderTopStyle pageBreakInside pageBreakBefore minHeight overflowX overflowY scrollbarShadowColor paddingTop textJustify display textTransform borderRightColor rubyPosition scrollbar3dLightColor clipRight maxWidth paddingBottom clipBottom layoutGridChar layoutGridType scrollbarBaseColor paddingRight orphans borderTopWidth marginRight accelerator tableLayout borderStyle textAlign filter borderBottomColor layoutFlow borderTopColor right fontSize outline textAutospace fontFamily pageBreakAfter scrollbarDarkShadowColor borderWidth borderSpacing quotes textUnderlinePosition blockDirection rubyOverhang cursor borderLeftStyle backgroundAttachment listStyleImage rubyAlign scrollbarHighlightColor clipLeft layoutGridLine textKashida width wordBreak textDecoration captionSide outlineStyle scrollbarArrowColor writingMode overflow msBlockProgression visibility top marginBottom color left clipTop behavior borderLeftWidth textKashidaSpace position backgroundImage clear textIndent whiteSpace

轮播图组件 react banner slider(17-07-23)

react-banner-slider

最近好忙,每天都要很晚回家。事情也比较多,又到了亲朋好友结婚的季节了。。。。jQuery源码也好久没继续看了,不过最近差不多理解了react的构建**,大概知道如何实现组件的实现,setState背后的机制和简单的diff,确实无论哪个库或者哪个框架都有不少干货,要不然也不会流行开来。

先放地址npm
github

entry

刚好需要写一个轮播图的组建,其实轮播图应该是很多前端er的入门任务吧,不过好像我这种半路出家的当时没实现过。于是就写了一个banner组建,主要应用于pc端吧,以后再对移动端进行适配。
应用起来真是超级简单

import Banner from 'react-banner-slider';
<Banner className='custome-banner-class' style={{marginTop: 30}} duration={1} stopDuration={500} direction={true} autoPlayer={true}>
  <Banner.Item className='custome-banner-item-class' key={1} backgroundSrc={'./static/1.jpg'}>
    <div style={{position: 'absolute', top: '50%', left: '50%'}}>
      <h2>Title</h2>
      <span>Description</span>
    </div>
  </Banner.Item>
  <Banner.Item className="cnmdsjfhais" key={2} backgroundSrc={'./static/2.jpg'}>111</Banner.Item>
  <Banner.Item key={3} backgroundSrc={'./static/3.jpg'}>111</Banner.Item>
</Banner>

**斗争过程

当时就想着一定要最终样子像antd里面Select选择器组件那样。 而且个人认为两者比较像, 外层决定它是一个什么样的组件,内层比较固定,在Select里面就是Select.Option基本就是字符串,以供用户选择。 在这里banner其实内部也特别简单就是图片而已。但是呢背景是图片可以,但图片上方总还是要有一些元素、文字甚至小图片等,而且肯定还是主要内容,毕竟是Banner嘛。 因此开始的关键就是如何做结构的处理。

  • 顶层Banner肯定确认无疑
  • Banner的子元素应该是符合条件的, 毕竟要从这里面拿到图片的信息。(Banner.Item)
  • 最内层也就是背景图片上的自定义信息,例如文字图标等。

实现

实现1

之前确实没有写过像这样的组件,只是跟着antd模仿写了几个组件而已,日常写业务都比较简单,也比较定制化。这次自己写起来第一个问题就是处理children的问题。 首先要思路清晰,什么样的才是符合条件的Banner子元素,其余的剔除掉。 还有就是最内层自定义内容的处理, 这部分有一点比较重要的就是css属性,其实所有组件都应该如此。给用户足够的自由去自定义组件的整体或部分的样式。 起始这个问题算是借鉴antd里面的处理方式,用户传入的参数一个不少的全都保留下来,并且对class做拼接,style做合并,但保证用户的优先级要更高。

实现2

当整体的结构固定下来之后(尽管代码还不完善), 就应该考虑主要需求了,

  • 图片转起来
  • 自动播放?
  • 播放顺序?
  • 每幅图片停留时间?

这里面要讲一下的就是图片转起来的方式。
然后想到两种方案。

  • 假设五张图片五张图片就横向排开,这样宽度就是500%, 然后动态的改变left, 容器overflow: hidden
  • 五张图片层叠堆起来,所占的位置就一个,不显示的就隐藏掉,这样好像dom结构更简单。

于是呢采用了第二种,简单啊。 5个元素的位置固定,控制visible就可以了。然后呢也确实简单。 那么下一个问题就是咋么播起来、转起来的问题了。

实现3

转起来那肯定是动画喽, 先抽象一下,大概有4个动画

  • 从中间向右 ---> 隐藏
  • 从中间向左 ---> 隐藏
  • 从右向中间 ---> 显示
  • 从左向中间 ---> 显示

然后呢, 思路也比较简单, js控制判断哪个图片要做哪个动画,然后为这个元素添加一个类, 然后再删掉这个类名就好了。

展示其中一个动画, 只控制了位置,控制透明度来增加渐入渐出是另外的类名,确保每个类作该做的事情。

@-webkit-keyframes moveToRight {
  from {
    -webkit-transform: translateX(0%);
  }
  to {
    -webkit-transform: translateX(100%);
  }
}

然后效果还不错。

bug1

之后呢发现如果快速点击就完蛋了, 容易出现js控制跟不上问题。 寻找问题在于上一个动画还没结束下一个动画就进来了。 之后做了一点**斗争,是应该结束掉动画呢,还是让当前动画执行完呢。 其实实现起来都比较简单。 但还是让当前动画执行完毕吧, 考虑点主要有两个

  • 没个动画执行时间非常短只有几百毫秒 正常状况下 除非用户疯狂点击,一般不会出现乱动的问题
  • 动画如果执行一半就立即切换状态 生硬

综合这两点,给每个Banner增加了一个是否在动画的状态flag来决定能不能继续添加动画。

总结

本想放一张gif图在这个位置,但无奈录屏了几次 都太大了。都要100M,可能是分辨率太高的问题。所以放一张手机拍的吧,效果不是很好。
gif
过去几个月也发布了几个小npm包

  • 格式检查 这个也一直在用,主要用于ajax前对数据进行格式检查,然后再提示用户是否正确。蛮好的
  • 数字转汉子 目前就应用过一次,虽然目前能达到需求,但内部逻辑日后还有很大改进空间

jQuery chapter 4 数据缓存Data----jQuery源码分析系列

chapter 4 数据缓存Data

data部分提供为每个dom或js对象添加数据的功能。可能不是特别常用,但其为其他模块提供了支持,比如动画、队列、样式操作等模块。

这部分代码也并不复杂。全局有一个cache对象内部存储dom元素对应的对象,每个dom都会添加一个唯一标签,通过标签,cache内的对象可以与dom一一对应。为js对象添加则更加简单,为对象添加一个不会被占用的属性名即可。

先看整体结构,比较清晰,检测元素是不是允许设置数据,允许则先计算一些标签信息,再设置属性、最后再增加移除数据的接口。

jQuery.extend({
	cache: {}, // cache就是全局盛放dom数据的容器
	uuid: 0,   // dom唯一标签的计数器
	expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
	noData: { },
	hasData: function( elem ) // 检测元素是否有对应的数据
	data: function( elem, name, data, pvt /* Internal Use Only */ ) // 设置或者读取数据
	removeData: function( elem, name, pvt /* Internal Use Only */ ) // 移除数据
	_data: function( elem, name, data )  // 设置读取内部数据
	acceptData: function( elem ) // 检测是不是允许设置数据
});

jQuery.fn.extend({
	data: function( key, value )    // 对外接口,设置、读取数据

	removeData: function( key )  // 移除数据
});

下面还是一块一块看,先从jQuery.extend来看, 一般它都是jQuery.fn.extend的基础。

源码 jQuery.extend

acceptData

主要是防止三类dom,embed、applet和flash,这三类不支持扩展属性,因此无法设置标签,没有标签就没办法让数据和dom进行统一,标签就像id一样是唯一的,是dom和cache沟通的桥梁。

// 判断一个dom元素是不是可以设置数据
acceptData: function( elem ) {
  // 如果不是dom则一定可以,直接返回true
  if ( elem.nodeName ) {
    // 看dom的name是不是在noData里面,如果再则取出来,不再则match=undefined
    var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
    // 如果dom是embed或者applet则return false, 如果是object恰巧是falsh则也返回false
    if ( match ) {
      return !(match === true || elem.getAttribute("classid") !== match);
    }
  }
  return true;
}

noData: {
  "embed": true,
  // Ban all objects except for Flash (which handle expandos)
  "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
  "applet": true
},

data 为js对象或者dom设置元素

data: function( elem, name, data, pvt /* Internal Use Only */ ) {
  // 不支持设置 直接return
  if ( !jQuery.acceptData( elem ) ) {
    return;
  }

  var privateCache, thisCache, ret,
    internalKey = jQuery.expando,
    getByName = typeof name === "string",

    // 判断是不是dom元素
    isNode = elem.nodeType,

    // 如果是dom元素则cache就是jQuery.cache这个对象,否则就是js对象本身
    // 分开设置是因为 js本身的数据可以直接被GC收集掉
    cache = isNode ? jQuery.cache : elem,

    // 如果是dom元素,则返回 elem[ internalKey ] 有可能是undefined
    // 如果是js对象,之前如果设置过数据则elem[ internalKey ]为真,返回internalKey, 否则false
    id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
    isEvents = name === "events";

  // 如果是要获取某个元素的数据,但没有数组则直接返回
  if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
    return;
  }

  if ( !id ) {
    // 如果之前没设置过数据
    // dom元素的话就增加一个固定属性名(internalKey的值),值为自增的jQuery.uuid
    // 果然是js对象,则id就为internalKey的值
    if ( isNode ) {
      elem[ internalKey ] = id = ++jQuery.uuid;
    } else {
      id = internalKey;
    }
  }

  // 刚才也说过了,cache会是一个引用。jQuery.cache或者js元素本身
  if ( !cache[ id ] ) {
    cache[ id ] = {};

    // jQuery这部分数据不应该被字符串序列化时出现。
    if ( !isNode ) {
      cache[ id ].toJSON = jQuery.noop;
    }
  }

  // 如果传入的是对象,则把传入的内容扩展到现有的数据上
  // pvt控制是内部数据还是自定义数据
  if ( typeof name === "object" || typeof name === "function" ) {
    if ( pvt ) {
      cache[ id ] = jQuery.extend( cache[ id ], name );
    } else {
      cache[ id ].data = jQuery.extend( cache[ id ].data, name );
    }
  }

  privateCache = thisCache = cache[ id ];

  // 如果是不是pvt,则代表是自定义数据,则cache的真正目的地是cache.data
  // 过了这里之后也就是thisCache一定指向正确的cache对象。
  if ( !pvt ) {
    if ( !thisCache.data ) {
      thisCache.data = {};
    }

    thisCache = thisCache.data;
  }

  // camelCase: function( string ) {
  //    return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
  //  },
  // rmsPrefix = /^-ms-/,
  // rdashAlpha = /-([a-z]|[0-9])/ig,
  // fcamelCase = function( all, letter ) {
  //    return ( letter + "" ).toUpperCase();
  //  },
  // 只要data为真就用驼峰的方式设置名字和数据。
  if ( data !== undefined ) {
    thisCache[ jQuery.camelCase( name ) ] = data;
  }

  // Users should not attempt to inspect the internal events object using jQuery.data,
  // it is undocumented and subject to change. But does anyone listen? No.
  // 一段非常*的注释:
  // 用户不应该试图使用jQuery.data去查看内部events对象,
  // events是没有注解并且可能会更改的。但是谁会听呢? 没人听。
  // 如果用户要查看events属性,并且不是自定义的那部分,则把真正的events返给用户.
  // 很无奈的注释
  if ( isEvents && !thisCache[ name ] ) {
    return privateCache.events;
  }

  // 通过查看为转换驼峰和转换驼峰的name来查看数据,有就返回, 
  // 如果都不是getByName则直接返回整个缓存的数据
  if ( getByName ) {
    // First Try to find as-is property data
    ret = thisCache[ name ];
    // Test for null|undefined property data
    if ( ret == null ) {
      // Try to find the camelCased property
      ret = thisCache[ jQuery.camelCase( name ) ];
    }
  } else {
    ret = thisCache;
  }

  return ret;
},

_data惨不忍睹,专用于设置内部数据

_data: function( elem, name, data ) {
  return jQuery.data( elem, name, data, true );
},

hasData

判断一个dom元素或者js对象有没有通过jQuery设置过数据,当然也不能是空对象

hasData: function( elem ) {
  elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
  return !!elem && !isEmptyDataObject( elem );
},

removeData 移除数据

removeData: function( elem, name, pvt /* Internal Use Only */ ) {
  // 压根不会设置数据 直接return
  if ( !jQuery.acceptData( elem ) ) {
    return;
  }

  // 这段var就获取了dom的id或者internalKey,也就是cache的属性名
  var thisCache, i, l,
    internalKey = jQuery.expando,
    isNode = elem.nodeType,
    cache = isNode ? jQuery.cache : elem,
    id = isNode ? elem[ internalKey ] : internalKey;

  // 如果没有这个属性名对应的值,则直接返回
  if ( !cache[ id ] ) {
    return;
  }

  if ( name ) {
    // 根据pvt来修正 以让thisCache指向正确的cache对象
    thisCache = pvt ? cache[ id ] : cache[ id ].data;
    if ( thisCache ) {
      // 要将name变成数组的形式,不是数组则转换格式
      if ( !jQuery.isArray( name ) ) {
        // 如果恰巧是单个name字符串,恰巧也存在那么直接name = [name]
        if ( name in thisCache ) {
          name = [ name ];
        } else {
          // 再试试能不能先驼峰转换完了有, 有则[name], 没有则安空格split成数组
          name = jQuery.camelCase( name );
          if ( name in thisCache ) {
            name = [ name ];
          } else {
            name = name.split( " " );
          }
        }
      }
      // 循环数组delete属性
      for ( i = 0, l = name.length; i < l; i++ ) {
        delete thisCache[ name[i] ];
      }

      // 如果删除之后不是空对象则return
      if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
        return;
      }
    }
  }

  // 如果name有值,那么正常应该已经return了, 走到这里证明name的数据移除完,剩余的是个空数据容器了
  // 如果是删除自定义属性,则要删除自定义数据,同时删除自定义数据的容器.data
  if ( !pvt ) {
    delete cache[ id ].data;
    // 如果剩下的不是空对象则return
    if ( !isEmptyDataObject(cache[ id ]) ) {
      return;
    }
  }

  // 优雅降级的表现 能delete则delete,否则设置null
  if ( jQuery.support.deleteExpando || !cache.setInterval ) {
    delete cache[ id ];
  } else {
    cache[ id ] = null;
  }

  // 优雅降级的表现, 在dom上面, 如果支持delete扩展属性就delete,其次removeAttribute,最后是设置为null
  if ( isNode ) {
    if ( jQuery.support.deleteExpando ) {
      delete elem[ internalKey ];
    } else if ( elem.removeAttribute ) {
      elem.removeAttribute( internalKey );
    } else {
      elem[ internalKey ] = null;
    }
  }
},

源码 jQuery.fn.extend

removeData

先看简单的
很明显这是为$()获取到的数组应用each来为每个元素都执行removeData操作的,同时都是移除自定义数据

removeData: function( key ) {
  return this.each(function() {
    jQuery.removeData( this, key );
  });
}

data

data: function( key, value ) {
  var parts, attr, name,
    data = null;
  // 如果key是undefined,返回第一个dom对应的缓存数据
  if ( typeof key === "undefined" ) {
    // 当前获取到了元素,长度才会大于0
    if ( this.length ) {
      // data设为第一个元素对应缓存数据
      data = jQuery.data( this[0] );
      // 是dom元素 且没有对应的data中没有parseAttrs属性
      if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) {
        // 获取dom元素所有属性的集合,
        attr = this[0].attributes;
        for ( var i = 0, l = attr.length; i < l; i++ ) {
          name = attr[i].name;
          if ( name.indexOf( "data-" ) === 0 ) {
            name = jQuery.camelCase( name.substring(5) );
            dataAttr( this[0], name, data[ name ] );
          }
        }
        // 在遍历完dom属性之后为dom设置属性名为parsedAttrs,值为true的内部属性
        jQuery._data( this[0], "parsedAttrs", true );
      }
    }
    return data;
  } else if ( typeof key === "object" ) {
    // 如果key是一个对象,则为每一个dom元素都执行jQuery.data来设置属性
    return this.each(function() {
      jQuery.data( this, key );
    });
  }

  // key既不是undefined也不是object, 那应该是string了
  parts = key.split(".");
  parts[1] = parts[1] ? "." + parts[1] : "";
  // 如果有key,且value是undefined则应该是获取数据
  if ( value === undefined ) {
    // 尝试用getData来获取数据, 暂时没看这个东西 太复杂
    data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);

    // 上一步获取失败了,再从缓存数据里获取,再尝试dataAttr获取
    if ( data === undefined && this.length ) {
      data = jQuery.data( this[0], key );
      // 如果在缓存数据里找到了,下面这行是不会执行的,稍后会看到分析
      data = dataAttr( this[0], key, data );
    }
    // 如果这次找到data,直接返回,走则去掉parts[1],再执行一个data尝试一次
    return data === undefined && parts[1] ?
      this.data( parts[0] ) :
      data;

  } else {
    // 如果有key和value 则为每一个元素都设置缓存数据,依旧是triggerHandler,暂时看不懂
    return this.each(function() {
      var self = jQuery( this ),
        args = [ parts[0], value ];

      self.triggerHandler( "setData" + parts[1] + "!", args );
      jQuery.data( this, key, value );
      self.triggerHandler( "changeData" + parts[1] + "!", args );
    });
  }
},

外部源码 不归属于jQuery.extend 和 fn.extend

dataAttr

function dataAttr( elem, key, data ) {
  // 如果data不是undefined则直接返回undefined
	if ( data === undefined && elem.nodeType === 1 ) {
    // rmultiDash = /([A-Z])/g;
    // 修正变量名从驼峰到data-a-b-c
		var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
		data = elem.getAttribute( name );
		if ( typeof data === "string" ) {
      // 如果是字符串,尝试把字符串还原本身格式,true、false、null、数字、对象, 如果都不能还原则当作普通字符串返回
			try {
        // rbrace = /^(?:\{.*\}|\[.*\])$/
				data = data === "true" ? true :
				data === "false" ? false :
				data === "null" ? null :
				jQuery.isNumeric( data ) ? parseFloat( data ) :
					rbrace.test( data ) ? jQuery.parseJSON( data ) :
					data;
			} catch( e ) {}
			// 顺便再把刚刚解析出来的数据data-*的数据混存到jQuery.cache[id].data里面
			jQuery.data( elem, key, data );
		} else {
      // 只要data不是字符串(可能是null)那就是没找到,直接设为undefined返回
			data = undefined;
		}
	}
	return data;
}

cleanData: function( elems ) {
  var data, id,
    cache = jQuery.cache,
    special = jQuery.event.special,
    deleteExpando = jQuery.support.deleteExpando;

  for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
    if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
      continue;
    }

    id = elem[ jQuery.expando ];

    if ( id ) {
      data = cache[ id ];
      if ( data && data.events ) {
        // 遍历events内的数据
        for ( var type in data.events ) {
          //按两种方式移除事件监听或代理, 还不完全清楚event部分
          if ( special[ type ] ) {
            jQuery.event.remove( elem, type );
          } else {
            jQuery.removeEvent( elem, type, data.handle );
          }
        }
        // Null the DOM reference to avoid IE6/7/8 leak (#7054)
        // 设置为null以防ie678内存泄漏
        if ( data.handle ) {
          data.handle.elem = null;
        }
      }
      // 在dom上移除为缓存数据创建的标签
      if ( deleteExpando ) {
        delete elem[ jQuery.expando ];
      } else if ( elem.removeAttribute ) {
        elem.removeAttribute( jQuery.expando );
      }
      // 删除缓存数据占用的那个对象
      delete cache[ id ];
    }
  }
}

总结

对于数据缓存其实分两个部分 一个是dom,一个是js对象。 在整个页面初始化时,会得到一个唯一的jQuery开头的id字符串,这个id将作为之后数据缓存的标签id。

  • js对象比较简单,直接在js对象的内部增加一个属性名为标签id的对象。
  • dom也是为dom元素增加一个一个属性名为标签id的属性,但值为一个从0开始的全局自增的数字,同时会为jQuery.cache这个全局对象增加一个相同数字的属性,这样也为dom元素找到了一个盛放数据的地方。

无论dom还是js对象的缓存数据内部都分自定义数据和内部数据。 自定义数据放在data这个子属性内,内部数据直接挂在缓存对象上。

在代码里有一个地方处理的很巧妙,尽管分为dom和js两部分,但实现的时候先获取到承载数据的那个对象,只要获取的缓存数据的那个对象,后面的处理逻辑就都一样了,其实都是js object的操作了。

JS 杂碎点笔记

0、 基本数据类型
Undefined、Null、Boolean、Number、String
1、parseInt()、 Number()、parseFloat()
三个方法都属于window
Number():

  • Boolean 转为 1、0
  • null转为0
  • undefined 转为 NaN
  • 任何不符合要求都转为NaN
  • 空字符串转为 0
  • 无参数也转为 0
    parseInt(string, radix):
  • 空字符串为NaN
  • 无参数转为 NaN
  • 尽可能的去转换, 尽可能的不报NaN parseInt('23xd') --> 23
  • 使用时尽可能的传入radix
    parseFloat()
  • 只解析十进制
  • 16进制始终转换为 0

2、 Number.prototype.toString(radix)

尽量传入radix以确保正确的十进制

3、String

字符串是不可变的,也就是说字符串一旦创建,值就不能改变
a = 'a'; a= a + 'b'
先创建更大的新字符串,完成拼接工作,再销毁掉原本'a'。

4、Object.isPrototypeOf()

用于检测传入的对象是否是传入对象的原型

let a = {}, b = Object.create(a);
a.isPrototypeOf(b)   // true

5、Object.propertyIsEnumerable(propertyName)

检查属性是否支持枚举、是否能for-in出来

6、 ++、--操作转换

有效的数值字符串 --> 数字

无效的数值字符串('23rdf') --> NaN

false --> 0

true --> 1

object依次调用valueOf() --> toString()

7、位运算
ES规定所有的数值都是以IEEE-754 64位格式存储的,但真正操作中后台会转为32位的整数,然后执行操作,最后再转换回64位,因此开发人员是无法感知64位存储格式的。

其中前31位是整数的值, 32位表示符号位, 0是整数,1是负数

正数以纯二进制形式存储
18: 10010

负数以补码的形式储存
-18: 1111 1111 1111 1111 1111 1111 1110 1110

补码的计算步骤

  1. 绝对值的二进制码
  2. 二进制的反码,即0和1互换
  3. 反码 + 1

正是由于64位与32位之间存在转换关系,导致一个bug,即NaN与Infinity在2进制计算过程中会被当作0(至于为啥不知道...)
~NaN === ~Infinity === -1

~按位非 求反码(变换符号再减1)
~25 === -26
~-25 === 24

& 按位与

| 按位或

^ 按位异或

<< 左移 将数值的所有位向左移动指定位数,右侧用0来补齐,左移不会影响符号位

>> 右移 与左移刚好相反,右移出来的空位用0来补充,也不影响符号位

>>> 无符号右移 不保留符号位的右移
导致正数的 >>> 与 >> 效果一致
负数会变为正数,因为符号位被0填充,同时会非常巨大,因为负数原本以补码形式存储

8、 *转换规则

  • 超出范围为 Infinity 或 -Infinity
  • 有一方为NaN, 结果为NaN
  • Infinity * 0, 结果为NaN
  • Infinity * 非0, 结果为Infinity加符号位计算
  • Infinity * Infinity, 结果Infinity
  • 非数值,调用Number()再计算

9、 /转换规则

  • 超出范围为 Infinity 或 -Infinity
  • 有一方为NaN, 结果为NaN
  • 0 / 0, 结果NaN
  • 0 / Infinity, 结果0加符号位
  • 非0 / 0, 结果为Infinity加符号位
  • 非0 / Infinity, 结果为Infinity加符号位
  • Infinity / Infinity, 结果NaN
  • 非数值,调用Number()再计算

10、% 取余转换规则

  • Infinity / 有限数, 结果为 NaN
  • 有限数 / Infinity, 结果为有限数
  • Infinity / Infinity, 结果位 NaN
  • any / 0, 结果位NaN
  • 非数值,调用Number()再计算

11、 +号规则

都是数值:

  • 一方为NaN,结果为NaN
  • Infinity + -Infinity = NaN
  • +0 + -0 = +0

有字符串时,双方都转为字符串再做拼接

对象、数组、布尔调用toString(), undefined、null调用String()方法获取对应字符串

但是!!!

[] + {} + undefined + null --> "[object Object]undefinednull"

undefined + null --> NaN

12、 - 规则

  • 一方为NaN,结果为NaN
  • Infinity - -Infinity = NaN
  • -Infinity - Infinity = NaN
  • +0 - -0 = -0
  • -0 - -0 = +0
    字符串、布尔、null、undefined调用Number()
    对象一次调用valueOf() toString()

13、比较符号 > < >= <=

  • 双方数值 直接比较
  • 双方字符串, 对比字符编码值
  • 一方数值,另一方转为数值再比较
  • 任何数值和NaN比较都会得到false
'Brick' < 'alphabet'  // true B: 66  a:97
'23' < '3' // true 

14、 ==
== 在转换时会尽量向数值上面靠,
但是在向数值转换前有几个要求

  • null == undefined
  • null 和 undefined 不应被转换为任何数值
  • NaN 出现则返回false
  • 对象则判断是不是引用同一个实体

15、var与 no var

es5中 不使用var会将变量定义到全局环境下,即window... 容易搞事情

16、label配合for循环 !!!废弃 不推荐使用

类似于c语言的go语法

outermost:
  for(var i=0 ;i< 10; i++){
    for(var j=0 ;j< 10; j++){
      if( j == 5 && i == 5){
        break outermost;
      }
      console.log( '' + i + j)
    }
  }

break outermost;起到一次性跳出两个循环的作用,最后只会打印到54
continue outermost; 起到一次性跳出两个循环的作用,但是还会进去的。。。。 也就是说打印完54,下一次就是60了

17、 with(object) !!!废弃 不推荐使用, 严格模式直接报错

with(obj){
  console.log(key);
}

在with语句块内,变量会先寻找局部变量,找不到在寻找obj上面是否有同名属性。

是不是有点全局变量window的意思,但是不可以使用,性能差,而且不支持严格模式。

18、 原始数据类型和引用数据类型 内存区别
栈:原始数据类型(Undefined,Null,Boolean,Number、String)
堆:引用数据类型(对象、数组和函数)

两种类型的区别是:存储位置不同;
原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体

19、没有块级作用域,只有作用域(执行环境)(ES5)

if(true){
  var color = 'red'
}
alert(color) // 正确
for(var i=0; i< 10; i++){var m = 100}
alert(i, m) // 正确

20、 IE、opera可以主动触发垃圾回收 !!!不建议

IE: window.CollectGarbage()

Opera: window.opera.collect()

21、new Array()

使用new Array()Array()效果是一样的。

new Array(arg) 参数长度为1时要注意:

  new Array(20) // []长度为20,用undefined填充
  new Array('20') // [ '20' ]  长度为1,内容为字符串20
  [,,,]  // IE8及其以下为 [undefined, undefined, undefined, undefined]
          // 其余浏览器为 [undefined, undefined, undefined ]
          // 也就是说最后一位尽量不要留逗号

22、数组方法

  • 改变原数组:reverse、sort、
  • 产生新数组:slice、concat、splice
  • 其余方法:indexOf、lastIndexOf、reduce、reduceRight、every、filter、forEach、map、some、

23、Array.prototype.indexOf()

可以接受第二个参数表示查找的开始位置 arr.indexOf(searchElement[, fromIndex])

24、Date()

// 两者返回的是距离1970、1、1零时的毫秒数
Date.parse() // 需要看浏览器心情,差异性很大
Date.UTC(2005, 4, 1, 1, 59, 59)  // 年、月、日、时、分、秒    月0-11 省略日默认1, 其余省略默认0

Date.now() // 静态方法获取当前时间毫秒数
+ new Date() // 在不支持的浏览器上兼容

25、 函数复制

函数复制(赋值)是真的复制。看例子

// example 1
function a(){ console.log('a') }
b = a;
a();   // ok
b();   // ok
a = null;
a();   //error
b()    // ok
// example 2
var a = function(){ console.log('a') }
b = a;
a();   // ok
b();   // ok
a = null;
a();   //error
b()    // ok

26、arguments.callee !!! 不建议使用,不允许使用

一个神奇的方法,可以获得参数的调用函数自身,在递归中可以使用,方便获取自身。但是!!!

警告:在严格模式下,第5版 ECMAScript (ES5) 禁止使用 arguments.callee()。当一个函数必须调用自身的时候, 避免使用 arguments.callee(), 通过要么给函数表达式一个名字,要么使用一个函数声明.

27、Function.caller !!! 不建议使用,不允许使用

caller是javascript函数的一个属性,它指向调用当前函数的函数,如果函数是在全局范围内调用的话,那么caller的值为null。

28、arguments.caller !!! 不建议使用,不允许使用

arguments.caller 这是我们遇到的第二个caller,没啥用,在严格模式下无法访问,非严格模式下值也为undefined,而且貌似被废弃了

29、String.prototype.slice、String.prototype.substring.prototype.substr

只传入一个参数,且为正数,则结果一致,表示从某一位开始截取到最后

传入两个正参数,slice、与 substring结果一致,表示从第几位到第几位, substr表示从第几位开始截取几位长度的字符串

slice中负数表示倒着数就可以了

substr只能识别第一位为负数,第二位出现负数会将第二位转为0

substring会将所有负数转为0

var a = 'hello world';
a.slice(4); //"o world"
a.slice(4,7);  //"o w"
a.slice(-7, -4); //"o w"

a.substring(4); //"o world"
a.substring(4,7);  //"o w"
a.substring(-4);  //"hello world"
a.substring(-4, -7);  //""

a.substr(4);  //"o world"
a.substr(4, 1);  //"o"
a.substr(-1); //"d"
a.substr(-1, 0); //""

30、String.prototype.replace

如果参数1传入的是字符串,那么只会替换一次,传入带有g符号的正则才会全局替换

31、String.prototype.localCompare

str.localCompare(str2)按字符一个一个比较两者之间在字符标的前后顺序,如果str在str2参数之前返回-1, 如果在其之后返回1, 相等返回0

32、String.fromCharCode()

所做之事与charCodeAt()刚好相反

String.fromCharCode(104, 101, 108, 108, 111) --> 'hello'

33、Math

Math.ceil() // 向上取舍
Math.floor() // 向下取舍
Math.round() // 标准取舍

34、Object.key 数据属性、访问器属性

对象的每一个属性(property)都包含数据属性和访问器属性。
数据属性包含[[Configurable]] [[Enumerable]] [[Writable]] [[Value]]
访问器属性包含[[Configurable]] [[Enumerable]] [[Get]] [[Set]]

可以看出两者都包含[[Configurable]] [[Enumerable]]属性。 那什么时候会用到呢

let book = {};
Object.defineProperty(book, 'author', {});
// 在这里第三个属性就是描述属性, 但是只能传入数据属性或者访问器属性,不能串了,否则报错

35、Object.getPrototypeOf(obj)

获取一个对象的原型, 也是chrome私有实现的obj.__proto__的规范实现

36、for in、Object.keys()、Object.getWonPropertyNames()

  • for..in.. 是对某个对象的可枚举属性进行遍历,不保证属性出现顺序,不保证属性属于对象本身还是原型链
  • Object.keys() 是某个对象的所有可枚举属性名组成的数组,数组内容与for..in..一致,甚至顺序与for..in..一致。
  • Object.getWonPropertyNames() 同样返回一个数组,但是返回的是对象自身的所有属性名组成的数组

37、Object.create(proto, properties)

Object.create(proto) 作为一种常见的实现继承的方式已经不难理解,但它还接受第二个参数,并且第二个参数与Object.definieProperties()的第二个参数格式一致,将作为新对象的熟悉。

38、location

location即属于window对象又属于document,并且两者是对同一对象的引用 window.location === document.location

location.assign(new_url); //location.href、window.location赋值也是触发此函数
location.replace(new_url);
location.reload();

39、检测浏览器插件

浏览器插件在很多时候是很多网站工作的必备元素,例如很多网站需要flash, 可以提前检测flash,如果不存在就让提示用户去下载。

在标准浏览器中呢

navigator.plugins  // 返回一个数组,包含当前开启的所有插件
plugin = {
  description : "Enables Widevine licenses for playback of HTML audio/video content. (version: 1.4.8.1000)",
  filename : "widevinecdmadapter.plugin",
  length : 1,
  name : "Widevine Content Decryption Module"
}

IE

try{
  new ActiveXObject(COMname)
  return true;
}catch(e){
  return false;
}

40、HTMLCollection

HTMLCollection[0]、HTMLCollection.item(0)、HTMLCollection.namedItem('name')
当我们通过document.getElementsByXX获取到的是一个HTMLCollection类数组对象,它可以直接通过下标[0]来获取第一个元素,也可以通过.item(0)来获取第一个元素,同时还可以HTMLCollection.namedItem('name')来根据name属性获取对应元素

41、NodeList、NamedNodeMap、HTMLCollection实时动态性

这三个神奇的亲属关系的元素类型有一个神奇的特点。那就是特们总是保持最新的页面元素。换句话讲什么意思呢,代码表示

var a = document.getElementsByTagName('div'); // a.length = 249
document.body.append(document.createElement('div'));
a.length // 250

看到没,明明对a(HTMLCollection)没有操作,但是a的内容却根据页面的变化而实际变化。 所以对这三者的操作要注意。

  • NodeList由Node.childNodes 和 document.querySelectorAll产生
  • HTMLCollection由Element.getElementsBy产生
    (JS高级编程10.2.4标出,但实测chrome下document.querySelectorAll产生的NodeList并不具有像getElementsByTagName获取到的HTMLCollection那样具有实时动态性)
    (但是实测a = document.body.childNodes产生的NodeList具有实时动态性)

42、compatMode 渲染模式

渲染模式一般分为标准模式和混杂模式。 document.compatMode可以获取当前的渲染模式,值为 CSS1Compat 或者 BackCompate。 那种是标准应该非常容易分辨喽

43、Element.insertAdjacentElement( position, element);

方法将一个给定的元素节点插入到相对于被调用的元素的给定的一个位置。
这个方法的兼容性简直爆炸,can.i.use显示基本全部兼容(firefox47以前不兼容),但好像平时很少使用它。

/*
'beforebegin': 在该元素本身的前面. 作为同级元素
'afterbegin':只在该元素当中, 在该元素第一个子孩子前面.
'beforeend':只在该元素当中, 在该元素最后一个子孩子后面.
'afterend': 在该元素本身的后面. 作为同级元素
*/
document.body.('afterbegin', document.createElement('div'))

44、contains

如果判断一个元素是不是某个元素的子元素,可以直接通过parentElem.contains(childElem)的结果true or false来判断,而且兼容性还是很好的,除了在非常低的safari下(但是apple的升级率很高啊,哪还有低版本safari)

45、scrollIntoView、scrollIntoViewIfNeeded

两者都是作用在元素上的,

  • elem.scrollIntoViewIfNeeded会判断当前元素是不是在显示区域内,如果不在就滚动页面让其显示,
  • elem.scrollIntoView会滚动页面让元素显示在最上方
    scrollIntoView兼容性不错,scrollIntoViewIfNeeded则惨不忍睹

46、DOM2增加了xhtml命名空间

html本身是不支持命名空间的,但由于xml支持,所以xhtml也支持了命名空间。

与命名空间相关的方法在方法名字中都有NS或者以NS结尾。

47、document.defaultView、document.parentWindow

使用document.defaultView是获取window的一个途径,有时是在elem.ownerDocument.defaultView, 但此方法在IE9以下不被支持,替代方法 document.parentWindow

48、访问iframe内容

iframeElem.contentDocument || iframeElem.contentWindow.document

49、style操作

window.getComputedStyle(elem) 返回的样式是一个实时的 CSSStyleDeclaration 对象,当元素的样式更改时,它会自动更新本身;

CSSStyleDeclaration.cssText;
CSSStyleDeclaration.length;
CSSStyleDeclaration.parentRule;
CSSStyleDeclaration.getPropertyPriority(propertyName);
CSSStyleDeclaration.getPropertyValue(propertyName);
CSSStyleDeclaration.setProperty(propertyName, value, priority)
CSSStyleDeclaration.removeProperty(propertyName)

50、offset、client

  • 偏移量 offsetHeight、offsetWidth不包含margin、包含border、padding
  • 客户区 clientHeight、clientWidth只包含padding

51、document.createNodeIterator(elem)、document.createTreeWalker(elem)

  • createNodeIterator:返回Iterator遍历对象,可以用来nextNode()一直循环便利elem的所有元素
  • createTreeWalker: 返回一个方法更佳丰富的遍历对象

52、document.createRange()

范围,可以选择文档的一个区域,IE单独实现的是document.createTextRange()。 range表示包含节点和部分文本节点的文档片段。 一般在处理选择文本是常用到。

53、canvas宽与高

canvas元素比较特殊有两个尺寸,一个叫元素尺寸,一个是绘图尺寸。

  • canvas.style / css方式更改的事元素尺寸
  • canvas.width / 更改的事绘图尺寸
  • canvas默认两个尺寸都是300*150
  • 因此如果只改变元素尺寸,实际绘图可能会被放大或者缩小
  • 只更改绘图尺寸可能造成图片不能占满或者超出元素大小

54、 Error

  • Error 所有错误的基类
  • EvalError 调用eval方法时
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError 保存意外类型 或者访问不存在的方法时
  • URIError encodeURI和decodeURI时出错

55、JSON

  • JSON.stringify在序列化对象时函数和原型成员都会被忽略掉,同时undefined的任何属性也会被忽略掉。
  • JSON.stringify第二个参数 过滤器
      a = { a: 1, b:2, c:3 };
      // 数组格式   表示保留的属性名
      JSON.stringify(a, ['b', 'a']);   // "{"b":2,"a":1}"
      JSON.stringify(a, ['b'])  // "{"b":2}"
      // 函数格式 过滤函数
      JSON.stringify(a, (key, value)=>{ 
        if(key === 'a') {
          return 10000
        } else {
          return value
        }
      })   // "{"a":10000,"b":2,"c":3}"
  • JSON.stringify第二个参数 缩紧和空白符, 范围为0-10的数字 表示缩紧和空白符,可读性更高
      JSON.stringify(a, null, 4);
      // "{
      //    "a": 1,
      //    "b": 2,
      //    "c": 3
      // }"
    • JSON.parse 第二个参数是还原函数,也就是JSON.stringify对应的过滤函数
      a = { a: 1, b:2, c:3, d: new Date() };
      aa = JSON.stringify(a);
      JSON.parse(aa, (key, value)=>{
        if(key === 'd'){
          return new Date(value)
        } else {
          return value
        }
      })

56、 http状态码 常用

  • 200 OK
  • 204 No Content 没有新文档,浏览器应该继续显示原来的文档。如果用户定期地刷新页面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的
  • 301 Moved Permanently 永久性转移, 客户请求的文档在其他地方,新的URL在Location头中给出,浏览器应该自动地访问新的URL
  • 302 Found 临时性转移,类似于301,但新的URL应该被视为临时性的替代
  • 304 Not Modified
  • 307 Temporary Redirect 和302(Found)相同。许多浏览器会错误地响应302应答进行重定向,即使原来的请求是 POST,即使它实际上只能在POST请求的应答是303时才能重定向。由于这个原因,HTTP 1.1新增了307,以便更加清除地区分几个状态代码: 当出现303应答时,浏览器可以跟随重定向的GET和POST请求;如果是307应答,则浏览器只能跟随对GET请求的重定向。
  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 405 Method Not Allowed 请求方法(GET、POST、HEAD、Delete、PUT、TRACE等)对指定的资源不适用。
  • 406 Not Acceptable 指定的资源已经找到,但它的MIME类型和客户在Accpet头中所指定的不兼容
  • 410 Gone 所请求的文档已经不再可用,而且服务器不知道应该重定向到哪一个地址。它和404的不同在于,返回407表示文档永久地离开了指定的位置,而404表示由于未知的原因文档不可用。
  • 500 Internal Server Error
  • 501 Not Implemented
  • 502 Bad Gateway 服务器作为网关或者代理时,为了完成请求访问下一个服务器,但该服务器返回了非法的应答

57、防篡改对象

  • Objec.preventExtensions 对象不能新增属性, 但原有属性可以更改 严格模式新增属性报错
  • Object.isExtensible检测
  • Object.seal 对象不能扩展、且现有属性的[[Configurable]]将变为false,意味着不能被删除
  • Object.isSealed检测
  • Object.freeze 对象既不能扩展,也不能修改
  • Object.isFrozen检测

58、监测网络状态

  • navigator.onLine属性可以查找到 true/false,但是存在bug,规范不统一
  • 为window添加online、offline事件, 可以获得网络状态变化事件
  window.addEventListener('offline', ()=>{alert('offline')})
  window.addEventListener('offline', ()=>{alert('online')})

59、应用缓存、离线应用、application cache、appcache

  • 关联描述文件
  • 描述文件
      CACHE MANIFEST
      #Comment
    
      file.js
      file.css
    
  • window.applicationCache是浏览器用来查看缓存文件的对象

60、globalStorage

  • globalStorage 不是通用实现
  • globalStorage 不是Storage的实现
  • globalStorage['wrox.com] 才是Storage的实现
  • globalStorage 目的是允许持久化数据跨域存储

61、IndexedDB 数据库存储

  • IndexedDB不是标准API,需要做判断
  • IndexedDB所有获取与设置操作都为异步操作, 基本都需要onsuccess、onerror回调函数。
  • IndexedDB需要在onupgradeneeded里面获取db的指针, 不可以在onsuccess里面获取
  • IndexedDB更新很快, 介绍setVersion的文章都是过期文章
// 增加示例
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;   
if(!window.indexedDB)
{
  console.log("Your Browser does not support IndexedDB");
}
let request = window.indexedDB.open('test', '2.0');
request.onsuccess = (e)=>{console.log('success')}
request.onerror = (e)=>{console.log('error')}
request.onupgradeneeded = (e)=>{ 
  window.db = e.target.result; 
  window.objectStore = db.createObjectStore("students", { keyPath : "rollNo" });
  console.log('onup')
};
setTimeout(()=>{
  let transaction = db.transaction(["students"],"readwrite");
  transaction.oncomplete = function(event) {
      console.log("Success :)");
  };
  transaction.onerror = function(event) {
      console.log("Error :(");
  };
  let objectStore = transaction.objectStore("students");

  objectStore.add({rollNo: rollNo, name: name});
}, 200)

62、Page Visibility API
现在iqiyi、pandatv的页面如果是后台标签页打开都不会开启播放,只是渲染页面。 大概就是使用这样的思路, 先判断页面是否是可见页面,如果不是可见页面就不播放视频。

  • document.hidden
  • document.visibilityState
  • visibilitychange

jQuery笔记small + 总结 ----jQuery源码分析系列

jQuery源码分析从开始(17-05-30)到现在(17-08-05)整整两月零五日,中间也因为工作暂停过,也高兴过头刷夜过,一路走来断断续续,好在没有放弃。回头看看写过的那些无意义无价值的注释恰恰是成长的过程。 单看某一段代码,某一段注释可能并不复杂,但当把整个项目系统的看过一遍以后,才发现原来一环扣一环,它的风格从一而终、它的写法自古不变,里面基本没有复杂的逻辑,有的只是更全更广的基础知识点。

  • 没经历过你不知道cssFloat、StyleFLoat的区别
  • 没经历过你不知道Regex.source是什么
  • 没经历过你不知道runtimeStyle又是什么

只有经历过才知道知识的匮乏,外面的世界更大,自己知道的更少,认识到更渺小的自己。
读过jQuery可能要比阅读一本生硬的设计模式会让你了解的更多,更重要的是你知道别人是如何设计,一天天一段段代码也会让你潜移默化的在日后给你这好像曾经在jQuerry中看到过、有过类似代码的感受。

虽然并不是真正的对jQuery全部分析,但核心core部分确实基本全部走过,感谢前辈们的各种经验、blog、问题回答让后人在遇到问题时会有更清楚的认识,特别感谢高云(nuysoft)老师的jQuery技术内幕这本书,本人基本是按照书本的流程和帮助才能走完这晦涩无趣生硬的源码。mock.js是nuysoft老师的又一力作,在发现这个事实后也去搂了一眼mock的源码,发现里面的注释依旧那么清楚, 也许这就是习惯的力量吧。

虽然标题是终,但前端的路途、学习的路途不可能终止,即使是jQuery的学习也不可能终止。这只是一小段旅途的休息站,日后的万里长城更等待着、静候着我去探索。

如何检测dom元素

target.nodeType

类数组对象

下标0,1,2,3数字且拥有length属性(arguments对象是一个很好的例子)
a = { 0: 1, 1: 2, 2: 3, length: 3};

类数组对象转换真正的数组

利用slice方法,但原对象的key一定是0,1,2,3数字, 且必须有length属性。
a = { 0: 1, 1: 2, 2: 3, length: 3};
b = Array.prototype.slice.call(a, 0);

apply和call

虽然都是改变方法执行的this值,第一个参数都是this,但是apply只接受一个多余参数,call可以接受多个。
apply会把第二个参入如果是数组扁平化一次,因此可以利用这个特性来实现二维数组转一位数组。
Array.prototype.push.apply({}, [1,2,3]) --> { 0:1, 1:2, 2:3, length:3}
Array.prototype.push.call({}, [1,2,3,4,5]) --> { 0:[1,2,3], length:1}

数字与字符串快速转变

i = +i;
i = i + ''

数字类型的判断方式

function isNumeric( obj ) {
return !isNaN( parseFloat(obj) ) && isFinite( obj );
}

数字字符串的正则匹配

/-?\d*(?:.\d*)?(?:[eE][+-]?\d+)?/g
-?号可有可无 \d数字不限(可以直接.5) (?:.\d*)? 小数部分可有可无 (?:[eE][+-]?\d+)? 科学计数部分可有可无

函数的四种创建方式

function func () {} // 普通方式
let func = function(){} // 匿名函数
new Function ( string ) // 利用字符串构建函数
还有一种类似函数的方法
eval('(' + string + ')') // eval利用

new Function可以创建可复用的函数, eval仅仅是立即执行传入的字符串内容,
stackoverflow 有一个不错的对比问题 website

函数执行上下文的一段解释, 翻译自 stackoverflow

Functions don't inherit the context they are called in. Every function has it's own this and what this refers to is determined by how the function is called. It basically boils down to:
函数的执行上下文不会继承自调用他们的环境。每个函数都有自己的作用上下文,他们执行上下文指向取决于函数如何被调用,基本规则如下:
"Standalone" ( func()) or as immediate function ( (function(){...}()) ) : this will refer to the global object (window in browsers)
独立的函数或者立即执行函数: this将指向全局对象(在浏览器中即window)
As property of an object ( obj.func()): this will refer to obj
作为属性的函数(obj.func): this指向obj
With the new [docs] keyword ( new Func() ): this refers to an empty object that inherits from Func.prototype
使用new Func()关键字创建的:this指向一个空对象,空对象继承自Func.prototype
apply [docs] and call [docs] methods ( func.apply(someObj) ): this refers to someObj
apply和call方法调用的函数:this执行传入的对象someObj

String.prototype.replace

str.replace(regexp|substr, newSubStr|function)
可以看出第一个参数 正则|子串 很好理解,
第二个参数 新子串|函数, 新子串也比较好理解, 函数好像不经常用到
function(substr, [group1, group2, group3...], startIndex, str)
函数的默认传入参数是这样构成的,很奇怪。 第一个参数是匹配到的子串, 倒数第二个参数是匹配到的子串开始index,最后一个参数是整个字符串, 中间部分可能没有,也可能有很多个。 主要是由正则中group的个数决定
i

call、apply、bind

1、三者都可以更改函数执行的上下文
2、call、apply会立即执行函数,bind返回一个新函数
3、apply只接受2个参数、call参数不限制, 同时apply会把第二个参数(数组格式)进行一个扁平化
4、bind也不限制参数个数,但新函数中bind传入的参数会排在最前面,原函数的参数依旧向后移

Regex.source

日常中可能很少碰到需要进行正则拼接,或者修改某个已经定义的正则。
但如果碰到 可以使用Regex.source 来获取正则的字符串形式。
下面是个例子, 修改一个正则再生产一个新的正则
/[\w]?/.source --> '[\w]?'
/[\w]?/.source.replace(/?/, '') --> '[\w]'
new RegExp(/[\w]?/.source.replace(/?/, '')) --> /[\w]/

String split 正则

如果需要用 空格 来分割字符串, 第一反应可能是 str.split( ' ' )
但str如果不太标准,例如有 ' '两个空格连在一起的情况, 是不是考虑用正则呢 str.split( /\s+/ )
'qwer asdf'.split(' ') --> ["qwer", "", "", "", "", "", "", "", "", "", "", "asdf"]
'qwer asdf'.split(/\s+/) --> ["qwer", "asdf"]
尽管这也不是万能的,比如 ' '.split(/\s+/) --> ["", ""], 但总是好一点点,
换种思路再来一遍!

innerHTML、innerText、textContent、createTextNode

首先所有innerHTML与outerHTML类似,在读取时返回的字符串中包含子代标签信息,outter包含自身标签
innerText和outerText都不会包含任何标签信息
在设置时innerHTML、outerHTML会解析字符串内的转义字符、标签信息等
innerText、outerText不会解析内容,直接字符串设置
textContent行为与innerText基本一致
createTextNode是创建文本标签,也不会对内部内容进行解析
当然兼容性不在讨论范围内

position

一个元素只设置position是没有意义的,必须要有top或者left, 否则容易出现bug

getAttributeNode、setAttributeNode与getAttribute、setAttribute

前两者是获取属性节点,是一个对象,后两者是直接设置属性或读取属性,虽然目的是相同的,但后两者在IE67有很大问题,前两者可以很好的解决这个问题。

float属性

float是css中一个很好的、很常用的属性,但是在获取设置float值的时候却不是那么简单的
在低版本ie中要读取float的姿势是 elem.style.styleFloat
ie9及其以后 elem.style.styleFloatelem.style.cssFloat都有
在chrome、ff中正确姿势是 elem.style.cssFloat(实测高版本chromeelem.style.float也可以)
但是呢? 上面两种方法都是基于 elem.style的,大家都知道这只能获取内连style
在获取计算float值时呢,getComputedStyle的作用就体现出来了
window.getComputedStyle(elem).cssFloat就可以取到计算后的样式了,同时高版本的chrome也可以window.getComputedStyle(elem).float, 甚至可以window.getComputedStyle(elem).getPropertyValue('float'), 如果使用getPropertyValue就只能使用float,因为要求传入的是css属性名, 同时传入的应该是'-'分隔的属性名而非驼峰规则。
但但是? IE没有window.getComputedStyle,只有elem.currentStyle('float')。或者呢使用elem.currentStyle.getAttribute('float'),使用getAttribute也是不需要考虑怪异名称的,只是需要驼峰属性名即可。
因此float属性读取和设置的时候还是要做if else判断的

opacity属性

低版本IE8及其以下是通过filter滤镜属性来实现opacity的, 同时ie中让filter生效的另一个因素就是要有layout属性, 触发layout的方法就是设置zoom值, zoom = 1
filter: alpha(opacity=50);
当然现在基本不用考虑IE8及其以下了,但是filter的功能却不知事opacity透明度那么简单, 毕竟filter在奇遇浏览器也有这个属性!!!
filter有14中滤镜

Alpha     让HTML元件呈现出透明的渐进效果
Blur     让HTML元件产生风吹模糊的效果
Chroma     让图像中的某一颜色变成透明色
DropShadow     让HTML元件有一个下落式的阴影
FlipH     让HTML元件水平翻转
FlipV     让HTML元件垂直翻转
Glow     在元件的周围产生光晕而模糊的效果
Gray     把一个彩色的图片变成黑白色
Invert     产生图片的照片底片的效果
Light     在HTML元件上放置一个光影
Mask     利用另一个HTML元件在另一个元件上产生图像的遮罩
Shadow     产生一个比较立体的阴影
Wave     让HTML元件产生水平或是垂直方向上的波浪变形
XRay     产生HTML元件的轮廓,就像是照X光一样

Alpha滤镜又有多种参数

Alpha 滤镜参数详解
参数名     说明     取值说明 
Opacity     不透明的程度,百分比。    从0到100,0表是完全透明,100表示完全不透明。
FinishOpacity     这是一个同Opacity一起使用的选择性的参数,当同时Opacity和FinishOpacity时,可以制作出透明渐进的效果,比较酷。    从0到100,0表是完全透明,100表示完全不透明。
Style     当同时设定了Opacity和finishOpacity产生透明渐进时,它主要是用赤指定渐进的显示形状。    0:没有渐进;1:直线渐进;2:圆形渐进;3:矩形辐射。
StartX     渐进开始的 X 坐标值    
StartY     渐进开始的 Y 坐标值    
FinishX     渐进结束的 X 坐标值    
FinishY     渐进结束的 Y 坐标值    

runtimeStyle

在IE中style对象有三类
elem.style: 获取内连样式属性
elem.currentStyle: 类似于getComputedStyle获取当前元素最终的属性
elem.runtimeStyle: 顾名思义,运行时样式。
前两者容易理解,那么什么是运行时样式呢
关于HTML Object中三个Style实例的区别
关于使用runtimeStyle属性问题讨论
这里恰巧有位m$的老哥的blog,讲解了runtimeStyle的个人理解。

runtimeStyle的修改不会立即同步到style,也不会立即展现到页面元素。 在jQuery中就是利用这个特性来计算 em%单位向px单位转换的。(pixelLeft属性帮助计算px)
上面文章也提到了

  • runtimeStyle属性一定要配对使用,即element.runtimeStyle.xxx = 'attribue';和element.runtimeStyle.xxx = '';配对

document对象

document.defaultView 返回window对象
document.documentElement 返回html元素
docuemnt.body 返回body元素
document.ownerDocument null
document.getRootNode === document
document.compatMode 表明当前文档的渲染模式是混杂模式还是"标准规范模式".
BackCompat"代表"混杂模式", "CSS1Compat"代表"标准规范模式".
document.designMode 整个文档是否可编辑

elem.ownerDocument 返回document对象
elem.getRootNode 返回document对象
elem.getRootNode() == elem.ownerDocument

jQuery chapter9 DOM遍历 ----jQuery源码分析系列

chapter9 DOM遍历

在jQuery如日中天的年代,DOM操作才是页面更新的几乎唯一途径,如今在各大框架React, VUE, Angular的蚕食下,大家都在降低对DOM的直接操作,转为对数据的操作,由框架去决定如何更新DOM。但DOM的操作仍是一名前端最基础的要求。DOM操作的前提是获取DOM元素,除了常规的getElementById, getElementsByClassName, $(),另一种途径就是根据当前元素,通过目标元素与当前元素的关系如兄弟、孩子、祖先等来寻找目标元素,也就是现在要讲的DOM遍历。

下面10个方法是jQuery暴漏出来的10个对兄弟、祖先、孩子元素获取的方法。可以看的出,10个方法都是通过一个模版函数挂载到jQuery.fn上的,模版函数内公用部分完成过滤、排序、驱虫操作,最后将结果再以jQuery对象的形式返回。

// 10个方法大部分依赖了dir、nth、sibling基础方法去寻找目标元素
jQuery.each({
  // 返回一个元素的父亲
  parent: function( elem ) {
    var parent = elem.parentNode;
    return parent && parent.nodeType !== 11 ? parent : null;
  },
  // 返回一个元素所有父亲
  parents: function( elem ) {
    return jQuery.dir( elem, "parentNode" );
  },
  // 返回一个元素所有父亲,知道碰到until元素为止
  parentsUntil: function( elem, i, until ) {
    return jQuery.dir( elem, "parentNode", until );
  },
  // 返回一个元素的下一个兄弟
  next: function( elem ) {
    return jQuery.nth( elem, 2, "nextSibling" );
  },
  // 返回一个元素的上一个兄弟
  prev: function( elem ) {
    return jQuery.nth( elem, 2, "previousSibling" );
  },
  // 返回一个元素的所有后面兄弟
  nextAll: function( elem ) {
    return jQuery.dir( elem, "nextSibling" );
  },
  // 返回一个元素的所有前面兄弟
  prevAll: function( elem ) {
    return jQuery.dir( elem, "previousSibling" );
  },
  // 返回一个元素的所有后面兄弟或者碰到某个元素为止
  nextUntil: function( elem, i, until ) {
    return jQuery.dir( elem, "nextSibling", until );
  },
  // 返回一个元素的所有前面兄弟或者
  prevUntil: function( elem, i, until ) {
    return jQuery.dir( elem, "previousSibling", until );
  },
  // 元素所有兄弟元素
  siblings: function( elem ) {
    return jQuery.sibling( elem.parentNode.firstChild, elem );
  },
  // 返回一个元素所有子元素
  children: function( elem ) {
    return jQuery.sibling( elem.firstChild );
  },
  // 返回元素的所有子元素包含各种注释、文本节点,如果是iframe元素则返回window对象
  contents: function( elem ) {
    return jQuery.nodeName( elem, "iframe" ) ?
      elem.contentDocument || elem.contentWindow.document :
      jQuery.makeArray( elem.childNodes );
  }
}, function( name, fn ) {
  jQuery.fn[ name ] = function( until, selector ) {
    var ret = jQuery.map( this, fn, until );
    // var runtil = /Until$/,
    // 如果函数名是不是以UNtil结尾的,表示不需要until参数
    // 修正参数
    if ( !runtil.test( name ) ) {
      selector = until;
    }
    // 如果有参数selector,并且参数selector是字符串,则执行过滤操作
    if ( selector && typeof selector === "string" ) {
      ret = jQuery.filter( selector, ret );
    }

    // 当过滤后仍有多于1个dom元素,还需要进行去重操作
    //   guaranteedUnique = {
    //    children: true,
    //    contents: true,
    //    next: true,
    //    prev: true
    //  }; 
    ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;

    //  rmultiselector = /,/
    //   rparentsprev = /^(?:parents|prevUntil|prevAll)/
    // 需要对特殊的操作进行倒序操作
    if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
      ret = ret.reverse();
    }
    // 用找到的dom数组构建新的jQuery对象并返回
    return this.pushStack( ret, name, slice.call( arguments ).join(",") );
  };
});

jQuery.extend({
  // 过滤操作, 调用的事Sizzle接口
  filter: function( expr, elems, not ) {
    if ( not ) {
      expr = ":not(" + expr + ")";
    }
    return elems.length === 1 ?
      jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
      jQuery.find.matches(expr, elems);
  },
  // 上面可以看出dir有三个值 parentNode、previousSibling、nextSibling
  dir: function( elem, dir, until ) {
    var matched = [],
      cur = elem[ dir ];
    // 循环调用elem[dir], 然后一直查找,知道找到window 或者 不存在 或者 与 until一致则停止
    while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
      if ( cur.nodeType === 1 ) {
        matched.push( cur );
      }
      cur = cur[dir];
    }
    return matched;
  },
  // result 有 2
  // dir 有nextSibling、parentNode、previousSibling、nextSibling
  nth: function( cur, result, dir, elem ) {
    result = result || 1;
    var num = 0;
    // 按照一个方向,向下走几次
    // result表示走几次
    // dir表示方向
    // 当result为0时,cur就是当前的元素本身, 那么会执行++num ==> 1,cur=cur[dir]
    // 在此进入循环 这一次 ++num ==> 2,也就会break,就达到了寻找紧邻的兄弟的目的
    for ( ; cur; cur = cur[dir] ) {
      if ( cur.nodeType === 1 && ++num === result ) {
        break;
      }
    }

    return cur;
  },

  // 获取元素n的所有后面兄弟元素,但是不能包含elem元素
  // 当n为一个父元素的第一个子元素,那么就是获得了当前父元素任意一个子元素的所有兄弟元素
  sibling: function( n, elem ) {
    var r = [];

    for ( ; n; n = n.nextSibling ) {
      if ( n.nodeType === 1 && n !== elem ) {
        r.push( n );
      }
    }

    return r;
  }
});

SQL基础知识

基础

概念

数据库(database): 保存有组织的数据的容器(通常是一个文 件或一组文件)。

DBMS(数据库管理系统): 数据库是通过DBMS创建和操纵的容器。

表(table):某种特定类型数据的结构化清单。

模式(schema): 关于数据库和表的布局及特性的信息。

列(column): 表中的一个字段。所有表都是由一个或多个列组成的。

数据类型(datatype): 所容许的数据的类型。每个表列都有相应的数据类型,它限制(或容许)该列中存储的数据。

行(row): 表中的一个记录。

主键(primary key): 一列(或一组列),其值能够唯一区分表中每个行。

  • 任意两行都不具有相同的主键值;
  • 每个行都必须具有一个主键值(主键列不允许NULL值)。
    好习惯
  • 不更新主键列中的值;
  • 不重用主键列的值;
  • 不在主键列中使用可能会更改的值。(例如,如果使用一个名字作为主键以标识某个供应商,当该供应商合并和更改其 名字时,必须更改这个主键。)

操作符(operator):用来联结或改变WHERE子句中的子句的关键 字。也称为逻辑操作符(logical operator)。

通配符(wildcard): 用来匹配值的一部分的特殊字符。

搜索模式(search pattern): 由字面值、通配符或两者组合构成的搜索条件。

SQL: 是结构化查询语言(Structured Query Language)的缩写。SQL是一种专门用来与数据库通信的语言。

DBMS可分为两类:一类为基于共享文件系统的DBMS,另一类为基于客户机—服务器的DBMS。

客户机—服务器应用分为两个不同的部分。

  • 服务器部分是负责所有数据访问和处理的一个软件。这个软件运行在称为数据库服务器的计算机上。与数据文件打交道的只有服务器软件。
  • 客户机是与用户打交道的软件。向服务器部分发送数据添加、删除、更新等请求。

基本命令

sql命令不区分大小写,不过将指令大写,表名、列名小写是一种好的习惯

  • SHOW DATABSES; // 显示当前数数据库列表
  • USE tableName; // 使用(选择)某个数据库
  • SHOW TABLES; // 显示数据库中表列表
  • SHOW COLUMNS FROM tableName; 或 DESCRIBE tableName; // 显示表中列的详细信息
  • SHOW STATUS,用于显示广泛的服务器状态信息;
  • SHOW ERRORS和SHOW WARNINGS,用来显示服务器错误或警告消息。

检索数据

  • SELECT columnName FROM tableName; // 从某张表中搜索1个列
  • SELECT columnName1, columnName2, columnName3, columnName4 FROM tableName; // 从某张表中搜索多个列
  • *: SELECT * FROM tableName; // 从某张表中搜索所有列
  • DISTINCT: SELECT DISTINCT columnName FROM tableName; // 只检索出不同的列, 即 如果有两条记录 值相同,则只返回一条
  • Limit: SELECT columnName FROM tableName Limit n; // 检索结果只返回限制的N条记录
  • Limit: SELECT columnName FROM tableName Limit n1, n2; // 检索结果只返回限制的从n1条的n2条记录, 记录是从0开始计数的
  • Limit OFFSET: SELECT columnName FROM tableName Limit n1 OFFSET n2; // 检索结果只返回限制的从n2条的n1条记录

排序

先搜索 再排序

子句: SQL语句由子句构成,有些子句是必需的,而有的是可选的。一个子句通常由一个关键字和所提供的数据组成。子句的例子有SELECT语句的FROM子句,我们在前一章看到过这个子句。 子句的排列顺序会影响最后的结果。

  • ORDER BY: SELECT columnsName FROM tableName ORDER BY columnsName; // 对搜索结果排序, 可以根据多个列进行排序
  • DESC、ASC: SELECT columnsName FROM tableName ORDER BY columnName1 DESC, columnName2 ASC; // 指定排序方向, 升降序

过滤数据

先搜索 再过滤 最后排序

  • WHERE: SELECT columnsName FROM tableName WHERE column = value
  • 操作符img
  • 空值检测 WHERE column IS NULL
  • AND: SELECT columnsName FROM tableName WHERE column1 = value1 AND column2 = value2
  • OR: SELECT columnsName FROM tableName WHERE column1 = value1 OR column2 = value2
  • IN: SELECT columnsName FROM tableName WHERE column1 IN ( value1, valu2, value3) // IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。 类似于OR
  • NOT: ex:NOT IN, NOT BETWEEN // 有且只有一个功能,那就是否定它之后所跟的任何条件。
  • LIKE: SELECT columnsName FROM tableName WHERE column LIKE %value%
  • REGEXP: SELECT columnsName FROM tableName WHERE column REGEXP 'regexpattern'

通配符

  • %: 任何字符出现 任意次数 column LIKE '%value%' %不能匹配NULL
  • _: 下划线只匹配单个字符而不是多个字符。

正则匹配

  • |: 或
  • [1234]: 匹配其中一个
  • [1-5]: 匹配范围
  • 匹配字符类img
  • 多次匹配img
  • 定位符img

计算字段

  • 拼接Concat: SELECT Concat(string1, column1, string2, column2) FROM tableName
  • 删除空格Trim、RTrim、LTrim: SELECT Trim(Concat(string1, column1, string2, column2)) FROM tableName
  • 使用别名 AS: SELECT Trim(Concat(string1, column1, string2, column2)) AS new_name FROM tableName
  • 算数计算 + - * /: SELECT Trim(Concat(string1, column1, string2, column2)) AS new_name, column1 * column2 AS new_name2 FROM tableName

数据处理函数

每个DBMS对数据处理函数的实现都不尽相同,因此不要过于依赖数据处理函数。
img
img
img

汇总数据

  • AVG() 返回某列的平均值 SELECT AVG(colum) as column_average FROM tableName
  • COUNT() 返回某列的行数 COUNT(*) 会计算表内一共有多少行包含NULL, COUNT(column)则会忽略NULL
  • MAX() 返回某列的最大值
  • MIN() 返回某列的最小值
  • SUM() 返回某列值之和

分组数据

先搜索 再过滤 再分组 最后排序
GROUP BY 有时会报一个错误

ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'mysql.user.User' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

错误原因也比较简单就是因为GROUP BY后面的字段与前面的字段不是完全一致的sql_mode=only_full_group_by,可以通过设置sql_mode来关闭这个选项

  • GROUP BY 对检索后数据进行分组 SELECT columnsName FROM tableName GROUP BY columnsName;
    • GROUP BY子句可以包含任意数目的列
    • 如果分组列中具有NULL值,则NULL将作为一个分组返回。如果列中有多行NULL值,它们将分为一组。
  • HAVING: 支持所有WHERE的搜索 SELECT columns FROM table GROUP BY column HAVING column > num
    • 唯一的差别是 WHERE过滤行,而HAVING过滤分组

图中搜索的含义是: WHERE子句过滤所有prod_price至少为10的 行。然后按vend_id分组数据,最后HAVING子句过滤计数为2或2以上的分组(过滤分组)
img

SELECT子句使用顺序

img
img

子查询

当我们需要先从A表中查出某个数据作为B表的筛选条件时。

SELECT product_name FROM product WHERE product_id = ( SELECT product_id FROM order WHERE order_id = 1000 )

先在订单表中查出订单1000对应的产品ID,再去产品表中查出对应ID的产品名称

联结查询

关系型数据库最核心的设计之一就是各表之间互相通过外键依赖来完成数据存储,那么查询的时候肯定不希望每次都像子查询那样麻烦。

  • 使用WHERE来表达联结条件 SELECT table1.column1, table2.column2, table2.column3 FROM table1, table2 WHERE table1.column1 = table2.column1
  • 明确指出内部联结 SELECT table1.column1, table2.column2, table2.column3 FROM table1 INNER JOIN table2 ON table1.column1 = table2.column1
  • 使用别名 自联结 SELECT t1.column1, t1.column2 FROM table as t1, table as t2 WHERE t1.c1 = t2.c1 AND t2.c2 = 100
    img
  • 外部联结 LEFT/RIGHT OUTER JOIN 外部联结还包括没 有关联行的行: SELECT table1.column1, table2.column2, table2.column3 FROM table1 LEFT OUTER JOIN table2 ON table1.column1 = table2.column1
  • 内部联结与外部联结区别: 内部联结如果�table1中某一行没有在table2中匹配到,那么table1这一行是不会出现在结果中,但是外部联结会保留这一行。 外部联结的LEFT、RIGHT就是用来表明那一张表的每一行都应该被保留

组合查询

UNION 简单讲就是讲多个WHERE联合到一起
SELECT ven_id, ven_name, produ_price FROM products WHERE produ_price <= 10 OR ven_id IN (1001, 1002) ==>

SELECT ven_id, ven_name, produ_price FROM products WHERE produ_price <= 10 UNION SELECT ven_id, ven_name, produ_price FROM products ven_id IN (1001, 1002)

  • UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔。
    - UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)。
  • 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型(例如,不同的数值类型或不同的日期类型)。
  • UNION会自动取消重复的行, 如要保留多次 需使用UNION ALL
  • 在用UNION组合查询时,只能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。

插入数据

INSERT INTO table VALUES(value1, valu2, value3);

  • INSERT语句一般不会产生输出
  • 存储到每个表列中的数据在VALUES子句中给出,对每个列必须提供一个值
  • 如果某个列没有值,应该使用NULL值(假定表允许对该列指定空值)
  • 各个列必须以它们在表定义中出现的 次序填充

INSERT INTO table(column1, column2, column3) VALUES(value1, value2, value3);

  • VALUES列表中的相应值填入列表中的对应项, 即使表的结构改变,此INSERT语句仍然能正确工作
  • 可以省略列。这表示可以只给某些列提供值,给其他列不提供值。
  • 一条INSERT插入多条记录 INSERT INTO table(columns) VALUES(values), VALUES(values), VALUES(values)
  • INSERT INTO table(columns) SELECT columns FROM table2 请确保columns与columns位置一致, mysql不关心列名,在这里更关心列的位置

更新和删除数据

UPDATE table SET column2 = value2, column3 = value3 WHERE column1 = value1

  • UPDATE 由三部分组成 更新的表名、列名和新值、更新行的过滤条件(除非更新整个表)
  • 如果一次性更新多行,当有一行出现错误时,所有行会被恢复为原样 可以使用IGNORE 来强制更新继续并且忽略错误的那一行

DELETE FROM table WHERE column1 = value1

  • 删除行而不是删除某一列 删除某一列请使用UPDATE 列 = NULL
  • 当没有WHERE时代表删除整张表所有行
  • 删除整张表效率较低, 可使用TRUNCATE table 来代替,他是删除表然后重建一张一样的表,但是DELETE FROM tabl逐行删除

表操作

创建表

    CREATE TABLE table (
        column1 type NOT NULL AUTO_INCREMENT,
        column2 type NOT NULL,
        column3 type NULL  DEFAULT 1,
        column4 type NULL,
        PRIMARY KEY(column1)
    ) ENGINE = InnoDB

注意事项

  • 每个表只允许一个AUTO_INCREMENT列,而且它必须被索引(如,通过使它成为主键)。
  • SELECT last_insert_id() 获取最后一次插入的自增的值

引擎类型

  • InnoDB是一个可靠的事务处理引擎,它不支持全文本搜索;
  • MEMORY在功能等同于MyISAM,但由于数据存储在内存(不是磁盘)中,速度很快(特别适合于临时表)
  • MyISAM是一个性能极高的引擎,它支持全文本搜索,但不支持事务处理。

更改表结构

  • ALERT TABLE table ADD column type; 增加字段
  • ALERT TABLE table DROP COLUMN column; 删除字段
  • ALTER TABLE table ADD INDEX indexName (column); 增加索引,为column增加索引,索引名称为indexName
  • ALTER TABLE table ADD CONSTRAINT fkName FOREIGN KEY (column1) REFERENCES table2(column2); 增加外键 在table表中未column设置外键,约束来自于table2的column2字段,外键的名称叫做fkName
  • RENAME TABLE table1 TO table2; 修改表名称, 将表名有table1 修改至 table2

视图

视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态 检索数据的查询。

视图特别像提前写好一段sql语句,将这段语句的返回结果作为一个临时的表(视图)。 之后可以拿这个临时表(视图)当做普通表一样进行sql查询等。

不建议在视图上进行更新操作, 同时视图数量不宜过多

  • 重用SQL语句。

  • 简化复杂的SQL操作。在编写查询后,可以方便地重用它而不必知道它的基本查询细节。

  • 使用表的组成部分而不是整个表。

  • 保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限。

  • 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。

  • CREATE VIEW viewName AS SELECT columns FROM tables WHERE .....; 创建视图

  • SHOW CREATE VIEW viewname; 显示视图简介

  • DROP VIEW viewname; 删除视图

  • CREATE OR REPLACE VIEW viewName AS .......; 更新视图

事务

事务处理(transaction processing)可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行。

  • 事务(transaction)指一组SQL语句;
  • 回退(rollback)指撤销指定SQL语句的过程;
  • 提交(commit)指将未存储的SQL语句结果写入数据库表;
  • 保留点(savepoint)指事务处理中设置的临时占位符(place-holder),你可以对它发布回退(与回退整个事务处理不同)

隐含提交(implicit commit),即提交(写或保存)操作是自动进行的。 但事务过程中不会进行隐含提交,需要我们明确的显式提交。

  • START TRANSACTION; 开始一个事务
  • ROLLBACK; 回滚
  • SAVEPOINT name; 创建一个保留点,并命名。
  • ROLLBACK TO pointname; 回滚到某一个保留点
  • COMMIT; 提交前面所做的操作

用户管理

  • CREATE USER username IDENTIFIED BY 'password'; 创建用户
  • RENAME USER old TO new; 更改用户名
  • DROP USER name; 删除用户
  • SHOW GRANTS FOR name; 显式某个用户的权限
  • GRANT SELECT ON db.table FOR name; 为用户name分配db数据库下table表的select权限
  • REVOKE SELECT ON db.table FOR name; REVOKE是GRANT的反操作
  • SET PASSWORD FOR username = Password('new password'); 更改其他用户的密码
  • SET PASSWORD = Password('new password'); 更改当前用户的密码

权限列表
img
img

跨域问题详解

各种跨域真实详解

跨域可能是面试中高频的一个问题,可能刷几道题就能知道大概怎么回答了,但是你真的都实验过,能全部掌握呢。

直接在服务器端设置 access-control-allow-origin

access-control-allow-origin可以设置成已知的安全请求来源,甚至可以设置为*,这样前端基本可以随便访问了。
img1
img1
由这两张图片即可看得出来。当跨域的请求发出时会先有一个options连接,规范在这里,MDN解释在这里,简单说就是

OPTIONS请求旨在发送一种“探测”请求以确定针对某个目标地址的请求必须具有怎样的约束(比如应该采用怎样的HTTP方法以及自定义的请求报头),然后根据其约束发送真正的请求。比如针对“跨域资源”的预检(Preflight)请求采用的HTTP方法就是OPTIONS。

在这里我们的请求肯定跨域了,有的是垮了域名,有的是跨了端口,反正是跨域了,浏览器帮助我们先发一个探测性请求,如果发现服务器是允许这类请求的,再将真正的请求发送给服务器。在这种方法里,前端最简单,最省事情,不用做任何更改,后台也基本没啥修改。 而且本人不认为Access-Control-Allow-Origin设置会为系统增加什么安全性问题,毕竟数据的安全不是靠跨域防护来保护的,写个脚本爬你数据的时候,Access-Control-Allow-Origin可根本没任何作用。

window.name进行跨域

Window 对象表示浏览器中打开的窗口。
如果文档包含框架(frame 或 iframe 标签),浏览器会为 HTML 文档创建一个 window 对象,并为每个框架创建一个额外的 window 对象。
注释:没有应用于 window 对象的公开标准,不过所有浏览器都支持该对象。

这是一段w3school对window的简单介绍,也就是window更像是我们浏览器的一个标签。 window.name跨域也正是利用了这个特性, 浏览器会为每一个窗口附带一个name属性,这要是这个窗口不关闭,那么name属性可以一直存在。

// 打开一个浏览器标签, 并打开www.baidu.com
// 在控制台输入
window.name = '切换页面不能重置我';
// 然后不要切换标签, 在同一标签打开 www.google.co.uk
alert(window.name)
// 你发现window.name还是存在的
// 仅仅证明了window.name是跟随浏览器窗口的

然后大概的**就是利用iframe和window.name来进行进行跨域数据交流,虽然window.name只能存储字符串,但是大部分我们的数据都可以转换成字符串,毕竟还有JSON这个大杀器。

假设我们有一个网页,地址为www.test.com/a.html,后台会将数据自动渲染到网页内部,同时这个页面会将数据设置到window.name。因为test.com和数据网站是同域的。

我们将www.test.com/a.html设置为主应用页面www.baidu.com的一个iframe里,这样在www.baidu.com的首页尝试读取这个数据发现报错了,告诉我们跨域的iframe不可以互相进行交流。

我们需要做的就是再将iframe的src设置为与主页面同域的地址就可以了,不存在的网址也可以。这样就不存在跨域的问题了。

同时由于iframe就代表了一个浏览器窗口,iframe并没有更新或者销毁,所以window.name就可以成为数据交流的桥梁。

这部分可以简单的封装一下,记得用完之后再将iframe移除掉,以防内存泄漏。

// 直接在百度首页控制台执行这段代码即可
state = 0;
iframe = document.createElement('iframe'),
loadfn = function() {
  if (state === 1) {
    // 第二次触发load时加载https://www.xxx.com/data.html之后
    // 这时候就可以读取window.name了,因为同域了
    var data = iframe.contentWindow.name;    // 读取数据
    alert(data);    
  } else if (state === 0) {
    // iframe第一次load是 加载https://www.xxx.com/data.html完毕时触发
    // 此时是无法读取iframe的 window.name,因为跨域了
    // 在这一次触发后 再将iframe的src设置为同域名的地址, 不存在也无所谓
    state = 1;
    iframe.contentWindow.location = "https://www.baidu.com/proxy.html";    // 设置的代理文件
  }
};
iframe.src = 'https://www.xxx.com/data.html';
if (iframe.attachEvent) {
    iframe.attachEvent('onload', loadfn);
} else {
    iframe.onload = loadfn;
}
document.body.appendChild(iframe);

https://www.xxx.com/data.html的源码

    console.log('设置name')
    window.name = '我是设置的name数据'

document.domain

在浏览器中,同一个页面内有多个iframe,是可以拿到各个对应的window对象,但是如果不是同源的会发现这个window对象简直是个辣鸡,什么都不能做,也不能获取内部的docuemnt

这时候document.doamin的作用就体现出来了,iframe和主页面的document.doamin设置为相同值就可以了。例如在http://www.example.com/a.html加载一个iframehttp://example.com/b.html,跨域了所以不能互相操作。但是把document.doamin设置为example.com之后就是同源的了,各自获取的window对象可以正常使用了。

但是document.domain的属性是有限制的,只能把document.domain设置成自身或更高一级的父域,且主域必须相同。也就是说a.b.c.xxx.com,只能设置为b.c.xxx.comc.xxx.comxxx.com,不能设置为其他值了。

因此这个方法限制性很大, 而且你还是不能在主页面发送ajax去请求跨域的页面,但是你可以通过iframe来发送ajax去请求与iframe同源的页面,然后再去拿数据,毕竟主页面已经完全可以操作iframe了。

jsonp跨域

这是一个神奇的方法。能实现的原理是因为html加载script是不受同源限制的,所以才会有cdn这类服务商帮我们提供各式各样的js包、工具的地址。然后利用这个特性,我们将要请求的ajax接口按照script的形式加载到我们的页面。但是返回的数据是一个直接执行的函数callback(data),这个callback也是页面之前script已经定义好了的函数。data是本次请求服务器真正给的数据。

<script src="http://www.example.com/getweather?date=2017-07-07&callback=process"></script>
function process(data){
  // process data
  console.log(data);
}

这个script真正加载回来的内容是什么呢?

process({temperature: 37, date: '2017-07-07', city: 'bj'})

当这个script被加载完毕后,它会立即执行,然后就相当于执行了我们之前已经定义好的一个函数,参数就是真正的数据。

但jsonp这种跨域方式是通过script标签来实现的,那么就只能限制在get请求了,

window.postMessage

postMessage是一个不错的API,其兼容性也不错,ie8都被部分支持,可以在iframe之间进行数据沟通。

// A页面
let iframe = document.getElementById('frame').contentWindow;
iframe.postMessage('发送消息', '*');
// iframe
window.addEventListener("message", receiveMessage, false);
function receiveMessage(e){
  console.log(e);
}

posyMessage这种方法来实现跨域数据交流还是比较直观的,也符合一贯的思路,有人监听事件,有人触发事件,只是监听和触发处于两个页面了而已。

jQuery chapter1 ----jQuery源码分析系列

chapter1

  • 解析jquery版本为1.7.1 没有解读最新版是因为水平有限,希望先借助当前已有资料进行练习
  • 规定jquery通常表示jquery这个文件或这个包。
  • jQuery表示内部的变量等代码层面的内容。

jquery 整体

jquery整个文件是一个立即执行函数。 这样做的好处呢就是不会污染全局变量,同时全局的其余包或者变量也不会影响jquery的内部工作。
不污染全局且不被外部影响是一个包被广泛使用的基本要求之一。
关于立即执行函数就不在解释。可以查看
github

整个立即执行函数传入两个对象,window和undefined:

  • 1、内部大量用到window和undefined,作为内部局域变量可以减少作用域链的查找,这样加快速度
  • 2、方便代码压缩, a=window b=undefine

整个代码的最后 window.jQuery = window.$ = jQuery 将jQuery对象抛出,并挂载在window上,这样可以全局任意位置随时调用。 日常的调用习惯$(params)可以推算出jQuery应该属于一个可执行的函数。

借助IDE可以轻松找到 window.jQuery = window.$ = jQuery 中jQuery(变量1)的定义位置, 23-958行。那么文件中起于的代码全部都是为这个jQuery变量服务的。恰巧这个jQuery(变量1)变量指向一个立即执行函数。这个函数最终返回的是一个内部变量也叫jQuery(变量2)。
换句话说, jquery文件是一个立即执行函数, 里面定义一个jQuery(变量1)变量,这个变量也指向一个立即执行函数。 这个函数最终返回的是一个指向具体方法的变量(变量2)。 也就是说整个jquery在调用的时候 其实是在调用一个具体的方法。这个方法也就是整个jquery的入口函数。因此下一步是要搞清楚new jQuery.fn.init( selector, context, rootjQuery ) 做了哪些工作。

这里可能有点迷惑的就是定义了太多的jQuery,其实自习看一下作用域,删减掉部分代码 就比较能清晰的看出各个jQuery的级别或者作用域,然后就知道每次return的是哪个jQuery。

// 下面定义的是jQuery(变量2)
var jQuery = function( selector, context ) {
    // The jQuery object is actually just the init constructor 'enhanced'
    return new jQuery.fn.init( selector, context, rootjQuery );
  }
// 下面定义的是jQuery(变量1)
var jQuery = (function() {
  // 下面一行是定义jQuery(变量2)哦。
  var jQuery = function( selector, context ) {
    return new jQuery.fn.init( selector, context, rootjQuery );
  }
  // ....
  // ...
  // ...
  return jQuery  // 因此 这里return的是jQuery(变量2)
})();
window.jQuery = window.$ = jQuery   // 因此 这里window上挂载的是jQuery(变量1)

在jQuery(变量2)的var指令时,同时定义了高达40个左右的变量,尽管里面有注释,但有非常多的正则不太容易理解。因此我们不必现在理解他们,用到了再回头看。或者后期专门一节来分析所有正则。 这些变量中还有很多变量是用于缩写的, 可能这也是一个好习惯吧。

jQuery.fn.init

又发现一个很有意思的地方就是 jQuery.fn = jQuery.prototype = {},简而言之,jQuery.fn 又是一个缩写,它指向的是jQuery这个函数的原型链。 我们上一节讲到关心的是jQuery.fn.init这个函数。那么这个函数其实就是jQuery.prototype原型链上面定义的一个方法而已。为什么定义在原型链上,也就是原型链的好处可以待会再讲。

继续看代码 jQuery.fn = jQuery.prototype下面是 jQuery.fn.init.prototype = jQuery.fn;,这句话代表了什么呢。等价于

jQuery.fn = jQuery.prototype; 
jQuery.fn.init.prototype = jQuery.prototype;

这里就可以解释一下原型链的好处了。 原型链最大的好处也就是实现了类似于继承的概念。当一个属性在对象本身上面找不到的时候,他就会去寻找她的原型链,直到寻找到最顶端。 因为无论jQuery还是$调用的入口方法返回的是一个new jQuery.fn.init实例, 恰巧jquery还实现了链式写法,链式写法最基本的就是每个方法调用后要返回本身this,才能保证下一个方法可以继续执行。在这里就可以知道虽然所有的方法不会挂载在init上,应该属于jQuery,但是通过将两者的原型链共享,那么也可以让init随意调用jQurry的方法了。 注意:jquery链式操作返回的this应该是属于fn.init的。

看一下fn.init的源码, 这个正是我们使用$()时的调用方法。 然后接下来一行行分析这个init函数。
约定:

  • 源码中所带的英文注释全部被删掉以方便查看。
  • selector译为选择器
  • context译为上下文
  • rootjQuery译为根元素
 init: function( selector, context, rootjQuery ) {
      var match, elem, ret, doc;

      // 处理 $(""), $(null), or $(undefined)三种情况, 当选择器为假时实例一个空jQuery留作以后使用
      if ( !selector ) {
        return this;
      }

      // 处理 $(DOMElement) 选择器为原生dom元素时, 将上下文也设为该dom元素,并且将该元素压入数组, 同时可以得知其实this是一个数组
      if ( selector.nodeType ) {
        this.context = this[0] = selector;
        this.length = 1;
        return this;
      }

      // 处理选择器为body字符串并且没有上下文的情况
      // 上下文设为document,并且将body压入this数组
      if ( selector === "body" && !context && document.body ) {
        this.context = document;
        this[0] = document.body;
        this.selector = selector;
        this.length = 1;
        return this;
      }

      // 处理选择器是字符串的情况 
      if ( typeof selector === "string" ) {
        // 需要区分是普通字符串 还是 #id类型
        if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
          // 假设字符串 为<开头>结尾并且长度大于3 就为字符串dom元素
          match = [ null, selector, null ];

        } else {
          // 否则用正则去判断string
          match = quickExpr.exec( selector );
        }
        // A simple way to check for HTML strings or ID strings
        // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
        // quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
        // 这个正则整体就是一个大括号, 大括号内部又分成两个括号
        // 但是大括号的开头时 ?: 也就是说这个()匹配的内容是不会被单独记录的。
        // 正则的两个小()被 | 分割也就是说可以匹配两个中任意一个即可
        // 第二部分: #([\w\-]*)$ 比较好理解 匹配#div这样的形式 当然后面id的名字也可以包含-
        // 第一部分: [^#<]*(<[\w\W]+>)[^>]*$ 
        // [^#<]* 表示开头不是#或<,长度为0或n
        // (<[\w\W]+>) <开始 中间任意字符串长度大于1 >结尾 
        // [^>]*$ 不以>结尾
        // 这样直接就把所有#开头的交给了第二个括号去匹配。因为如果#开头则表示 [^#<]* 匹配到0次(也不能算错),但是也不是<,则匹配失败了。
        // 如果是以<开头的 则仍表示[^#<]* 匹配到0次, 然后正常匹配 <> 并且以贪婪模式保留<>中间的内容作为()的内容输出
        // 如果是以其余字符串形式开头表示[^#<]* 匹配到多次, 但是会被抛弃,只去寻找<>
        // 匹配的结果是一个数组, 长度为3
        // [ selector, htmlString, id]

        // 正则匹配成功, 并且 ( 匹配到了htmlString 或者 没有提供上下文)
        if ( match && (match[1] || !context) ) {

          // 匹配到了htmlString
          if ( match[1] ) {
            // 处理上下文以及正确的document
            context = context instanceof jQuery ? context[0] : context;
            doc = ( context ? context.ownerDocument || context : document );

            // rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/
            // 又是一个正则
            // ^< 以<开头
            // (\w+) 括号包裹,内部至少一个字符
            // \s* 0或多个空白符
            // \/? 0或1个/  反斜杠
            // (?:<\/\1>)? ?: 表示括号内的内容不用被记录输出, 最后一个?表示0或1次 ()内部其实是</加第一个括号内容> 其实就是html的闭合标签
            // <div></div> 通过
            // <div></div1> 不通过 
            // <div>gdfgdf</div1> 不通过
            // <div /></div> 也通过
            // 其实rsingleTag 就是用来检测 htmlString是不是单个简单标签,而且不含innerHTML和任何text
            ret = rsingleTag.exec( selector );

            if ( ret ) {
              // jQuery.isPlainObject是一个检测传入参数是不是 纯对象的方法, 只有当参数为纯javascript且没有Ownproperty才返回true
              // 这里当htmlString是简单标签时又根据上下文分了两种处理方式
              if ( jQuery.isPlainObject( context ) ) {
                selector = [ document.createElement( ret[1] ) ];
                jQuery.fn.attr.call( selector, context, true );

              } else {
                selector = [ doc.createElement( ret[1] ) ];
              }

            } else {
              // htmlString为复杂标签的处理方式
              ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
              selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
            }

            return jQuery.merge( this, selector );

          // else处理 选择器为字符串, 并且为id选择器的情况
          } else {
            elem = document.getElementById( match[2] );

            // Check parentNode to catch when Blackberry 4.6 returns
            // nodes that are no longer in the document #6963
            if ( elem && elem.parentNode ) {
              // Handle the case where IE and Opera return items
              // by name instead of ID
              if ( elem.id !== match[2] ) {
                return rootjQuery.find( selector );
              }

              // Otherwise, we inject the element directly into the jQuery object
              this.length = 1;
              this[0] = elem;
            }

            this.context = document;
            this.selector = selector;
            return this;
          }

        // 处理上下文不存在或者上下文为jquery对象时
        } else if ( !context || context.jquery ) {
          return ( context || rootjQuery ).find( selector );

        // 处理上下文存在的情况
        } else {
          return this.constructor( context ).find( selector );
        }

      // 处理选择器为函数的情况
      } else if ( jQuery.isFunction( selector ) ) {
        return rootjQuery.ready( selector );
      }

      // 如果选择器有selector属性, 则认为它是一个jquery对象,将其selector和context赋值给当前this
      if ( selector.selector !== undefined ) {
        this.selector = selector.selector;
        this.context = selector.context;
      }
      // 应对其余情况, 同时当选择器为jquery对象时也会被执行
      return jQuery.makeArray( selector, this );
    },

至此, 我们调用$()无论传入任何参数, 内部方法已经实现了分流。 分成几种不同情况去处理,有字符串,有domElement,还有jquery,当然还有假值。 当然现在也只能看到分流这一步, 分流后各小项是如何处理的当前还不得而知, 同时jQuery.fn也仅仅分析了init一个属性。 下一节 会继续分析jQuery.fn的其余属性。

buildFragment

在前一节看到 当选择器selector为复杂string的时候,会调用buildFragment方法来产生相对应的dom元素。

 // htmlString为复杂标签的处理方式
ret = jQuery.buildFragment( [ match[1] ], [ doc ] );

这里match[1]为字符串 doc为矫正后的document

args 带转换为dom元素的字符串或html代码
nodes 用于修正创建文档片段DocumentFragment的文档对象
script 存放html代码中script元素
jQuery.buildFragment = function( args, nodes, scripts ) {
	var fragment, cacheable, cacheresults, doc,
	first = args[ 0 ];
  // 继续矫正document 确保后面不出现异常
	if ( nodes && nodes[0] ) {
		doc = nodes[0].ownerDocument || nodes[0];
	}
	if ( !doc.createDocumentFragment ) {
		doc = document;
	}
  // 一系列检测 决定是否可以缓存这段 待转换文本 
  if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&
		first.charAt(0) === "<" && !rnocache.test( first ) &&
		(jQuery.support.checkClone || !rchecked.test( first )) &&
		(jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {

		cacheable = true;
    // 如果符合缓存要求, 尝试从缓存仓库fragments中读取缓存的内容,
    // 可以理解为dom转换比较费时费力 不希望重复做两次同样的事情
    // jQuery.fragments = {};
		cacheresults = jQuery.fragments[ first ];
		if ( cacheresults && cacheresults !== 1 ) {
			fragment = cacheresults;
		}
	}
  // 如果没有缓存 则先创建文档片段 再调用jQuery.clean来完善刚刚创建的文档片段
	if ( !fragment ) {
		fragment = doc.createDocumentFragment();
		jQuery.clean( args, doc, fragment, scripts );
	}

  // 如果符合条件 更改缓存的状态
  // 第一次缓存前fragments[ first ] 肯定为 undefined, 即cacheresults = undefined
  // 第一次缓存完之后 fragments[ first ] 变为 1
  // 第二次cacheresults就是1了, 那么第二次缓存的时候 fragments[ first ]就是真正的文档片段了, 
  // 第三次再去读取时 以后就读到真正的文档片段了
	if ( cacheable ) {
		jQuery.fragments[ first ] = cacheresults ? fragment : 1;
	}

	return { fragment: fragment, cacheable: cacheable };
};

jQuery.clean

在buildFragment方法中被调用, 用于将字符串转为真正的dom元素。 并且在$()的那个分支 最终的选择器就有clean创建而来 selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;, 这个childNodes正是clean方法最后传出来的。

下面的解释可能不是非常完全,保证可以看出clean方法的过程即可。

	clean: function( elems, context, fragment, scripts ) {
		var checkScriptType;

		context = context || document;

		// !context.createElement fails in IE with an error but returns typeof 'object'
		if ( typeof context.createElement === "undefined" ) {
			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
		}

		var ret = [], j;

		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
			if ( typeof elem === "number" ) {
				elem += "";
			}

			if ( !elem ) {
				continue;
			}

			// Convert html string into DOM nodes
			if ( typeof elem === "string" ) {
				// rhtml = /<|&#?\w+;/,
				// 如果不包含标签、字符代码和数字代码 直接调用createTextNode创建文本节点。
				// createTextNode的特点是不回转义字符
				// 但innerHTML可以
				if ( !rhtml.test( elem ) ) {
					elem = context.createTextNode( elem );
				} else {
					// Fix "XHTML"-style tags in all browsers
					// rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
					// 寻找不该自闭合的标签 并矫正
					// < \w /> 并且\w不是area|br|col|embed|hr|img|input|link|meta|param
					// 但也有问题哦  <div/></div>   --> <div></div></div>
					// 只是简单的把不该自闭合的变为自闭和
					elem = elem.replace(rxhtmlTag, "<$1></$2>");

					// Trim whitespace, otherwise indexOf won't work as expected
					// rtagName = /<([\w:]+)/,
					// wrapMap = {
					// 	option: [ 1, "<select multiple='multiple'>", "</select>" ],
					// 	legend: [ 1, "<fieldset>", "</fieldset>" ],
					// 	thead: [ 1, "<table>", "</table>" ],
					// 	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
					// 	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
					// 	col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
					// 	area: [ 1, "<map>", "</map>" ],
					// 	_default: [ 0, "", "" ]
					// },
					// 规范标签的包裹顺序
					var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(),
						wrap = wrapMap[ tag ] || wrapMap._default,
						depth = wrap[0],
						div = context.createElement("div");
					// ie9以下有个神奇的bug,不支持的html5标签 需要先调教一次 他才会认识 
					// 所以有了safeFragment 针对document和createSafeFragment两个方法
					// Append wrapper element to unknown element safe doc fragment
					if ( context === document ) {
						// Use the fragment we've already created for this document
						safeFragment.appendChild( div );
					} else {
						// Use a fragment created with the owner document
						createSafeFragment( context ).appendChild( div );
					}

					// 正式将正确包裹顺序的html代码 使用 innerHTML插入div元素
					div.innerHTML = wrap[1] + elem + wrap[2];
					

					// 利用之前的depth来逐步将div再次指向真正元素的父层
					// 例如option元素就只想select
					while ( depth-- ) {
						div = div.lastChild;
						// console.log(div)
					}

					// Remove IE's autoinserted <tbody> from table fragments
					// 针对ie做自动插入tbody的处理 忽略
					if ( !jQuery.support.tbody ) {

						// String was a <table>, *may* have spurious <tbody>
						var hasBody = rtbody.test(elem),
							tbody = tag === "table" && !hasBody ?
								div.firstChild && div.firstChild.childNodes :

								// String was a bare <thead> or <tfoot>
								wrap[1] === "<table>" && !hasBody ?
									div.childNodes :
									[];

						for ( j = tbody.length - 1; j >= 0 ; --j ) {
							if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
								tbody[ j ].parentNode.removeChild( tbody[ j ] );
							}
						}
					}

					// IE completely kills leading whitespace when innerHTML is used
					// ie678自动剔除前导空白符    忽略
					if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
						div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
					}
					// 这里的elem一直是一个存在且使用的变量 在for循环定义时定义 只是进入循环时 为string, 现在要变为dom数组了
					elem = div.childNodes;
					// console.log(elem)
				}
			}

			// Resets defaultChecked for any radios and checkboxes
			// about to be appended to the DOM in IE 6/7 (#8060)
			//  修正插入时 checked的bug
			// 代码很简单 只是需要知道有这个bug即可
			var len;
			if ( !jQuery.support.appendChecked ) {
				if ( elem[0] && typeof (len = elem.length) === "number" ) {
					for ( j = 0; j < len; j++ ) {
						findInputs( elem[j] );
					}
				} else {
					findInputs( elem );
				}
			}

			if ( elem.nodeType ) {
				ret.push( elem );
			} else {
				ret = jQuery.merge( ret, elem );
			}
		}
		// 如果传入了fragment, 将ret的dom元素都插入到fragment中,并且将script标签全部提取出来
		if ( fragment ) {
			checkScriptType = function( elem ) {
				return !elem.type || rscriptType.test( elem.type );
			};
			for ( i = 0; ret[i]; i++ ) {
				if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
					scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );

				} else {
					if ( ret[i].nodeType === 1 ) {
						var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType );

						ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
					}
					fragment.appendChild( ret[i] );
				}
			}
		}

		return ret;
	},

接下来对于jQuery(变量1)还有一个大的内部方法 就是 extend 和一个extend方法的调用。

jQuery.extend

jQuery.extend = jQuery.fn.extend一样决定了这个方法在原型链上。
extend其实就是扩展的意思,将属性从一个对象拷贝到target对象上面。 这样可以无限扩展jquery对象。 当然也可以用来扩展其它对象。 只是一个思路与想法。 同时jquery绝大多数的方法与外部插件都通过这种方法来实现

jQuery.extend = jQuery.fn.extend = function() {
		// 这里是默认配置
    var options, name, src, copy, copyIsArray, clone,
      target = arguments[0] || {},
      i = 1,
      length = arguments.length,
      deep = false;

    // 如果arguments[0]是一个布尔类型 则arguments[0] 表示deep变量, arguments[1]则顺位表示target
    if ( typeof target === "boolean" ) {
      deep = target;
      target = arguments[1] || {};
      // skip the boolean and the target
      i = 2;
    }

    // Handle case when target is a string or something (possible in deep copy)
		// 处理target不是{}的情况,默认一个空对象给target
    if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
      target = {};
    }

    // extend jQuery itself if only one argument is passed
		// 如果发现arguments只有一个元素, 那么target就是this,
		// 一般而言 this无非jQuery 或者 jQuery.fn 
    if ( length === i ) {
      target = this;
      --i;
    }

		// 真正的主体是一个循环,用于处理target之后的arguments元素
    for ( ; i < length; i++ ) {
      // 不处理arguments[i]为null 或 undefined的情况
			// null == undefined --> true
			// null != undefined --> false
			// null == false --> false
			// null != false --> true
      if ( (options = arguments[ i ]) != null ) {
        // Extend the base object
				// 遍历options对象或数组的key
        for ( name in options ) {
					// src表示target上同名的值
					// copy表示options上的值
          src = target[ name ];
          copy = options[ name ];

					// 如果options上的值 就是target本身,那么再把这个值赋给target没意义 且会报错。
          // Prevent never-ending loop
          if ( target === copy ) {
            continue;
          }

          // Recurse if we're merging plain objects or arrays
          if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
            if ( copyIsArray ) {
              copyIsArray = false;
              clone = src && jQuery.isArray(src) ? src : [];

            } else {
              clone = src && jQuery.isPlainObject(src) ? src : {};
            }

            // Never move original objects, clone them
						// 如果copy是数组或者对象 并且deep为true则递归的去拷贝
            target[ name ] = jQuery.extend( deep, clone, copy );

          // Don't bring in undefined values
          } else if ( copy !== undefined ) {
            // 只要不是深deep拷贝, 那么直接用copy覆盖或设置target同名属性即可。
						target[ name ] = copy;
          }
        }
      }
    }

    // Return the modified object
    return target;
  };

jQuery.prototype 除init外的其余方法和属性

selector、jquery、length、size、toArray、get

// 选择器 无实际意义,并不一定是可执行代码
selector: "",
// 当前jquery版本号
jquery: "1.7.1",
// 当前jQuery对象中元素个数
length: 0,
// 获取当前jQuery对象中元素个数的方法, 应该直接访问length,减少函数调用开销
size: function() {
	return this.length;
},
// 将jQuery对象的元素转为真正的数组
// jquery整个代码中有一点值得注意就是 this对象上面一只有一个length属性在动态的更改。且length值和[0],[1],[2]...息息相关
// 我们自己也可以实验,在对象上设置length值,然后调用Array.prototype.slice.call也可以获得length长度的数组	
toArray: function() {
	// slice = Array.prototype.slice,
	return slice.call( this, 0 );
},

// $('body').get(0)和 $('body')[0]其实是一样的
get: function( num ) {
	return num == null ? this.toArray() : ( num < 0 ? this[ this.length + num ] : this[ num ] );
},

看到这里需要看一下this到底是一个什么东东

找个地方随便打印一下this
会发现this有这么几个东西

// 以$('body')为例
this : {
	0 : body,
	context : document,
	length : 1,
	selector : "body",
}

this这鬼东西竟然是一个对象,但我们平时感觉他好像还有数组的特性。 this是一个对象这并不难理解,常识中this就应该是对象,但jquery中this的巧妙之处在于jquery一直在维护一个length属性,同时我们真正关心的对象元素的key都是0、1、2、3、4、5这样的连贯数字, 这样就让我们可以直接$('body')[0]来获取一个dom元素。 当然return this也实现了链式操作。 jquery设计的确实高明

pushStack

pushStack: function( elems, name, selector ) {
	// 创建一个新的空的jquery对象
	var ret = this.constructor();
	// push = Array.prototype.push
	if ( jQuery.isArray( elems ) ) {
		// Array.prototype.push.apply( ret, elems );
		// 实现了将数组elems的对象添加到对象ret上面
		// ret是jquery的一个this值而已。
		// 与这句话相反的是从this到对象  toArray方法
		push.apply( ret, elems );
	} else {
		// 可以猜到这个merge方法肯定也是处理类似的事情,只是数组比较简单,如果是对象可能需要for遍历等
		jQuery.merge( ret, elems );
	}

	// 需要将当前的jquery this指针保存到新建德ret对象上, 同时把执行上下文赋给新的jquery对象
	ret.prevObject = this;
	ret.context = this.context;
	// 根据调用方式的不同,来更改一下ret的选择器selector记录
	if ( name === "find" ) {
		ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
	} else if ( name ) {
		ret.selector = this.selector + "." + name + "(" + selector + ")";
	}

	// 这一次返回了新的jquery对象,新的this。但是调用者感觉不到,因为jquery所有的方法都基本在prototype上, 无论新建多少个jquery实例都可以共享,只需要保证每次return的是jquery的this就可以保证链式操作
	return ret;
},

end

在pushStack中会将原本存在的元素对象指针暂存到新的jQuery对象的prevObject属性。
end方法就是入栈的相反操作,推出栈顶部。
如果没有prevObject属性,就新建一个全空的全新的jQuery对象。

end: function() {
	return this.prevObject || this.constructor(null);
},

each

each顾名思义也就是类似于遍历的概念,方法接受callback和args(回调函数和参数)

// 发现each调用jQuery.each? 本身调用本身?仔细想一下其实不是的。
// 外层的这个each是jQuery.prototype.each(jQuery.fn.each)
// 内层的事jQuery.each
// 这里其实区别挺大的。外层的each其实是new jQuery.fn.init()实例来的this 当他身上没有挂载一个方法或者属性时他会寻找他的原型链。 也就是说$().each先寻找init方法上面each,失败,继续寻找 init.prototype.each就找到了 因为jQuery.fn.init.prototype = jQuery.fn;
// 内层的each其实是一个静态的方法。内层each的这个jQuery其实就是执行这句话的return new jQuery.fn.init( selector, context, rootjQuery )那个变量。但那个变量没有执行变为函数,也就是说没有this这个概念。 其实可以找到这个each是通过jQuery.extend({})挂载到jQuery身上的。 当然要访问也就是jQuery.each 相当于一个静态方法
// 例子
// let a = function(){ return this }
// a.prototype.each = '123'
// a.each = '456'
// a.each  --> 456
// (new a()).each    --> 123 靠原型链new时传递来实现的
each: function( callback, args ) {
	return jQuery.each( this, callback, args );
},

因此问题变成了 jQuery.each
jQuery.each就是为传入的数组或对象每个元素执行一遍回调函数而已, 思路想一下Array.prototype.map 和 Array.prototype.each即可

each: function( object, callback, args ) {
	var name, i = 0,
		length = object.length,
		// 判断object是不是有length属性 即是不是类数组对象或就是数组。
		// 类数组对象或者数组则为false, 数组等为true
		// 类数组对象 arguments是一个例子
		isObj = length === undefined || jQuery.isFunction( object );
		// 有没有传入参数 分为if else
		// 可以看出内部的循环便利每次出现false 则停止遍历break。 区别于continue 
	if ( args ) {
		// 是不是类数组或数组又分为 if else
		if ( isObj ) {
			// 非类数组和数组 用for in来遍历object
			for ( name in object ) {
				if ( callback.apply( object[ name ], args ) === false ) {
					break;
				}
			}
		} else {
			// 数组和类数组则直接用 下标来遍历
			for ( ; i < length; ) {
				if ( callback.apply( object[ i++ ], args ) === false ) {
					break;
				}
			}
		}

	// A special, fast, case for the most common use of each
	} else {
		if ( isObj ) {
			for ( name in object ) {
				if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
					break;
				}
			}
		} else {
			for ( ; i < length; ) {
				// 这里传入的第一个参数和第三个参数是一样的,因为 i++ 是要等这个运算结束以后才会累加
				// 区别在于 i++ ++i, so easy
				if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
					break;
				}
			}
		}
	}

	return object;
},

ready 比较复杂 暂时不分析

eq、first、last、slice

比较巧的是first、last调用的是eq,eq调用的是slice,因此先看slice,在看eq,最后看first和last

// slice这段调用的是pushStack, 而slice做的就是准备数据而已, 包括elems: slice.apply( this, arguments ), name: "slice", selector:slice.call(arguments).join(",");
// 这段代码依旧利用了Array.prototype.slice 数组原型方法的方式来从类数组对象抽取数据
slice: function() {
	// slice = Array.prototype.slice,
	return this.pushStack( slice.apply( this, arguments ),
		"slice", slice.call(arguments).join(",") );
},
// eq就是从this中抽出指定的唯一下标的一个dom元素(其实不一定是dom元素,但是为了方便分析,毕竟jqeury获取的大部分都是dom元素)
eq: function( i ) {
	// 利用自加可以快速实现 字符串向阿拉伯数字转变
	i = +i;
	// 其实eq设定只能获取一个元素,而且调用的是slice方法,所以需要尽可能的为slice指定两个参数,否则会造成slice或得到一个数组
	return i === -1 ?
		this.slice( i ) :
		this.slice( i, i + 1 );
},

first: function() {
	return this.eq( 0 );
},

last: function() {
	return this.eq( -1 );
},

map

又与each非常相似,内部调用了一个jQuery的静态同名方法map。 但这次还调用了pushStack这样一个入栈的方法。
可能与Array.prototype.map和Array.prototype.each类似,map不改变原值,需要返回新值,each直接在原值上更改,所以map需要新建一个jQuery对象,同时在prevObject保留了上一次的对象

map: function( callback ) {
	return this.pushStack( jQuery.map(this, function( elem, i ) {
		return callback.call( elem, i, elem );
	}));
},

// 这里有趣的一点是each和map里面的判断都变了,一个是判断isObj,一个是Array,而且对于缺省arg也不尽兴判断了。可能两段代码不是出自同一人之手。但基本效果是一致的
// 这里和each区别最大的就是 这里新建了一个ret空数组, 然后将遍历callback返回的数据都压入ret数组内,最后整个map返回的是扁平化之后的ret 组成的新数组。 这里新建的ret是空数组而不是jQuery空对象 可能是考虑到外层是pushStack调用,同时不应该直接修改,应该保存原有的到prevObject。
map: function( elems, callback, arg ) {
	var value, key, ret = [],
		i = 0,
		length = elems.length,
		// jquery objects are treated as arrays
		isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;

	// Go through the array, translating each of the items to their
	if ( isArray ) {
		for ( ; i < length; i++ ) {
			value = callback( elems[ i ], i, arg );

			if ( value != null ) {
				ret[ ret.length ] = value;
			}
		}

	// Go through every key on the object,
	} else {
		for ( key in elems ) {
			value = callback( elems[ key ], key, arg );

			if ( value != null ) {
				ret[ ret.length ] = value;
			}
		}
	}

	// Flatten any nested arrays
	return ret.concat.apply( [], ret );
},

jQuery.extend调用 389-893行。

基于前面定义的jQuery.extend方法来扩展jQuery对象。

noConflict

noConflict: function( deep ) {
	// 这两歌变量是在整个jQuery执行前定义的,用于保存现有的window.jQuery和window.$同名对象。
	// _jQuery = window.jQuery,
	// _$ = window.$,
	// 当调用noConflict时 会把$ 和  jQuery的控制权重新移交给之前使用的库或者插件等。
	if ( window.$ === jQuery ) {
		window.$ = _$;
	}

	if ( deep && window.jQuery === jQuery ) {
		window.jQuery = _jQuery;
	}

	return jQuery;
},

isReady、readyWait、holdReady、ready、bindReady

这5个属性或者方法都是用于处理ready立即执行函数的。 后面再分析吧,先捡软柿子捏。

isFunction、isArray、isWindow、isNumeric、type

可以看出isFunction、isArray都是依赖于type,isWindow、isNumeric来个方法比较纯粹。

// type内部先将undefined 和 null类型变量转为字符串形式 undefined 和 null,其余类型读取class2type, 如果读取不到则默认为object格式
type: function( obj ) {
	return obj == null ?
		String( obj ) :
		class2type[ toString.call(obj) ] || "object";
},
// class2type对象构建方式 利用jQuery.each 来构建
// 构建结果
<!--{
	[object Array] : "array"
	[object Boolean] : "boolean"
	[object Date] : "date"
	[object Function] : "function"
	[object Number] : "number"
	[object Object] : "object"
	[object RegExp] : "regexp"
	[object String] : "string
}-->
jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
	class2type[ "[object " + name + "]" ] = name.toLowerCase();
});
isFunction: function( obj ) {
	return jQuery.type(obj) === "function";
},
isArray: Array.isArray || function( obj ) {
	return jQuery.type(obj) === "array";
},
// 如果是object, 则判断setInterval有没有在对象里, 如果在则就是window了, 利用一个小技巧曲线救国
// 当然如果一个对象也实现了setInterval方法,可能这个检测就失效了。 但谁会没事实现或命名这样一个属性或变量呢
isWindow: function( obj ) {
	return obj && typeof obj === "object" && "setInterval" in obj;
},
// 首先利用parseFloat来转换为浮点数,如果转换失败会返回NaN,证明这不是一个可转换的数字
// isNaN来判断 如果是NaN那么就证明这不是一个数字或可转换为数字
// isFinite来确定不是一个无限大或小的数
// NaN的判断比较奇怪, 如前比较稳定的判断方法是isNaN和Object.is(), 可以看另一片blog https://github.com/AllFE/blog/issues/6
isNumeric: function( obj ) {
	return !isNaN( parseFloat(obj) ) && isFinite( obj );
},

isPlainObject、isEmptyObject

// 用于判断目标是不是一个真正的js纯对象。不可以是函数、dom对象等等
isPlainObject: function( obj ) {
	// 目标首先存在 并且jquery的类型检测是object。 在jquery类型检测中,一切不归于Boolean Number String Function Array Date RegExp 都算是object 因此最基本的还需要排除dom元素, 当认为nodeType就算dom元素,同时不可一世window对象
	但是这样 {nodeType: 'obj'}也会检测失败
	if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
		return false;
	}

	try {
		// 约定一个对象如果直接有constructor属性则必定不是纯Object, 但是constructor应该出现在其原型链上
		if ( obj.constructor &&
			!hasOwn.call(obj, "constructor") &&
			!hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
			return false;
		}
	} catch ( e ) {
		// IE8,9 Will throw exceptions on certain host objects #9897
		// 由于某些ie8、9原因需要用try--catch。 原因未知
		return false;
	}
	// 遍历对象的属性,如果是空对象则key最后为undefined,否则最后判断key应该是obj的自由属性,否则就是不是纯object
	var key;
	for ( key in obj ) {}
	return key === undefined || hasOwn.call( obj, key );
},
// 判断是不是空对象
isEmptyObject: function( obj ) {
	// 遍历 只要有name就报错
	for ( var name in obj ) {
		return false;
	}
	return true;
},

error

// 快速的创建error实例
error: function( msg ) {
	throw new Error( msg );
},

parseJSON、parseXML

parseJSON: function( data ) {
	// 传入数据不是字符串 则返回 null
	if ( typeof data !== "string" || !data ) {
		return null;
	}

	// 需要字符串先进行前后的空格去除
	data = jQuery.trim( data );

	// 尽可能的使用自带的JSON.parse功能去转译
	if ( window.JSON && window.JSON.parse ) {
		return window.JSON.parse( data );
	}

	// 验证json字符串的正确性 确保正确再进行转换 利用http://json.org/json2.js的**逻辑
	// http://json.org/json-zh.html 详细介绍了json的规定。
	// 利用rvalidescape、rvalidtokens、rvalidbraces将一个合法的json字符串转换为仅剩\,:{}\s的字符串
	// rvalidchars = /^[\],:{}\s]*$/,
  // rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "{\\r'qwer': '\\u2044\\b'}".replace(rvalidescape, '@') --> "{@'qwer': '@@'}"   主要解决转译码的问题
  // rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
	// rvalidtokens主要替换 ""包裹的字符串(内部不能有\n或者\r或者"存在) true false null 数字(数字比较麻烦,有小数还有科学技术)
  // rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
	// rvalidbraces主要替换':   ['.replace(reg, '@') 开方括号 或者首位置的方括号
	// 经过三个替换,最后应该只剩下\,:{}\s的字符串 则用rvalidchars进行测试, 测试通过则就算是正确的json, 然后利用( new Function( "return " + data ) )()来实现json转换
	if ( rvalidchars.test( data.replace( rvalidescape, "@" )
		.replace( rvalidtokens, "]" )
		.replace( rvalidbraces, "")) ) {

		return ( new Function( "return " + data ) )();

	}
	// 走到这里就说明不是合法的json对象
	jQuery.error( "Invalid JSON: " + data );
},

parseXML: function( data ) {
	// 利用跨平台w3c标准和ie来进行xml解析
	var xml, tmp;
	try {
		if ( window.DOMParser ) { // Standard
			tmp = new DOMParser();
			xml = tmp.parseFromString( data , "text/xml" );
		} else { // IE
			xml = new ActiveXObject( "Microsoft.XMLDOM" );
			xml.async = "false";
			xml.loadXML( data );
		}
	} catch( e ) {
		xml = undefined;
	}
	// 报错
	if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
		jQuery.error( "Invalid XML: " + data );
	}
	return xml;
},

noop

空函数

noop: function() {},

globalEval

globalEval: function( data ) {
	// rnotwhite = /\S/  保证非空
	if ( data && rnotwhite.test( data ) ) {
		// ie直接使用execScript或者使用匿名函数内部eval
		// 为什么使用匿名函数?
		// eval执行上下文只针对当前调用的环境, 但是匿名函数是直接挂在全局的,因此匿名函数内部的eval也就是全局的了
		// execScript直接就是全局
		( window.execScript || function( data ) {
			window[ "eval" ].call( window, data );
		} )( data );
	}
},

camelCase

实现驼峰转换

// rmsPrefix = /^-ms-/,  如果以-ms-开头的则替换为ms-
// rdashAlpha = /-([a-z]|[0-9])/ig,  将以-开头的都替换为大写的并且去掉-
fcamelCase = function( all, letter ) {
	return ( letter + "" ).toUpperCase();
},
camelCase: function( string ) {
	return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
},

nodeName

判断nodeName

nodeName: function( elem, name ) {
	return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
},

trim

// String.prototype.trim
// 下一行第二个trim是一个已经定义的变量
// 如果trim存在 直接调用浏览器的trim
// 否则用正则匹配掉左右的空白
// trimLeft = /^\s+/,
// trimRight = /\s+$/,

trim: trim ?
	function( text ) {
		return text == null ?
			"" :
			trim.call( text );
	} :
	function( text ) {
		return text == null ?
			"" :
			text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
	},

makeArray

将数据压入一个数组

makeArray: function( array, results ) {
	// 如果没有传入结果数组就新建
	var ret = results || [];
	// 当数据不为undefined、null时才进入,意味着false也进入
	if ( array != null ) {
		// The window, strings (and functions) also have 'length'
		// Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
		var type = jQuery.type( array );
		// 如果是简单类型直接push进数组
		// 否则调用merge进行数组拼接组合
		if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
			push.call( ret, array );
		} else {
			jQuery.merge( ret, array );
		}
	}

	return ret;
},

merge

主要用于拼接两个jQuery对象 或者将一个数组内容赋予给一个jQuery对象
jQuery对象有个特点就是有length属性,并且有从0开始的紧密下标

merge: function( first, second ) {
	var i = first.length,
		j = 0;
	// second是array的情况
	if ( typeof second.length === "number" ) {
		for ( var l = second.length; j < l; j++ ) {
			first[ i++ ] = second[ j ];
		}
	} else {
	// second是jQuery对象的情况
		while ( second[j] !== undefined ) {
			first[ i++ ] = second[ j++ ];
		}
	}
	first.length = i;
	return first;
},

grep

过滤掉数组中满足条件的元素,并将其与不满足条件的返回,并且不修改原数组

grep: function( elems, callback, inv ) {
	// 定义一个ret新数组, 通过!!将inv一定能转为true|false
	var ret = [], retVal;
	inv = !!inv;

	// 同样的用!!来获得callback的执行结果true|false
	// 如果输出结果与inv相同则过滤掉 否则添加进ret
	for ( var i = 0, length = elems.length; i < length; i++ ) {
		retVal = !!callback( elems[ i ], i );
		if ( inv !== retVal ) {
			ret.push( elems[ i ] );
		}
	}

	return ret;
},

proxy

返回一个拥有特定上下文的新函数,有点类似于bind
有两种形式 proxy(func, context)、 proxy(context, name)
第一种比较正常
第二种意味着指定context上name属性对应的函数上下文一定为context

proxy: function( fn, context ) {
	// 如果context为string则是第二种情况,需要修正fn和context
	if ( typeof context === "string" ) {
		var tmp = fn[ context ];
		context = fn;
		fn = tmp;
	}

	// 如果传入的函数有问题,不是可执行的返回undefined
	if ( !jQuery.isFunction( fn ) ) {
		return undefined;
	}

	// 模拟bind
	// 先将现在传入的参数提起出来保存到args中
	// 然后生成返回的更改了执行上下文的函数proxy
	// proxy内部又返回了一个新的函数,当新proxy执行的时候 内部新建的函数运动了apply
	// 1、直接使用apply是不可以的,因为apply是立即执行的,不符合需求
	// 2、新proxy内部既可以利用现在传入的参数,也可以利用真正执行时传入的新参数
	var args = slice.call( arguments, 2 ),
		proxy = function() {
			return fn.apply( context, args.concat( slice.call( arguments ) ) );
		};

	// 更改函数的guid,暂时不知道guid具体作用
	// 并且proxy.guid(|| jQuery.guid++;左边的)个人认为不会取到, 一直是undefined, 因为proxy才刚刚定义。。。。。不知道jquery怎么想的
	proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;

	return proxy;
},

access

为集合elems中的元素设置一个或多个属性 或读第一个元素的属性值

access: function( elems, key, value, exec, fn, pass ) {
	var length = elems.length;

	// 如果key是一个对象 就是多个属性的时候 遍历设置
	if ( typeof key === "object" ) {
		for ( var k in key ) {
			jQuery.access( elems, k, key[k], exec, fn, value );
		}
		return elems;
	}

	// 当value有值时设置属性
	if ( value !== undefined ) {
		// value是函数 并且 exec为true, pass为false时
		exec = !pass && exec && jQuery.isFunction(value);
		// 遍历元素集合 为每一个元素设置key属性值,
		// 传入的第三个值由exec决定,可能是一个函数计算过的值 也可能是直接静态值
		// 如果是函数利用call来将执行上下文绑定到elems[i]上
		for ( var i = 0; i < length; i++ ) {
			fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
		}

		return elems;
	}

	// 执行这里说明是读取第一个元素的key属性值或者返回undefined
	return length ? fn( elems[0], key ) : undefined;
},

now

返回当前时间的时间戳, 一个简写吧

now: function() {
	return ( new Date() ).getTime();
},

uaMatch

获取当前浏览器的内核和版本号

// rwebkit = /(webkit)[ \/]([\w.]+)/,
// ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
// rmsie = /(msie) ([\w.]+)/,
// rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,

uaMatch: function( ua ) {
	ua = ua.toLowerCase();

	var match = rwebkit.exec( ua ) ||
		ropera.exec( ua ) ||
		rmsie.exec( ua ) ||
		ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
		[];

	return { browser: match[1] || "", version: match[2] || "0" };
},

配合一起看
// 假设navigator.userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"

browserMatch = jQuery.uaMatch( userAgent );
if ( browserMatch.browser ) {
	jQuery.browser[ browserMatch.browser ] = true;
	jQuery.browser.version = browserMatch.version;
}

// safari也是webkit内核
if ( jQuery.browser.webkit ) {
	jQuery.browser.safari = true;
}

sub

生成另一个jQuery备份, 但是这个不影响原有jQuery。

sub: function() {
	function jQuerySub( selector, context ) {
		return new jQuerySub.fn.init( selector, context );
	}
	jQuery.extend( true, jQuerySub, this );
	jQuerySub.superclass = this;
	jQuerySub.fn = jQuerySub.prototype = this();
	jQuerySub.fn.constructor = jQuerySub;
	jQuerySub.sub = this.sub;
	jQuerySub.fn.init = function init( selector, context ) {
		if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
			context = jQuerySub( context );
		}

		return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
	};
	jQuerySub.fn.init.prototype = jQuerySub.fn;
	var rootjQuerySub = jQuerySub(document);
	return jQuerySub;
},

未完待续

FE Blog

前端的一切

online

网络好文

JS

Node

CSS

jQuery

underscore

Chrome Extension

react

BFC

BFC

在重新理解块级元素那篇文章中,提到了外边距折叠(也有人称坍塌、合并),在解释折叠原因和解决方案时提到了一个概念叫BFC,因此也单独开一篇来重新理解一下BFC是什么。两篇文章先看哪一篇都可以,也没啥先后顺序和前后基础的关系。

先来一个一个的看概念

Block-level elements are those elements of the source document that are formatted visually as blocks (e.g., paragraphs). The following values of the ‘display’ property make an element block-level: ‘block’, ‘list-item’, and ‘table’.
Block-level boxes are boxes that participate in a block formatting context. Each block-level element generates a principal block-level box that contains descendant boxes and generated content and is also the box involved in any positioning scheme
上面两段内容翻译一下

  • 块级元素是文档中定义的格式化为可视块了的元素。设置display属性为‘block’, ‘list-item’, and ‘table’可以使元素变为块级元素。
  • 块级盒block-level box是参与了块级排版上下文(BFC)的一种盒子。每一个块级元素会产生一个块级盒子,块级盒子它包含子盒子和产生的内容,同时它参与任何排版位置计算。
    从这两段内容我们得出几个结论:
  • 参与排版的是块级盒子block-level box, 而不是block-level element
  • 块级盒子block-level box是由块级元素产生的。

关于block-level box块级盒子,这里就不放示意图了,就是margin、border、padding、context这几个知识点,构成一个block-level box,一搜一大把示意图。

关于BFC是如何布局呢,也先看一段规定

Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with ‘overflow’ other than ‘visible’ (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.
In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block. The vertical distance between two sibling boxes is determined by the ‘margin’ properties. Vertical margins between adjacent block-level boxes in a block formatting context collapse.
In a block formatting context, each box’s left outer edge touches the left edge of the containing block (for right-to-left formatting, right edges touch). This is true even in the presence of floats (although a box’s line boxes may shrink due to the floats), unless the box establishes a new block formatting context (in which case the box itself may become narrower due to the floats).
还是先翻译一下上面那段内容

  • 浮动、绝对定位、块级容器但不是块级元素(inline-block、table-cells、table-captions)、overflow值不为visible的块级元素会为他们的内容建立新的BFC
  • 在一个BFC中,盒子会一个接一个垂直排列,从包含块的最顶端开始。两个相邻盒子的垂直距离由它们的margin决定,在同一个BFC内,可能会出现外边距折叠现象
  • 在一个BFC中,每一个盒子的左外边会碰到包含块的左内容边(从右向左拍的相反),即使在是浮动元素也是如此(即使一个盒子的行盒是因为浮动而收缩了的),除非这个盒子新建了一个块级排版上下文(在某些情况下这个盒子自身会因为floats而变窄)。

通过这段内容我们似乎能看出来,整个页面的排版首先考虑的是BFC之间的排列,至于像外边距折叠这些东西都是在某个BFC内部才会出现的现象,因此BFC之间的排列才是最重要的。

BFC的排列其实也很简单,就是经常说的

普通流中的块元素独占一行,然后从上往下一个接一个的排布
虽然这句话可能描述的不一定是BFC的排列,但基本是正确的。在普通文档流中,每个块级元素产生一个块级盒子,块级盒子要独占一排,从上到下,左边紧贴父元素排列。

那我们再来看一看经常与BFC一起提到的一个叫外边距折叠的现象。

刚才规定里面都讲到了,在同一个BFC内部才会出现外边距折叠,意味着只要我们重新建立BFC就有可能规避外边距折叠的现象。
这两张图片可以比较好的解释一下,当出现外边距折叠的某一方被一个新的BFC包裹了,那么原本折叠的两方就像不能碰面一样,也就不能发生折叠现象了。
image1
image2

但是,注意刚才用词都是有可能规避,再去看前面翻译的那段也有加重的几个字:他们的内容。 举个例子,两个垂直元素发生折叠,你即使为上面元素设置overflow: hidden也是没有效果的,因为是为他们的内容建立新的BFC,但是它们本身还是在原本的BFC中,所以嘛,还是会折叠!

因此BFC和外边距折叠其实是两码事,只是很多情况下,新建一个BFC可以解决外边距折叠这个问题而已,仅此而已。

position、display、float叠加

其实这块内容放在这里多少有些突兀,也有点不太合适,不过恰好就是看到了、恰好就是任性。
image3
这张图片也告诉了我们position、display、float共同设置时的效果。除去各自本身单独设置时产生的效果不谈,它们共同设置的时候,就像有优先级一样,会产生一些属性的遮蔽效果。

设定值 转换后
inline-table table
inline, run-in, table-row-group, table-column, table-column-group, table-header-group, table-footer-group, table-row, table-cell, table-caption, inline-block block
其他 不转换

这部分的是从前人blog中看到,通过最后链接可以查看详细的解释,但基本的概念与平时使用也差不多。

  • display: none 就像开关一样,控制一切
  • 当设置了absolute、fixed之后,元素便像块级元素一样,飞了起来。float被忽略
  • 其余position才会有float的展现余地。
  • 都没有设置的情况下,只能来看display了。。。。

总结

  • 触发BFC的不是特殊元素,BFC只是部分元素的一种属性
  • 从html结构来看,其与BFC一点关系都没有,BFC仅仅是存在于CSS的布局的一种方式。
  • BFC与普通容器也没有根本区别,只是BFC产生了一个独立的区域(矩形),BFC内的元素不会影响外界,外界也不会影响到BFC内部。
  • BFC有一些普通容器没有的特性,比如可以包含浮动元素,因此可以通过触发BFC来解决父级容器高度塌陷的问题。
  • IE低版本可以使用zoom:1来触发hasLayout, 其与BFC基本相似。

references

jQuery chapter5 队列Queue ----jQuery源码分析系列

chapter5 队列Queue

队列是一种最基本的数据结构,队列就是一种特殊的线性表,只允许在队尾插入,队头删除,也就是我们常说的先进先出,后进后出。jQuery也实现了这样一种结构,通过shift()push()对Array进行操作,来模拟队头删除和队尾插入。队列模块最重要的功能之一就是为后面的动画模块提供支持,毕竟很多动画是要按顺序执行,那这时候队列的作用就非常重要了。

整体结构

我们在这里这里先讨论一个基本问题,就是每次每个模块好像都有jQuery.extend 和 jQuery.fn.extend,为什么这样呢。
我们可以这样考虑这个问题,jQuery.extend是扩展到jQuery对象上面的,jQuery.fn.extend是扩展到jQuery原型链上面的。 当我们$()获得的对象实例是继承了原型链,但是$并没有继承原型链啊。也就是说jQuery.extend里面的方法,我们可以直接$.queue使用,但是jQuery.fn.extend必须是$().queue使用,虽然函数名是一样的,但完全不是一个东西啊。因为实例之后虽然原型链的东西继承了,但是原来那一套东西是不会跟着过去的。更详细的解释可以看另一篇文章关于new的认识、附带lazyman和currying的理解。 举个例子

function alone(){};
alone.queue = 'abc';
alone.prototype.queue1 = 'qwer';
let a = new alone();
console.log(alone.queue)   // "abc"
console.log(alone.queue1)   // undefined
console.log(a.queue)   // undefined
console.log(a.queue1)   // "qwer"

这部分代码也没啥幺蛾子, 整体结构也比较容易理解,维护一个盛放队列的容器,并且对队列实现入列和出列的操作即可。

jQuery.extend({
  _mark: function( elem, type ) {},
  _unmark: function( force, elem, type ) {},
  // 函数入列,并返回队列
  queue: function( elem, type, data ) {},
  // 函数出列,并执行
  dequeue: function( elem, type ) {}
});
jQuery.fn.extend({
  // 取出函数队列,或者函数入列
  queue: function( type, data ) {},
  // 函数出列,并执行
  dequeue: function( type ) {},
  // 清空队列
  clearQueue: function( type ) {},
  // 观察函数队列和计数器是否完成,并返回异步队列的只读副本
  promise: function( type, object ) {}
});

源码 jQuery.extend

queue

在这里我们就可以发现,队列模块是依靠数据缓存模块来维护队列的,将队列分散到各个dom元素或者js对象上,用一个单独的内部属性来作为盛放队列的容器。

queue: function( elem, type, data ) {
  var q;
  if ( elem ) {
    // 修正队列名称,并提供默认队列名称
    type = ( type || "fx" ) + "queue";
    q = jQuery._data( elem, type );
    
    // 如果没提供入列函数,就直接退出
    if ( data ) {
      // 如果之前没有队列缓存数据,或者传进来的是一个数组,那就将data变为数组然后进行整体存储或者替换, 否则就只是普通的数组push作为入列
      if ( !q || jQuery.isArray(data) ) {
        q = jQuery._data( elem, type, jQuery.makeArray(data) );
      } else {
        q.push( data );
      }
    }
    return q || [];
  }
},

dequeue

用于从队列中取出队头的函数并执行

dequeue: function( elem, type ) {
  // 修正type名 并且查询当前type的队列,并取出列头第一个方法
  type = type || "fx";
  var queue = jQuery.queue( elem, type ),
    fn = queue.shift(),
    hooks = {};

  // 如果列头是一个inprogress,则再重新取出一个,这是动画正在执行的占位符号
  if ( fn === "inprogress" ) {
    fn = queue.shift();
  }

  if ( fn ) {
    // 如果当前type是fx即默认代表动画的队列则再头部插入一个inprogress表示占位符
    if ( type === "fx" ) {
      queue.unshift( "inprogress" );
    }

    // 新建了一个.run结尾的缓存数据,并把一个空对象存进去,引用类型哦
    jQuery._data( elem, type + ".run", hooks );
    // 执行这个刚刚取出来的函数, 为他增加两个变量,
    // 第一个封装过的next, 在调用next的时候会再执行jQuery.dequeue就可以保证衔接继续一直出列
    // hooks可以存储数据,然后之后的可以读取,相当于是队列前后执行函数进行数据沟通的桥梁
    fn.call( elem, function() {
      jQuery.dequeue( elem, type );
    }, hooks );
  }

  if ( !queue.length ) {
    // 如果队列内没有fn了,则应该清理一下缓存数据
    jQuery.removeData( elem, type + "queue " + type + ".run", true );
    handleQueueMarkDefer( elem, type, "queue" );
  }
}

_mark、_unmark

在jQuery中有很多只在内部调用的方法,其名称都是以_开始的。
_mark 和 _unmark特别像计数器,实际上它们也只是为动画部分服务。

// _mark用于增加计数器个数
_mark: function( elem, type ) {
  if ( elem ) {
    // 修正type
    type = ( type || "fx" ) + "mark";
    // 新建或者为原本的缓存数据+1
    jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
  }
},
// _unmark用于减少计数器个数
_unmark: function( force, elem, type ) {
  // 可以看出来如果接收的是三个参数,那么顺序是正确的
  // 如果接收的参数是两个,那么第一个是元素,第二个是type,force就是false
  if ( force !== true ) {
    type = elem;
    elem = force;
    force = false;
  }
  if ( elem ) {
    // 修正type
    type = type || "fx";
    var key = type + "mark",
    // 如果force是true则直接让count变为0, 否则先读区当前的count再减一
      count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
    // 有count值就设置count,没有的话就做删除数据的操作
    if ( count ) {
      jQuery._data( elem, key, count );
    } else {
      jQuery.removeData( elem, key, true );
      handleQueueMarkDefer( elem, type, "mark" );
    }
  }
},

源码 jQuery.fn.extend

这一部分可能是更常用的,直接获取完元素之后使用,可能更方便一点。

queue

功能肯定与jQuery.extend中的同名方法类似。

queue: function( type, data ) {
  // 修正参数, 如果第一个参数不是string则证明只有一个参数,那么data就是要入列的函数, type则就是默认的fx
  if ( typeof type !== "string" ) {
    data = type;
    type = "fx";
  }
  // 如果修正之后都没有data,那就是一个参数也没有,那就是读取第一个元素的fx的数据
  if ( data === undefined ) {
    return jQuery.queue( this[0], type );
  }
  // 只要有data则就为每一个元素都 入列data方法
  // 同时 如果是默认的fx动画类型,并且第一个元素不是inprogress的占位符,则自动执行一次出列
  return this.each(function() {
    var queue = jQuery.queue( this, type, data );
    if ( type === "fx" && queue[0] !== "inprogress" ) {
      jQuery.dequeue( this, type );
    }
  });
},

dequeue

为每个元素都执行一次出列

dequeue: function( type ) {
  return this.each(function() {
    jQuery.dequeue( this, type );
  });
},

delay

delay: function( time, type ) {
  // time 有可能是动画的几个string类型
  // slow: 600, fast: 200, _default: 400
  // 先修正time和type
  time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
  type = type || "fx";

  // 入列一个延迟函数, 这个函数里面有next和hooks,这个可以去看之前那个dequeu的方法
  // 执行函数传入的第一个参数会是 再次调用一个dequeue以达到next继续调用的效果
  return this.queue( type, function( next, hooks ) {
    var timeout = setTimeout( next, time );
    hooks.stop = function() {
      clearTimeout( timeout );
    };
  });
},

clearQueue

通过把队列设置为[]来起到置空队列的效果

clearQueue: function( type ) {
  return this.queue( type || "fx", [] );
},

promise

这个方法的目的不是特别明确,只能看个代码的大概

promise: function( type, object ) {
  // 如果只有一个元素, 那么应该是object, 但是整个函数内部发现object没有被再次使用到。。。。真神奇
  if ( typeof type !== "string" ) {
    object = type;
    type = undefined;
  }
  // 修正type
  type = type || "fx";
  // 新建一个异步队列
  var defer = jQuery.Deferred(),
    elements = this,
    i = elements.length,
    count = 1,
    deferDataKey = type + "defer",
    queueDataKey = type + "queue",
    markDataKey = type + "mark",
    tmp;
  function resolve() {
    if ( !( --count ) ) {
      defer.resolveWith( elements, [ elements ] );
    }
  }
  // 遍历当前this的所有元素, 为每个元素的type + "defer"缓存数据增加一个resolve方法,
  // 同时这里有个计步器count++
  // 这个resolve函数也比较有意思,一定要count为1的时候才会执行
  while( i-- ) {
    if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
        ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
          jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
        jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
      count++;
      tmp.add( resolve );
    }
  }
  resolve();
  // 返回了这个队列的只读副本
  return defer.promise();
}

进过查证,这里有一个bug,最后一行其实是 return defer.promise(object),这样这个异步只读副本就附加到object对象上

handleQueueMarkDefer

负责检测元素的关联的队列和计数器是否完成,如果完成了就调用一次.promise,这样当前的count就会减一,以来继续判断是不是全部执行完毕。

function handleQueueMarkDefer( elem, type, src ) {
  // 新建局部变量,并且获取defer函数
  var deferDataKey = type + "defer",
    queueDataKey = type + "queue",
    markDataKey = type + "mark",
    defer = jQuery._data( elem, deferDataKey );
    // 根据传入的参数来决定 什么样的条件, 
    // 如果有关联的回调函数、计数器和队列才会进入
  if ( defer &&
    ( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
    ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
    // 队列是空的了,并且计步器也是0了,那么久移除关联的回调函数,并且调用一次上面的promise
    setTimeout( function() {
      if ( !jQuery._data( elem, queueDataKey ) &&
        !jQuery._data( elem, markDataKey ) ) {
        jQuery.removeData( elem, deferDataKey, true );
        defer.fire();
      }
    }, 0 );
  }
}

总结

jQuery队列的内部实现也不复杂,就是规范一个固定出入方式的Array,这个Array还是由数据缓存部分提供基础支持。

原型链与继承

原型链与继承

众所周知JS的继承最主要是通过原型链来实现。最近踩了一个坑,所以再来回顾一下继承相关的概念。

Problem

  function p(){
    this.num = 1;
  }
  p.prototype.age = 2;
  p.prototype.num = 2;
  p.prototype.add = function(){
    this.num ++;
    this.age++;
  };
  let a = new p();
  a.num;
  a.age;
  a.add();
  a.num;
  a.age;

其实这段代码和继承一点关系都没有,只是实例化而已。来看一下后面几行的输出是什么

  a.num; // 1, p的num属性屏蔽了原型链上面的num属性
  a.age;  // 2,  按照原型链一环一环向上查找,终于找到了age属性
  a.add(); // 执行一个函数,这里面的问题就大了
            // 首先执行this.num++ 比较容易理解,a对象本身就有num属性, 好像没啥问题
            // this.age++ 因为a上面(hasOwnProperty)没有age属性,但是原型链上有这个属性,是不是就把原型链上的自增了呢
            // 答案肯定是否定的,this.age属性的读会按照原型链一环环向上查找,
            // 但是this.age属性的写和原型链一点关系也没有,但是呢this.age++ => this.age = this.age + 1;  需要先去读取this.age拿读取后的结果作为增的基础因子,再将结果直接赋值给对象a
  a.num;   // 2,  刚刚自增了一次, 所以是2
  a.age;   // NaN   undefined++ 肯定是NaN了

继承与实例化

  function p(){
    this.num = 1;
  }
  p.prototype.num = 2;
  let a = new p();

上面这段最普通的new代码叫 实例化,尽管这里面也涉及到原型链,但它不属于继承。

继承最基本的要求是要有一个父类、一个子类。

这可能不是一个难点,也不是一个复杂点,但恰好之前对这个概念有点模糊,认为实例化也属于继承的一种实现方式,概念上有点混淆。但现在应该清楚的能区分开,实例化和继承是没有关系滴。

继承的方式

原型链继承

将实例化的父类作为子类原型, 缺点非常明显啦,不太容易实现多继承,构造父类是不够灵活。

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

function Cat(){ 
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

// Test Code
var cat = new Cat();

构造继承

构造继承解决了前面不能实现多类继承的缺点,但是有更大的一个缺点就是不能继承父类原型链。 其实只是用this来执行了一个特殊函数而已。

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

// Test Code
var cat = new Cat();

实例继承

利用new操作符可以返回对象的特点, 在内部实例化一个父类,然后将父类return, 缺点非常大就是子类不能像普通类一样书写 Cat.prototype.key = ....

function Cat(name){
  var instance = new Animal();
  instance.name = name || 'Tom';
  return instance;
}

// Test Code
var cat = new Cat();

拷贝继承

拷贝继承就是最原始的方法,想办法将父类的属性都拷贝到子类上。 但是不能拷贝父类原型链,因此也不太完美

function Cat(name){
  var animal = new Animal();
  for(var p in animal){
    Cat.prototype[p] = animal[p];
  }
  Cat.prototype.name = name || 'Tom';
}

// Test Code
var cat = new Cat();

组合继承

这种方法看起来不错,既能保证了父类的静态属性又能保证父类的原型链,只是需要实例化父类两次,有点浪费

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal();

// Test Code
var cat = new Cat();

寄生组合继承

这个特别像是平时用到的super(), 虽然也实例化了两个类,但是第二次是一个super类,基本空白,只用于原型链传递,不会将静态属性与方法赋值两次, 但是这种方法需要提炼一下,以供日常使用。 比较完美,但比较麻烦。

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
(function(){
  // 创建一个没有实例方法的类
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Cat.prototype = new Super();
})();

// Test Code
var cat = new Cat();

总结

其实日常工作可能对继承使用的并不是特别多,但作为基础概念还是要牢记的, 文章较多的参考了其余的blog,主要在于加深印象。

参考

http://www.cnblogs.com/humin/p/4556820.html

jQuery chapter3 Deferred Object异步队列----jQuery源码分析系列

chapter3 Deferred Object 异步队列

jQuery(1.5以后)的ajax这类异步任务都被重写,将ajax与回调函数解耦,实现高内聚低耦合。

jQuery的异步队列部分也比较简洁,代码主要集中在1040-1400之中, 主要实现jQuery.Callbacks 和 jQuery.Deferred与jQuery.when三个方法。

jQuery.Callbacks又是在1.7引入,返回一个链式工具对象,用于管理所有回调函数。 后两个是通过jQuery.extends添加上去,因此我们从jQuery.Callbacks开始,看jQuery的Deferred Object异步队列都做了什么,又是怎么实现的

jQuery.Callbacks

先来看整个结构
jQuery.Callbacks定义了一堆变量 + 两个方法 add、fire与一个对象self,self内部属性也是一堆方法,最终整个函数以self作为返回值。

jQuery.Callbacks = function( flags ) {
  flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
  var list = [],
    stack = [],
    memory,
    firing,
    firingStart,
    firingLength,
    firingIndex,
    add = function( args ) {},
    fire = function( context, args ){},
    self = {
      add: function() { },
      remove: function() { },
      has: function( fn ) { },
      empty: function() { },
      disable: function() { },
      disabled: function() { },
      lock: function() { },
      locked: function() { },
      fireWith: function( context, args ) { },
      fire: function() { },
      fired: function() { }
    };
  return self;
};

createFlags

var flagsCache = {};
// 将字符串类型的flag转换为对象类型的
// 传入参数应该是 'once memory unique stopOnFalse'这样的格式,空格分割的单词
// 在一开始就设置了 flagsCache[ flags ] = object, 这样在之后操作object的时候,也起到了相同flag字符串的cache效果
// 使用正则匹配空格分割字符串 然后为object添加属性,整个过程没问题,perfect。
function createFlags( flags ) {
  var object = flagsCache[ flags ] = {},
    i, length;
  flags = flags.split( /\s+/ );
  for ( i = 0, length = flags.length; i < length; i++ ) {
    object[ flags[i] ] = true;
  }
  return object;
}

add

这个方法比较简单,可以看出来就是将args数组内部元素一个个添加进list, list也是jQuery.callbacks内的一个数组元素。 如果args内部还有数组则递归解构, 同时,也会根据flag的标签来检测是不是重复性。

add = function( args ) {
  var i,
    length,
    elem,
    type,
    actual;
  for ( i = 0, length = args.length; i < length; i++ ) {
    elem = args[ i ];
    type = jQuery.type( elem );
    if ( type === "array" ) {
      // Inspect recursively
      add( elem );
    } else if ( type === "function" ) {
      // Add if not in unique mode and callback is not in
      if ( !flags.unique || !self.has( elem ) ) {
        list.push( elem );
      }
    }
  }
},

fire

使用指定的上下文和参数来执行list中的回调函数
这里有一个变量特别乱 memory, 它的初始值是undefined, flags.memory表示在建callbacks时是否传入了memory这个flag。

var memory, firing, firingStart, firingLength, firingIndex;
fire = function( context, args ) {
  args = args || [];
  // 如果传入了memory flag则变量memory为 [context, args]
  memory = !flags.memory || [ context, args ];
  firing = true;
  firingIndex = firingStart || 0;
  firingStart = 0;
  firingLength = list.length;
  for ( ; list && firingIndex < firingLength; firingIndex++ ) {
    // 这里利用if内部来实现list的中的每个函数都被执行
    if (list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
      // 这里利用break来实现一旦有返回值是false,并且有stopOnFalse flag, 则后面的不再执行
      memory = true;
      break;
    }
  }
  // fireing就是表示是不是在执行回调函数
  firing = false;
  if ( list ) {
    if ( !flags.once ) {
      // 没有once flag
      if ( stack && stack.length ) {
        // 这一段有点意外,需要结合最后fireWith一起看,如果调用fireWith的时候,恰巧正在执行即firing为true, 则将这次之行指令压入stack, 待当前的任务执行完毕后,再调用stack内的上下文和参数 继续执行。
        memory = stack.shift();
        self.fireWith( memory[ 0 ], memory[ 1 ] );
      }
    } else if ( memory === true ) {
      // 有once 这个flag, 并且memory严格等于true就是刚才上面有stopOnFalse flag并且某一个回调函数返回值为false
      self.disable();
    } else {
      // 有once 这个flag,但刚刚没有回调函数返回false或者没有stopOnFalse flag
      list = [];
    }
  }
},

即是看完这段代码也对memory有点似懂非懂,再加上最后once这个flag。 不过可以看出来,是通过这几个flag来实现代码逻辑的分流操作。
基本可以看出来,只要list回调函数执行过之后,memory就被变成了[ , ]或true

self.add()

这个add大概可以帮助我们理解刚才上面那个fire函数内部复杂的逻辑。

首先调用add()将要添加的方法添加进list,

1)紧接着检测当前回调函数式是不是在执行过程,如果在执行过程,就把firingLength重新计算一下,这样就会把刚刚添加进去的方法给执行了。

2)如果没有在执行,并且memory既不是undefined也不是true,则表示曾经执行过,那么更改firingStart为之前的list.length,这样这一次就会只执行刚添加进去的方法,同时立即调用fire方法,再把刚刚添加进去的方法立即执行, 通过参数可以看出来,刚才memory那个数组就是保存context和参数。
// With memory, if we're not firing then
// we should call right away, unless previous
// firing was halted (stopOnFalse)

add: function() {
  if ( list ) {
    var length = list.length;
    add( arguments );
    if ( firing ) {
      firingLength = list.length;
    } else if ( memory && memory !== true ) {
      firingStart = length;
      fire( memory[ 0 ], memory[ 1 ] );
    }
  }
  return this;
},

self.remove()

我们发现add和remove都有个前提就是list存在,而且不存在的话不会使用默认的[],这表明,如果list不存在了,就代表执行过了,例如有once flag的情况, 表示只执行一次回调函数

remove对应于add表示要移除某些回调函数。

remove: function() {
  if ( list ) {
    var args = arguments,
      argIndex = 0,
      argLength = args.length;
    // 两个for循环来对比寻找要移除的对象在list中的位置。
    for ( ; argIndex < argLength ; argIndex++ ) {
      for ( var i = 0; i < list.length; i++ ) {
        if ( args[ argIndex ] === list[ i ] ) {
          
          if ( firing ) {
            // 如果当前正在执行回调函数list, 如果当前要剔除的元素位置在执行的范围内,将执行总长度减一
            // 如果已经执行过了,需要把当前执行的下标减一,因为list马上就要变化了。
            if ( i <= firingLength ) {
              firingLength--;
              if ( i <= firingIndex ) {
                firingIndex--;
              }
            }
          }
          // 移除完了元素之后,记得要将i减少一位,否则会跳过紧邻的下一个元素, 也正是这个原因,for循环那里是 i< list.length 而不是提前将list.length提取为变量
          list.splice( i--, 1 );
          // 如果有unique这个flag, 那么寻找到一次就不需要内部for循环继续了,可以直接跳过内部这个小循环,直接进入下一个要移除的函数的寻找匹配工作
          if ( flags.unique ) {
            break;
          }
        }
      }
    }
  }
  return this;
},

self.has、empty、disable、disabled、lock、locked

// 检测一个函数是否在list内部, 指针类型检测,easy
has: function( fn ) {
  if ( list ) {
    var i = 0,
      length = list.length;
    for ( ; i < length; i++ ) {
      if ( fn === list[ i ] ) {
        return true;
      }
    }
  }
  return false;
},
// 将list置空 easy
empty: function() {
  list = [];
  return this;
},
// 将当前callbacks设为残疾状态, 通过几个关键指标设为undefined
disable: function() {
  list = stack = memory = undefined;
  return this;
},
// 检测当前callbacks是否为残疾状态
disabled: function() {
  return !list;
},
// 锁定当前callbacks, 通过设置stack为undefined, 同时如果memory为undefined或者严格等于true,顺便执行致残行为
// 如果是memory模式,则对list无影响,同时可以继续添加移除和执行回调函数,只是不能更改上下文和参数
lock: function() {
  stack = undefined;
  if ( !memory || memory === true ) {
    self.disable();
  }
  return this;
},
// 检测当前callbacks是否为锁定状态
locked: function() {
  return !stack;
},

self.fireWith、fire、fired

// 使用给定的上下文来执行list中回调函数
fireWith: function( context, args ) {
  // 调用时会先检测stack,stack默认的是[], 只有self.locak()和self.disable()会置为undefined
  // 也就是说一个callbacks在锁定和残疾状态下是没有办法执行fireWith的
  if ( stack ) {
    if ( firing ) {
      if ( !flags.once ) {
        // 如果正在执行 且不是once模式,则设置stack
        stack.push( [ context, args ] );
      }
    } else if ( !( flags.once && memory ) ) {
      // 不在执行状态,且不是once flag或者没执行过, 则执行list回调函数
      fire( context, args );
    }
  }
  return this;
},
// 使用当前的上下文执行回调函数列表list
fire: function() {
  self.fireWith( this, arguments );
  return this;
},
// 查看当前list是否执行过
fired: function() {
  return !!memory;
}

jQuery.Deferred

借助Callbacks 我们来看一下jQuery.Deferred是如何实现的, Deferred是jquery中很重要的一个延迟解决方案,它和promise**很类似,也是jQuery.ajax的重要基础。因此,deferred就是jQuery的回调函数解决方案

我们都知道promise有一个很重要的特征就是有一个状态,初始值是pending,成功后变为resolved, 失败后变为rejected, 并且这个状态是单向不可逆的,同时只能更改一次,也就是说也么从pending-->resolved, 要么从pending-->rejected。 然后对应的会执行成功,或者失败的回调函数。

带着这个基础再来看deferred的实现。

function( func ) {
  // 首先内部定义了三个回调函数队列,利用callbacks,看得出来三个队列分别是成功(done),失败(fail),运行中(progress),具体名字可能和刚才的resolved、rejected。不一致,但意思可以理解。只是这里多了一个progress,运行中的回调函数队列。
  // 主意这里的flag, once表示只能执行一次 memory表示会记录之前执行的参数
  var doneList = jQuery.Callbacks( "once memory" ),
    failList = jQuery.Callbacks( "once memory" ),
    progressList = jQuery.Callbacks( "memory" ),
    // 定义初始值未pending
    state = "pending",
    // 定义了一个lists对象, 好像不知道具体用意,后面会有答案
    lists = {
      resolve: doneList,
      reject: failList,
      notify: progressList
    },
    // 定义了一个promise, interesting
    promise = {
      // 实现了done、fail、progress三个方法, 分别是向三个回调函数列表添加函数的方法。
      done: doneList.add,
      fail: failList.add,
      progress: progressList.add,
      // 返回当前state的方法
      state: function() {
        return state;
      },
      // isResolved 判断doneList这个Callbacks是不是执行过,执行过也大概意味着整个deferred成功了。应该也是判断deferred状态的方法。
      isResolved: doneList.fired,
      isRejected: failList.fired,

      // then 接受三个参数,但其实内部调用的反而是done、fail、progress,也是向三个回调函数列表添加函数的方法,只是一个简写而已。
      // 回去查看callbacks的代码能看出来 add方法return的是this, 也就是说这里可以支持链式写法
      then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
        deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
        return this;
      },
      // always是同时方法添加给done和fail,意味着不管失败还是成功都要调用这个方法
      always: function() {
        deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
        return this;
      },
      // 这个pipe略复杂
      // 首先 return是一个Deferred对象,并且调用了promise方法,大概意思就是复制一下这个对象, 后面就可以看到。
      // 内部用each来实现对done、fail和progress根据传入的三个参数向新产生的deferred对象添加回调函数,不过这个方法具体真没看懂具体的用意,查了一下1.8就被废掉了,只存在了0.1个小版本,就不费时间了。
      pipe: function( fnDone, fnFail, fnProgress ) {
        return jQuery.Deferred(function( newDefer ) {
          jQuery.each( {
            done: [ fnDone, "resolve" ],
            fail: [ fnFail, "reject" ],
            progress: [ fnProgress, "notify" ]
          }, function( handler, data ) {
            var fn = data[ 0 ],
              action = data[ 1 ],
              returned;
            if ( jQuery.isFunction( fn ) ) {
              deferred[ handler ](function() {
                returned = fn.apply( this, arguments );
                if ( returned && jQuery.isFunction( returned.promise ) ) {
                  returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
                } else {
                  newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
                }
              });
            } else {
              deferred[ handler ]( newDefer[ action ] );
            }
          });
        }).promise();
      },
      // 这里看着有点奇怪, 内部也有一个promise,其实内部这个promise指外层这个大的promise, 也就是说要把这个大的promise赋给传入的对象, 如果没有传入就新建一个, 有就把done、fail...属性赋给指定的obj,让他也有异步这个能力
      promise: function( obj ) {
        if ( obj == null ) {
          obj = promise;
        } else {
          for ( var key in promise ) {
            obj[ key ] = promise[ key ];
          }
        }
        return obj;
      }
    },
    // 这里用promise.promise得到一个半成品的promise,因为可以看出来现在为止,得到的这个deferred只能添加done\fail\progress函数,获取状态,但是不能更改state的能力
    deferred = promise.promise({}),
    key;
  // 这里就给刚刚声明的deferred增加了触发done、fail、progress函数队列的能力,还是利用Callbacks的基础方法
  for ( key in lists ) {
    deferred[ key ] = lists[ key ].fire;
    deferred[ key + "With" ] = lists[ key ].fireWith;
  }

  // 再为deferred添加成功失败必有得回调函数,更改状态的函数
  deferred.done( function() {
    state = "resolved";
  }, failList.disable, progressList.lock ).fail( function() {
    state = "rejected";
  }, doneList.disable, progressList.lock );

  // 如果整个deferred在声明的时候传入了一个函数,那就执行它
  if ( func ) {
    func.call( deferred, deferred );
  }

  // 一切都做好了,就返回这个deferred。
  return deferred;
},

这段代码看起来也不是很复杂,维护了三个回调函数队列和一个state,根据不同的方法去执行相对应的回调函数队列,并维护好state。一切的基础都在Callbacks。 可能有点不太明白的就是为什么要先声明一个promise再赋给deferred, 感觉直接声明deferred好像也没什么不可以。

when

when这段代码更容易理解。 when就是接受多个deferred对象,当所有都resolved之后,认为整个when是成功的,执行成功回调函数,否则,任何一个deferred参数fail了,则整个when是fail的。

var sliceDeferred = [].slice;
when: function( firstParam ) {
  var args = sliceDeferred.call( arguments, 0 ),
    i = 0,
    length = args.length,
    pValues = new Array( length ),
    count = length,
    pCount = length,
    // 声明了deferred作为when的主deferred对象,如果只有一个参数则不新建
    deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
      firstParam :
      jQuery.Deferred(),
    promise = deferred.promise();
  function resolveFunc( i ) {
    return function( value ) {
      args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
      if ( !( --count ) ) {
        deferred.resolveWith( deferred, args );
      }
    };
  }
  function progressFunc( i ) {
    return function( value ) {
      pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
      deferred.notifyWith( promise, pValues );
    };
  }
  if ( length > 1 ) {
    // 遍历所有的deferred参数,为每个参数添加一个成功的回调函数,和失败的回调函数,还有progress回调函数, 同时检测正确的deferred有结果,用于修正count
    // 就是让每一个deferred添加成功回调函数,然后这个函数在一直修正count,直到0则执行主deferred的回调函数。
    for ( ; i < length; i++ ) {
      if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
        args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
      } else {
        --count;
      }
    }
    // 如果一直修正count,最终是!0了,那就证明所有的参数都不是正确的deferred,那么立即执resolved了
    if ( !count ) {
      deferred.resolveWith( deferred, args );
    }
    // 如果length不大于1,同时主deferred也不是传入参数, 也证明没有正确的deferred,那么直接执行resolved好了
  } else if ( deferred !== firstParam ) {
    deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
  }
  return promise;
}

Summary

deferred部分在jQuery中起到了函数异步管理的作用,但内部并没有用到任何异步的内容,它的基础是jQuery.Callbacks对象。

jQuery.Callbacks对象:它在内部维护一个管理函数的list,同时有一个很重要的firing状态用来表示当前list内的函数是不是在执行中,对list的任何操作都需要考虑firing状态,用于保证list内的函数执行过程中即不略过也不被重复执行。

deferred也是一个很有意思的内容,deferred内部维护三个jQuery.Callbacks对象的list,对应done、fail、progress三个状态。并且deferred的三个状态是模拟promise的状态,逻辑处理也是按照promise的逻辑来处理,状态只能更改一次。

when是一个多deferred管理的方法,接受多个deferred作为参数,但实现一个主deferred对象同时when.count变量作为整体状态的记录,并且为每个deferred添加一个done和fail方法,done用于一直维护when.count, 然后fail用于终止并调用主deferred的fail。

这三个对象的设计可谓精妙。让使用者在主观上完成了异步、回调函数的轻松管理,再加上其链式写法让整个回调的管理更加简单。 但研究它的源码,也不过区区一两百行,但实现的东西却是极为丰富的。

Chrome插件开发初探

Chrome插件开发初探

凡是可以用 JavaScript 来写的应用,最终都会用 JavaScript 来写。

——Atwood定律(Jeff Atwood在2007年提出)

如今chrome基本是所有程序员的标配,每个人或多或少的都会使用到chrome的插件吧。 比如我常用的就有前5个。
image

  • stash 用于关闭并暂存当前所有tab的, 很实用, 方便在不同项目之间切换(毕竟API搬运工,每个项目要搬不同的API)

  • React、Redux 前端开发不解释

  • Open In IINA 又是一个很好用的视频播放插件,当页面有flash时可以点击这个 调用本地的播放器 而不让flash造成mac发热蒸鸡蛋的现象

  • 掘金 掘金的一个首页插件,每次打开新tab时就会自动加载掘金网站的热门帖子, 掘金PM想法不错

  • 还有最后一个 Time recorder(时间记录器),虽然没有发布在chrome商店,但是你可以自己去下载啊 -_-。

先上两张图片,由于家里电脑插件也是刚安装,要本还不是特别多。
image
image

想法

每天�电脑切换次数、打开时间最长的就是chrome > iterm > vscode, 因此就想看看自己每天都浏览什么网站。 历史记录虽然有但是不直观啊, 作为懒人怎么可能去翻历史记录,万一翻到不该看得怎么办。

于是有了Chrome插件之旅

�搜了搜之后发现360做了一件良心之事,把chrome extension开发文档翻译了!!! 我的天,尽管网页丑的要死,但还是不错的。但还是看着怪怪的,于是乖乖回去看原生的了。 �Chrome extension develop docs。 chrome团队文档写的很好,还配有示例。

Chrome extension编写

chrome extension就是按照特定要求编写的网站,依旧有HTML、CSS、JS + Chrome APIs组成, Chrome继续利用V8引擎来运行我们编写的�插件。 举个例子就是 chrome启动之后就在后台运行了我们编写的Web程序。 当点击icon时把我们需要的页面展示给用户就好了。

在插件中最重要的文件(或者称为入口文件, 本人取得名字 -_-) 就是manifest.json, 他就像我们的package.json一样, 一个配置文件,格式要求严格, 内部字段都是预先定义的。 所有字段与预设值都可以查到

来看一下time recorder的json文件来作为示例

{
  "manifest_version": 2,
  // 对整个插件最基本的描述
  "name": "Time recorder",
  "description": "This extension allows the user to record counts and time of each page you opened .",
  "version": "1.0",
  // 这就是我们浏览器右上角的那个小图标啦
  // 用户点击时就打开popup.html
  // 鼠标悬停显示 See Detail!
  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html",
    "default_title": "See Detail!"
  },

  // 我们的程序是否需要后台运行呢? 必然是的
  // 是否需要 persistent 持久运行呢? 是的
  // 后台运行的脚本有吗? 有啊background.js
  "background": {
    "persistent": true,
    "scripts": ["background.js"]
  },
  // 这个主要是图标描述文件
  "icons": { 
    "16": "icon.png",
    "48": "icon.png",
   "128": "icon.png" 
  },

  // 插件需要的权限
  // 比如 需要存储、需要tab的信息、 需要后台运行等等等
  "permissions": [
    "activeTab",
    "storage",
    "tabs",
    "background"
  ]
}

现在我们就大概明白了chrome extension整体运行的逻辑了。 也能大概想明白time recorder插件的执行逻辑, chrome启动就执行插件,每次打开一个新网址就记录,关闭的时候记录时间,同时记录每个网址打开的次数。 但是记录具体的网址不太现实,也没啥作用,大部分网址可能只打开一次就算了,因此实际记录的是域名。

还特地编写了从url-->host的处理逻辑,可能有时候还是不太严谨,但目前够用了。

function getHost(url) {
  if(!url){ return null; }
  var host = url;
  var regex = /.*\:\/\/([^\/]*).*/;
  var match = url.match(regex);
  if(typeof match !== "undefined" && null !== match) {
    host = match[1];
  }
  return host;
}

如何知道有新网址被打开呢, 主要是依赖于Chrome APIs, 这里面就有tab状态更新的事件,我们只需要在background.js脚本中监听这个事件,就能得知新tab打开,tab网址更换,tab被关闭等事件,与前端JS如出一辙,只是API变了一下而已。

经过重重处理,最后将数据存储在 Chrome.storage, 一个与localstorage非常类似的接口,但它是异步的。

在用户点击�浏览器右上角插件icon时,弹出popup.html文件,其实也是被作为一个新的tab打开的。 就像普通的绘图一样,把我们之前记录的数据绘制出来即可。

Chrome APIS坑

chrome提供的大部分API都是异步的,向Chrome.storage这样的存储都是异步的,因此需要注意程序书写时代码的位置

不足

当前插件记录的时网页打开时间,�但是我有个坏习惯,tab打开之后在tab不是特别多的时候经常不及时关闭,而且晚上下班浏览器还一直开着,那这些时间其实不应该被算进去。我们关心的应该是focused的网页,所以当前大逻辑还有待更改。

总结

其实我们能做事情还很多,有更多的领域等待去探索。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.