Giter Site home page Giter Site logo

blog's People

Contributors

hitobear avatar

blog's Issues

知道更好的CSS小技巧

背景图片设置时可以通过指定颜色来为网速慢图片未下载完时的页面添加效果

background:red url(./xxx) no-repeate center center

关键在于指定color属性值,这样在网速过慢时,用户不会看到长时间的一片空白,而是会用指定的颜色值代替

可以拿到指定网站的图标(访问一个网址时,浏览器标题栏显示的图标)

通过根目录/favicon.ico就可以访问到该图标,如在浏览器输入http://www.baidu.com

inherit值的应用

inherit可以应用于任何CSS属性中,而且它总是继承的父元素的计算值(line-height是小数点时,继承的是小数点值),对伪类元素来说,继承的是生成该伪类元素的宿主元素的该属性值

JS总结

1. 垃圾回收

如果一个对象没有被引用,它就是垃圾,将被回收

2. 内存泄露

内存泄露指的是由于浏览器的一些bug,使得该被标记为垃圾的对象没有被标记为垃圾,浏览器会占用越来越多的内存,除非关闭浏览器。
问题代码如下:

var fn=function(){};
document.body.onclick=fn;
fn=null;

执行这段代码后,最开始复制给fn的function依然被document.body.onclick所引用,因此不会被当作垃圾回收,但是当关闭页面(或从页面中删除引用该函数的这个元素)后,因为document对象不继续存在了,因此documentbody之间的引用关系断掉了,即body此时处于无引用状态,没办法找到body,同样没有办法访问到body.onclick了,在chrome等浏览器中,此时function函数会被当作垃圾,等待下一次被回收,而在IE6中,由于IE6的特殊垃圾回收机制,它不会被垃圾回收,导致可能会堆积很多垃圾,这个问题可以用以下办法解决,即添加监听事件:

window.onunload=function(){
document.body.onclick=null;//除删除元素外,还要手动解除函数所被关联的引用,如果绑定了多个事件,均需解除。。
}

3. 浅拷贝和深拷贝

经过了var a=b这样的赋值后,b的变化不影响a,就是深拷贝,除此之外则是浅拷贝。
基本类型的赋值都是深拷贝,所以我们通常谈论是否深拷贝时不考虑基本类型,只考虑对象

ReactReconciler源码学习

挂载方法mountComponent

/**
   * Initializes the component, renders markup, and registers event listeners.
   *
   * @param {ReactComponent} internalInstance
   * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
   * @param {?object} the containing host component instance
   * @param {?object} info about the host container
   * @return {?string} Rendered markup to be inserted into the DOM.
   * @final
   * @internal
   */
  mountComponent: function(
    internalInstance,
    transaction,
    hostParent,
    hostContainerInfo,
    context,
    parentDebugID, // 0 in production and for roots
  ) {
    if (__DEV__) {
      if (internalInstance._debugID !== 0) {
        ReactInstrumentation.debugTool.onBeforeMountComponent(
          internalInstance._debugID,
          internalInstance._currentElement,
          parentDebugID,
        );
      }
    }
    var markup = internalInstance.mountComponent(
      transaction,
      hostParent,
      hostContainerInfo,
      context,
      parentDebugID,
    );
    if (
      internalInstance._currentElement &&
      internalInstance._currentElement.ref != null
    ) {
      transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
    }
    if (__DEV__) {
      if (internalInstance._debugID !== 0) {
        ReactInstrumentation.debugTool.onMountComponent(
          internalInstance._debugID,
        );
      }
    }
    return markup;
  },

这里的渲染调用了实例本身的挂载方法,

      transaction,
      hostParent,
      hostContainerInfo,
      context,
      parentDebugID,
    );

这里实例拥有mountComponnet方法,此实例是在instantiateReactComponent()调用之后创建的ReactCompositeComponent的实例

块级元素具有包裹性后其宽度和其后代块级元素宽度的关系

普通块级元素的宽度是受到父元素宽度影响的,满足等式子元素margin-left+子元素border-left+子元素padding-left+子元素width+子元素padding-right+子元素border-right+子元素paddig-right=父元素width,
当子元素的边框值,内外边距都等于0时,由公式可以推导出,子元素的宽度即等于父元素的宽度。
即普通的块级元素的宽度是具有扩张性的,即以父元素为基础(父元素的宽度是固定的),会自动填充父元素的内容,但是当块级元素具有包裹性后,它的宽度会由自身内容决定,完全不同以往,那么块级元素如何才会具有包裹性呢?

块级元素如何具有包裹性

要让块级元素具有包裹,可以划分为三种方式

  • 通过为元素属性display设置为inline-block将元素设置为行内块元素
  • 将元素设置为浮动元素
  • 将元素设置为绝对定位(absolute或者fixed)

包裹性块级元素的宽度

固名思义,包裹性块级元素,既然包裹,那么它的宽度会由它的内容的宽度来决定,道理很简单,但真的是这样简单吗,举例说明就知道了~

包裹型元素和其子元素宽度的互相影响

为了更明显的观察规律,本例中为所有元素都添加了不同颜色的边框。
首先来看一个普通div元素的宽度,和这个元素行内块化后的效果对比图:

相关代码如下:


.outer{
      border:2px solid red;
    }
    div{
      margin-bottom:5px;
      padding-top:2px;
      padding-bottom:2px;
    }
    .inlineblock{
      display:inline-block;
      
    }
.first span{
      border: 1px solid green;
    }
<div class="outer">我是一个普通块级元素</div>

<div class=" outer inlineblock first"><span>我是一个inline-block元素</span></div>

可见,为外层div设置为inline-block后,当其子元素为非块级元素时,元素的宽度由子元素的宽度决定

当包裹型元素的子元素同时有块级元素和非块级元素时

想到这个问题时,总会一不小心发现一个悖论,包裹型元素本身的宽度由其子元素的宽度决定,那么当它的子元素是一个普通块级元素时,也不例外吗?这样的话,父元素由子元素决定,但因为这个子元素是一个普通块级元素,普通块级元素的宽度不是由父元素决定吗?这样无线循环,到底要怎样呢(懵懵懵),额,既然这样,直接看例子还是,看能不能找得到答案

首先来做第一个实验,相关示例代码如下

.outer{
      border:2px solid red;
    }
    div{
      margin-bottom:5px;
      padding-top:2px;
      padding-bottom:2px;
    }
    .inlineblock{
      display:inline-block;
      
    }
.seconed span{
      border: 1px solid green;
    }
.seconed div{
    border: 1px solid black;
}
  <div class=" outer inlineblock seconed">
    <span>我是一个inline-block元素</span>
    <div>我是一个div元素,内容比较长</div>
  </div>

图示效果如下:

由图中可以看出,外层inline-block元素的边框是紧紧包裹着内层div元素的,而div元素的边框恰好包裹了自身的内容,从这里是不是可以推出?当一个div具有包裹性后,其后代块级元素的宽度也具有了包裹性,由自身的内容决定,同样的,最外层的div由内层的块级元素的宽度决定了?由这个效果看确实是符合的。

然后,下面一次实验中我仅仅是把元素的文字内容换了,结果效果却大不相同,文字内容变换部分如下

  <div class=" outer inlineblock seconed">
    <span>我是一个inline-block元素,内容比较长</span>
    <div>我是一个div元素,内容比较短</div>
  </div>

此时效果如下:

乍一看好像没什么不同,外层div的红色边框依然紧紧包裹着内层div的黑色边框,可仔细一看发现了异常,说好的内层div继承包裹性呢?这里明明它的黑色边框没有包裹住自身的文字内容,反倒看上去相似填充了外层div的宽度,这不是和上一例相矛盾吗?话说回来,这次内层div填充了外层div的宽度,那外层div宽度是哪儿来的?不要忘了,它还有另一个span子元素,这时候的外层div的边框不是恰好包裹了span子元素的边框吗?这样好像说的通了,可是外层元素到底是根据div子元素的宽度而定还是根据span子元素的内容而定呢?div子元素的边框到底是包裹自身内容还是应该填充父元素的内容呢?其实也简单,不妨分几步看一下

当一个块级元素具有包裹性时,它本身的宽度和它的子元素的宽度计算法则可以按照以下步骤:
1.首先找出该元素的所有非块级子元素,并分别按行计算出一行中的非块级子元素的和宽度和,取宽度和最大的一个,计为maxInineWidth,如上面两个例子中,子元素中只有一行涉及到了非块级元素,且该行只有一个span元素,因此span元素的宽度即是这里所求的maxInlineWidth值(注意:极端情况不含有非块级子元素,maxInlineWidth值为0)
2. 然后计算下各个普通文档流块级元素的宽度(脱离文档流的块级元素无需计算在内),将块级子元素看做是纯包裹性元素,即它的宽度由其自身内容而定,找出最宽的div,将这个宽度值记为maxDivWidth
3.比较maxInlineWidth和maxDivWidth的值,取最大的一个,记为maxWidth,则外层的div对的宽度即为maxWidth
4.现在外层div的宽度已经确定了,所有子块级元素的宽度该扩充外层div了,即在第二步中计算出的所有宽度小于maxWidth的块级元素,都自动扩充内容区域填满外层div的宽度(而子元素中不属于块级元素的元素无需扩张了,因为行内块元素本身就是实实在在的包裹性元素,不受父元素宽度影响,当然,父元素宽度过小时,也会影响它,即它的宽度不会超过父元素,如文字内容在触碰到父元素的边框时会自动换行)

以上的计算方式其实是在外层包裹性元素的宽度不会受其它元素限制,可以随子元素内容扩充的情况下才有效的,即若外层包裹性元素的宽度受限的话,计算出的maxWidth有可能大于外层包裹性元素的受限宽度,此时,该外层包裹性元素的宽度依然取得是它的受限宽度(即因为受限,宽度最大不能超过XXX,或者因为受限,宽度必须为XXX),受限的情况可能有好几种,比如以下两种情况:

  • 该包裹性元素的父元素的宽度是一定的,那么该元素的宽度肯定不会超过父元素的宽度值,此时跳过第2,3,步,直接取受限宽度和maxInlineWidth值的最大值作为外层包裹性元素的宽度即可,其它步骤一样;

  • 显示为该包裹性元素指定了width值得话,那么该包裹性元素的宽度值也确定了,此时可以直接跳过1,2,3,即maxWidth的值直接为它的指定值,子块级元素受限于该外层div元素的同时也会依据它来扩充(即子元素的宽度不会超过该宽度值,但当子块级元素按包裹性计算为比该宽度值小时,又会自动扩充到该宽度值)。

由第3步可以看到,外层div的宽度是不会受到子元素中的不处于文档流的元素(如绝对定位或浮动)的任何影响的,但子元素中的不处于文档流的块级元素自身的宽度还是和普通块级元素一样,是会受到外层包裹元素宽度的制约的(不会超过这个值),不同的一点是,它不会自动扩充为何外层包裹性元素同宽(当自身包裹内容原本小于父元素宽度值时,因为脱离文档流了,变为纯包裹性元素(浮动元素和绝对定位元素都是)不再自动扩充。。)。自己试验验证一下即可,也可以看这篇文章的一个例子了解一下。

综上:当外层块级元素具有包裹性后(可能由于浮动,绝对定位,或变为行内块元素),它和它的子块级元素的宽度是互相影响的,并不是绝对的一个根据另一个而定,只不过表现的结果是,不管哪种情况(父级元素宽度等于子级元素宽度,或子级元素宽度扩充为父级元素宽度),表现结果均为,父级元素宽度和子级块元素的宽度是一样的(当子级块元素非处于文档流中的元素中时则例外,此时因为变为纯包裹性元素,和行内块元素一样,有可能小于父级元素宽度

用animation-delay实现一个loading效果

要实现的loading效果是这样的点击这里查看,这里对如何制作这样一个loading效果做一下总结

效果分析

  1. 打开Console的Animations选项卡,以10%的速率放慢动画观察效果
  2. 发现动画是由内外两个圆圈组成,而且内外圆圈效果几乎一样,只是有不同的动画延迟
    3.圆圈由小到大,逐渐淡出

效果实现

外层wrapper

首先画一个wrapper,让动画的两个圆圈在这个wrapper中绝对居中

 #wrapper{
      width:100px;
      height:100px;
      border:1px solid red;
      position:relative;
}
<div id="wrapper">
</div>

第一个圆圈

画出第一个圆圈,让其居中于外层wrapper,且自带动画效果,由小到大,逐渐淡出,动画效果呈线性,且无限循环

.circle{
      width:10px;
      height:10px;
      background:black;
      border-radius:50%;

     //绝对定位居中于父元素
      position:absolute;
      left:0;
      right:0;
      top:0;
      bottom:0;
      margin:auto;

      //圆圈的动画效果
      animation: s 1.5s linear infinite;
    }

//动画定义
@keyframes s{
      0%{width:0px; height:0px; opacity:1;}
      100%{width:100px; height:100px; opacity:0;}
    }
<div id="wrapper">
  <div class="cirle"><circle>
</div>

第二个圆圈

将第二个圆圈设为和第一个一样的效果,只是加上合适时间的动画延迟

#wrapper:nth-child(2){
      animation-delay:0.75s;
    }
<div id="wrapper">
  <div class="cirle"><circle>
  <div class="cirle"><circle>
</div>

目前为止,loading动画已经基本实现,接下来,做一些些优化

代码优化

用伪元素代替div

效果中,wrapper元素中的两个圆圈只是用来设置了元素样式,没有充当其他元素的容器,所以这里我们用before,after伪元素来替代两个circle类元素,注意:伪类元素要生效必须设置content属性

用loading类代替wrapper

用loading代替class,使元素更具有语义化

最终代码如下:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>loading animation</title>
  <style>
    .loading{
      width:100px;
      height:100px;
      position:relative;
    }
    .loading::before,.loading::after{
      content:'';
      width:10px;
      height:10px;
      position:absolute;
      background:black;
      border-radius:50%;
      left:0;
      right:0;
      top:0;
      bottom:0;
      margin:auto;
      animation: s 1.5s linear infinite;
    }
    .loading::after{
      animation-delay:0.75s;
    }
    @keyframes s{
      0%{width:0px; height:0px; opacity:1;}
      100%{width:100px; height:100px; opacity:0;}
    }
  </style>
</head>
<body>
<div class="loading">
  </div>
</body>
</html>

现在只要引入如下代码,就能引入loading元素了,具体效果可以看这里- [用JSbin打开]loading animation](https://jsbin.com/nulasok/edit?html,output)

<div class="loading"></div>

ReactDefaultInjection源码学习

  • 源码地址: src/renderers/dom/shared/ReactDefaultInjection.js
    第一次见 ReactDefaultInjection,还是因为ReacDom模块中在最开始调用了ReactDefaultInjection.inject();看名字,ReactDefaultInjection这个类应该是专门用于注入一些东西的,那么到底注入的什么呢?进入模块内部发现,整个模块只有一个inject()方法,还是先来看下代码吧~

源码

'use strict';

var ARIADOMPropertyConfig = require('ARIADOMPropertyConfig');
var BeforeInputEventPlugin = require('BeforeInputEventPlugin');
var ChangeEventPlugin = require('ChangeEventPlugin');
var DefaultEventPluginOrder = require('DefaultEventPluginOrder');
var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
var HTMLDOMPropertyConfig = require('HTMLDOMPropertyConfig');
var ReactComponentBrowserEnvironment = require('ReactComponentBrowserEnvironment');
var ReactDOMComponent = require('ReactDOMComponent');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactDOMEmptyComponent = require('ReactDOMEmptyComponent');
var ReactDOMTreeTraversal = require('ReactDOMTreeTraversal');
var ReactDOMTextComponent = require('ReactDOMTextComponent');
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
var ReactEventListener = require('ReactEventListener');
var ReactInjection = require('ReactInjection');
var ReactReconcileTransaction = require('ReactReconcileTransaction');
var SVGDOMPropertyConfig = require('SVGDOMPropertyConfig');
var SelectEventPlugin = require('SelectEventPlugin');
var SimpleEventPlugin = require('SimpleEventPlugin');

var alreadyInjected = false;

function inject() {
  if (alreadyInjected) {
    // TODO: This is currently true because these injections are shared between
    // the client and the server package. They should be built independently
    // and not share any injection state. Then this problem will be solved.
    return;
  }
  alreadyInjected = true;

  ReactInjection.EventEmitter.injectReactEventListener(ReactEventListener);

  /**
   * Inject modules for resolving DOM hierarchy and plugin ordering.
   */
  ReactInjection.EventPluginHub.injectEventPluginOrder(DefaultEventPluginOrder);
  ReactInjection.EventPluginUtils.injectComponentTree(ReactDOMComponentTree);
  ReactInjection.EventPluginUtils.injectTreeTraversal(ReactDOMTreeTraversal);

  /**
   * Some important event plugins included by default (without having to require
   * them).
   */
  ReactInjection.EventPluginHub.injectEventPluginsByName({
    SimpleEventPlugin: SimpleEventPlugin,
    EnterLeaveEventPlugin: EnterLeaveEventPlugin,
    ChangeEventPlugin: ChangeEventPlugin,
    SelectEventPlugin: SelectEventPlugin,
    BeforeInputEventPlugin: BeforeInputEventPlugin,
  });

  ReactInjection.HostComponent.injectGenericComponentClass(ReactDOMComponent);

  ReactInjection.HostComponent.injectTextComponentClass(ReactDOMTextComponent);

  ReactInjection.DOMProperty.injectDOMPropertyConfig(ARIADOMPropertyConfig);
  ReactInjection.DOMProperty.injectDOMPropertyConfig(HTMLDOMPropertyConfig);
  ReactInjection.DOMProperty.injectDOMPropertyConfig(SVGDOMPropertyConfig);

  ReactInjection.EmptyComponent.injectEmptyComponentFactory(function(
    instantiate,
  ) {
    return new ReactDOMEmptyComponent(instantiate);
  });

  ReactInjection.Updates.injectReconcileTransaction(ReactReconcileTransaction);
  ReactInjection.Updates.injectBatchingStrategy(ReactDefaultBatchingStrategy);

  ReactInjection.Component.injectEnvironment(ReactComponentBrowserEnvironment);
}

module.exports = {
  inject: inject,
};

变量alreadyInjected

这个变量是作为一个注入标记使用的,确保相关...只注入一次

inject方法

inject方法负责向ReactInjection注入一系列模块。。。在ReactInjection中定义了一系列需要注入的模块

被调用处:在ReactMount.js模块的初始处调用了.

ReactInjection

var ReactInjection = {
  Component: ReactComponentEnvironment.injection,
  DOMProperty: DOMProperty.injection,
  EmptyComponent: ReactEmptyComponent.injection,
  EventPluginHub: EventPluginHub.injection,
  EventPluginUtils: EventPluginUtils.injection,
  EventEmitter: ReactBrowserEventEmitter.injection,
  HostComponent: ReactHostComponent.injection,
  Updates: ReactUpdates.injection,
};

module.exports = ReactInjection;

ReactInjection对象中的键值是一个对象,需要注入相关..的,并都提供一个injection属性,将这个injection属性赋值给ReactInjection的key值,这个injection属性也是一个对象,提供注入其所需模块属性的方法。如ReactUpdates关于injection方法的定义:

var ReactUpdatesInjection = {
  injectReconcileTransaction: function(ReconcileTransaction) {
    invariant(
      ReconcileTransaction,
      'ReactUpdates: must provide a reconcile transaction class',
    );
    ReactUpdates.ReactReconcileTransaction = ReconcileTransaction;
  },

  injectBatchingStrategy: function(_batchingStrategy) {
    invariant(
      _batchingStrategy,
      'ReactUpdates: must provide a batching strategy',
    );
    invariant(
      typeof _batchingStrategy.batchedUpdates === 'function',
      'ReactUpdates: must provide a batchedUpdates() function',
    );
    invariant(
      typeof _batchingStrategy.isBatchingUpdates === 'boolean',
      'ReactUpdates: must provide an isBatchingUpdates boolean attribute',
    );
    batchingStrategy = _batchingStrategy;
  },
};
var ReactUpdates = {
   .......,
  injection: ReactUpdatesInjection,
  ......,
};

看到这里就明白调用 ReactInjection.Updates.injectBatchingStrategy(ReactDefaultBatchingStrategy)就相当于调用的 ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy),

面试题

  1. 关于引用
    代码如下
var a={n:1};
var b=a;
a.x=a={n:2};

alert(a.x);//--->undefined;
alert(b.x);//--->[object Object]

关键:a.x=a={n:2}先看左侧,即a.x取的时候a的值是原来的值,再到右边=a={n:2},这时候把{n:2}赋值给a,a变了,但左侧a不会再变。。
2. 1.toString()为什么报错Uncaught SyntaxError: Invalid or unexpected token,而1..toString()不报错?

因为JS中.符号有两个作用:

  • 作为小数点和后面的内容一起构成一个数字
  • 作为取属性操作符

本题中1.toString()之所以会报错,是因为JS解析时把点看作了小数点,此时期待的小数点后是数字,所以toString()会报错;
1..toString()中第一个点被作为小数点,因此第二个点被当做熟悉操作符,经过把1转换为内置Number类型,可以取到其中的toString方法

ReactUpdates源码学习

  • 源码地址: src/renderers/shared/stack/reconciler/ReactUpdates.js
    之所以记录一下ReactUpdates的学习心得,是因为在从ReactDom.render方法一步步深入时,进入到了
    ReactUpdates.batchedUpdates方法,发现原来一切实际的渲染,更新相关的处理都离不开ReactUpdates,ReactUpdates有多个重要的处理方法,这里还是先从ReactUpdates.batchedUpdates说起

ReactUpdates.batchedUpdates

instantiateReactComponent()中没有执行的组件渲染和生命周期方法,在这里会被执行,也就是说,和字面意思不同,ReactUpdates.batchedUpdates方法不仅负责组件的更新,在组件首次渲染时,也会由该方法处理渲染过程(或者说,首次渲染也是更新的一种?由空更新到有~)

  • 被调用函数:ReactMount._renderNewRootComponent
function batchedUpdates(callback, a, b, c, d, e) {
  ensureInjected();
  return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}

看到这里,调用了batchingStrategy的batchedUpdates方法,当然作用域中找batchingStrategy的定义了,果然看到了这个变量的定义,然而结果是这样的

15  var batchingStrategy = null;

什么?batchingStrategynull,那调用它的方法岂不是会报错?然后我找遍整个ReactUpdates模块,都不见为batchingStrategy重新赋值过,不要着急,既然本模块这里没有赋值,那肯定在其它地方赋值过了~
,这时候回到ReactDOM.js中,可以看到在最开头有以下一行代码:

26  ReactDefaultInjection.inject();

那么它到底做了什么呢?不出所料,它确实会为batchingStrategy赋值,将ReactDefaultBatchingStrategy赋给了该变量,深入了解可以看这里ReactDefaultInjection源码学习~

常见的布局

在写页面时,常见的布局有很多,如左右自适应,左固定右自适应,左右固定中间自适应等等,这里作一下总结。

左右自适应

左右自适应布局常用语页面的导航栏,左右侧各占一部分,且宽度不固定。

利用浮动实现左右自适应

常见的左右自适应的方式应该是左侧左浮动,右侧右浮动了,关键步骤如下:

首先,为导航栏的左右元素分别设置左右浮动,代码如下:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>利用浮动左右自适应</title>
  <style>
    nav{
      border:1px solid black;
     
    }
     div.left{
        float:left;
        border:1px solid red;
      }
     div.right{
        float:right;
        border:1px solid red;
      }
  </style>
</head>
<body>
<nav>
  <div class="left">导航栏左侧内容</div>
  <div class="right">导航栏右侧内容</div>
</nav>
</body>
</html>

此时效果如下图1所示:

图1
  可以看到,此时现在导航栏的两个元素确实左右侧布局的,但`nav`元素的黑色边框的高度是0,很简单,这是因为给它的子元素设置为浮动之后,它本身还没有被任何元素撑开,所以高度当然显示为0了,但这样无疑会影响它之后的其它元素的布局,所以我们还需要进行下一步清楚浮动。

清楚浮动

  清楚浮动,也是老生常谈了,最经典的还是通过设置clear:both来清楚浮动,这里也用这种方法试一下(当然要做到兼容还需要其它属性设置,这里不多解释,自行google~)
  这里通过为nav标签添加.clearfixclass属性来清楚浮动,.clearfix的属性设置的相关代码如下:

 .clearfix::after{
      content:'';
      display:block;
      clear:both;
 }

  再来看此时的效果,如下图2:

图2
  可以看到,现在在实现了左右自适应布局的同时,不会影响后续元素的布局了。但正如上所说,为了不影响后续元素的布局,我们不得不设置清除浮动,而清除浮动也设计到不同浏览器的兼容,有没有什么办法来替代这种方式呢?当然是有的,比如说~~

利用flex实现左右自适应

flex能精妙简洁的实现很多种布局方式,当然也包括左右自适应了,这一小节,就来看一下如何用flex实现左右自适应,步骤如下:

为父元素nav设置displayflex

为父元素nav设置justify-contentspace-between

  这时候再看结果,发现效果和图2一模一样,很好的完成了两个子元素的左右自适应功能。所以可以看到,这里仅仅通过为父元素nav设置两个flex相关的属性,即display:flex;justify-content:space-bettween就能替代之前的利用浮动来完成效果了,具体可以[打开JSbin来查看效果(http://jsbin.com/zomotew/1/edit?html,output)

  到这里可以总结一下这种flex方式实现的左右自适应的好处:

  • 无需为子元素再设置浮动或display:inline-block设置display:flex以后 ,它的子元素天生就有了包裹性了~
  • 更无需清楚浮动了(本来就没有设置浮动)
  • 只需设置display:flex;justify-content:space-bettween这两个属性,无需多说啦,代码简洁就是王道~~

开发常用命令

linux命令

目录文件增删改相关

1.创建目录 mkdir
全称: make directory
2. 删除 rm
全称: remove
3. 移动 mv
全称: move
4. 复制: cp
全称: copy
5.改变目录: cd
全称: change directory
6.新建文件: touch(触摸就会创建了:))
7.添加内容到文件 echo "abcd" > a.txt

Tips

  • rm -rf 其中f代表强制删除,如果没有f的话,控制台总是询问是否确认删除,用了f可以免去确认;而r代表recurse 即循环遍历删除,用于删除目录,如果删除一个文件夹的时候,不加-r参数,则控制台会提示rm: cannot remove 'xxx': Is a directory
  • 复制文件或文件夹时都会用cp命令,如cp a.txt b.txt,但复制文件夹时,用cp命令如 cp a b,控制台会报错:cp: -r not specified; omitting directory 'a',提示因为没有指定-r,所以忽略了目录a,即创建失败,所以和删除文件夹一样,在复制文件夹时也需要-r参数,表示递归
  • mv 表面意思是移动文件,但其实相当于重命名,如 mv a.txt b.txt 即把a.txt移动到了b.txt,相当于重命名(不同于rm 和 cp,mv一个目录不需要-r参数)
  • 创建目录时有时会有mkdir -p a/b/c命令,那么-p是什么呢?在当前目录中执行mkdir a/b/c时,如果当前目录下没有a/b这一个路径文件夹,则控制台会报错: cannot create directory ‘a/b/c’: No such file or directory,而执行mkdir -p a/b/c会发现 a, b, c文件夹均成功创建了,即-p参数的作用在于如果一个目录的父目录不存在,就创建它(parent?)
  • 创建文件或文件夹时 需要为名称添加引号吗?如mkdir XXX,答案是一般可加可不加,但在某些情况下必须加引号,即名字当中有某些特殊字符时(如空格),如需要建一个名为dir first的文件夹,因为名字当中含有空格,所以需要用引号,执行命令 mkdir "dir first",这里不加引号的话,会创建出两个文件夹(dir和first,因为被空格隔开了,系统不知道你要创建的是一个)
  • 目前为止可以看到常见的两种创建文件的方式,touch和echo+重定向(>),这两种方式都可以创建新文件,但是不同的是,echo方式会在创建新文件的时候为文件添加内容,如echo “aaa” > a.txt,其中,当某个文件存在时,再执行该echo命令,会报错,提示该文件存在(windows不会报该错);如果要在已有文件上追加内容,用 >> 即可,如 echo "add" >> a.txt;若要整个覆盖,则用>!符号强制重定向(强制覆盖文件),如echo "new" >! a.txt; touch方式会创建一个空的文件,无法指定内容,而touch一个已存在的文件,则该文件的更新时间会更新,内容不变

查看相关

  1. whoami 查看我是谁,当前用户
  2. 查看当前目录的绝对地址: pwd(print name of current/wording directory)
  3. ls 列出 查看当前文件夹下的文件和文件夹
    全称:list
    还可以执行ls xxx命令查看制定文件/文件夹xxx下的信息
    ls -a 查看所有文件(all) 加上-a参数后列表前两个分别为.和..,即当前目录和上层目录,还会显示一些其它以.开始的其他文件,即不加-a参数的话,所有以点开头的文件信息不会显示,加上-a则会显示.Linux中所有以.英文点号开头的文件或目录都是隐藏文件或隐藏文件夹
    ls -l 列出文件的详细信息,包括创建者,创建时间,文件的读写权限等
  4. tree 查看目录结构(树)
  5. cat 查看文件

下载相关

curl命令可以用来下载文件,如curl -L https://baidu.com > a.html,其中-L表示location,表示支持server的重定向,具体可以通过这里详细了解

curl命令会下载一个文件,而如果想下载一个网页的所有资源可以用wget命令,如wget -p -H -e robots=off https://baidu.com

空间大小相关

  1. df -kh 磁盘占用空间 d-disk,f-file system,k-KB 以KB的方式,h-humanreadable
  2. du -sh 当前目录大小 d-disk,u-usage,s-summary,h-humanreadable
  3. du -h 查看各目录的大小,去掉-s参数,无需总结

ReactCompositeComponent源码学习

  • 源码地址: src/renderers/shared/stack/reconciler/ReactReconciler.js
  • 作用:用户自定义组件的基础包装类,包含一系列组件挂载,卸载的方法。
  • 挂载入口:batchedUpdates->batchedMountComponentIntoNode->mountComponentIntoNode->ReactReconciler.mountComponent-> internalInstance.mountComponent

mountComponent方法

mountComponent: function(
    transaction,
    hostParent,
    hostContainerInfo,
    context,
  ) {
    this._context = context;
    this._mountOrder = nextMountID++;//模块全局属性nextMountID,赋值给实例的私有属性。
    this._hostParent = hostParent;
    this._hostContainerInfo = hostContainerInfo;

    var publicProps = this._currentElement.props;//这里的_currentElement指的TopLevelWrapper
类型的元素,它的props中的child属性对象的是真正的元素,详见[React源码学习-新建一个组件](https://github.com/hitobear/blog/issues/9)
    var publicContext = this._processContext(context);

    var Component = this._currentElement.type;

    var updateQueue = transaction.getUpdateQueue();

    // Initialize the public class
    var doConstruct = shouldConstruct(Component);
    var updateQueue = transaction.getUpdateQueue();//作为参数传给实例构造使用,什么用?
    var inst = this._constructComponent(
      doConstruct,
      publicProps,
      publicContext,
      updateQueue,
    );
    var renderedElement;
 // Support functional components
    if (!doConstruct && (inst == null || inst.render == null)) {
      renderedElement = inst;
    ......
   }
   this._instance = inst;//将真正的组件实例赋值给私有变量_instance
    // Store a reference from the instance back to the internal representation
    ReactInstanceMap.set(inst, this);将组件实例对象和这个包裹对象(ReactCompositeComponent类型的对象)作为key,value存入map
    var initialState = inst.state;
    if (initialState === undefined) {//什么时候不等于undefiend,构造函数中的setState会已经生效了??
      inst.state = initialState = null;
    }
   var markup;
if (inst.unstable_handleError) {
      markup = this.performInitialMountWithErrorHandling(
        renderedElement,
        hostParent,
        hostContainerInfo,
        transaction,
        context,
      );
    } else {
      markup = this.performInitialMount(//执行componentWillMount方法,递归挂载子节点元素
        renderedElement,
        hostParent,
        hostContainerInfo,
        transaction,
        context,
      );
    }
...
if (inst.componentDidMount) {
      if (__DEV__) {
      ......
        });
      } else {
        transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);//执行
//componentDidMount的方法,这里transaction.getReactMountReady是什么?
//enqueue又有什么用?应该是所有子孙的componentDidMount都会在所有组件挂载完后
//依入栈顺序来执行?mountReady就是准备完成挂载?
      }
    }

this._constructComponent->this._constructComponentWithoutOwner

this.this._constructComponent调用的实际是this._constructComponentWithoutOwner,在这个方法中,终于真正的调用了自定义组件的构造函数生成了组件类的一个实例对象,关键代码如下:

 var Component = this._currentElement.type;(//this._currentElement.type就是自定义的组件函数)
if (doConstruct) {
   return new Component(publicProps, publicContext, updateQueue);
} else{
   return Component(publicProps, publicContext, updateQueue);
}

this.performInitialMount

核心逻辑代码如下

performInitialMount: function(
    renderedElement,
    hostParent,
    hostContainerInfo,
    transaction,
    context,
  ) {
    var inst = this._instance;
   if (inst.componentWillMount) {
......
inst.componentWillMount();
......
// When mounting, calls to `setState` by `componentWillMount` will set
// `this._pendingStateQueue` without triggering a re-render. componentWillMount中的setState不会
//触发组件重绘,而是设置_pendingStateQuque变量,设置了之后什么时候生效,render方法的时候就会生效了把?
 if (this._pendingStateQueue) {
        inst.state = this._processPendingState(inst.props, inst.context);
 }
      }
// If not a stateless component, we now rende如果是一个无状态函数组件,renderedElement已经
//有值了,回退查代码
    if (renderedElement === undefined) {
      renderedElement = this._renderValidatedComponent();//执行组件的render()方法,生成的是一个元素对象,和开始ReactDom.render(element,ddd)中的第一个元素是同类
    }
var nodeType = ReactNodeTypes.getType(renderedElement);
    this._renderedNodeType = nodeType;
    var child = this._instantiateReactComponent(
      renderedElement,
      nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
    );
//这里this_instantiateReactComponent方法和ReactMount中的instantiateReactComponent方法是同一个,是ReactCompositeComponent的原型被赋值过,详见[新建一个组件-instantiateReactComponent()方法](https://github.com/hitobear/blog/issues/9)
    this._renderedComponent = child;
    //调用instantiate方法后,把下一级元素对象转化成了ReactCompositeComponentWrapper类的对象,和当前对像是同类,进而递归,调用 ReactReconciler.mountComponent(当初第一个渲染的元素的mountComponent方法就是通过这里一步步入栈过来的),其中代代子孙共享的是同一个transatcion变量(这也是为什么transaction要从外层传了?因为从ReactReconciler.mountComponent开始调,所以为了共享一个transaction,必须要在这之前就得到transaction)
    var markup = ReactReconciler.mountComponent(
      child,
      transaction,
      hostParent,
      hostContainerInfo,
      this._processChildContext(context),
      debugID,
    );
return markup;

看上去递归没有尽头,实际上我们只分析了自定义组件的渲染过程,知道string类型的元素(底层dom)为止,递归应该会结束。

this._processPendingState

源码

_processPendingState: function(props, context) {
    var inst = this._instance;
    var queue = this._pendingStateQueue;//待合并的状态
    var replace = this._pendingReplaceState;//替换状态标志,真表示激活,对原状态进行替换,
//假表示未激活,进行状态合并
    this._pendingReplaceState = false;//默认情况下,没有激活替换状态
    this._pendingStateQueue = null;

    if (!queue) {//队列里没有值,说明没有待合并的,则直接返回原状态即可
      return inst.state;
    }

    if (replace && queue.length === 1) {
      return queue[0];//替换标记激活,且队列里只有一个值的话,无需合并,直接返回新值即可
    }

    var nextState = Object.assign({}, replace ? queue[0] : inst.state);//剩下的情况,根据replace标记而定
//初始值,要么是原状态值`inst.state`,要么是queue[0],
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      Object.assign(
        nextState,
        typeof partial === 'function'
          ? partial.call(inst, nextState, props, context)
          : partial,
      );
    }

    return nextState;
  },

疑问

  • pendingStateQueue一开始是null,什么时候有值得,setState方法?setState方法怎么发挥作用的?
  • transaction.getUpdateQueue用来干嘛的?它的返回结果还传给了组件的构造函数作为第三个参数,又什么用?

原来我该在这时候用绝对定位,浮动,dispaly:inlineblock

  绝对定位,浮动和display:ilinline-block都是界面布局中常用的设置方式,一直以为这几个属性很简单,在我眼里,想要绝对定位于一个元素时,才会用绝对定位(顾名思义??),想要让多个块级元素水平排列时用浮动或display:inline-block(曾经一度觉得这两种方式有些重复,感觉不到它们的区别。。所以自己常常用浮动,display:inline-block用的很少),最近在看别人代码的时候,照着实现思路重新学习了一下这几个属性,所以这里对它们各自的特性,和彼此之间的异同点,以及何种情况该搬谁来当救兵做了一下小总结~

  开始呢,还是想敲下重点,就是不管是为一个块级元素设置了display:absolute;,float:..,还是display:inline-block也好,这个块级元素的在文档流中的行为都会发生一个变化—就是,它会自动的具有包裹性了,宽度不再自适应父元素的宽度(不过绝对定位元素根据其它设置是会有例外的,稍后会提到)

什么时候用浮动定位元素

  浮动元素可以用以下几个点来说明:

特点:包裹性,脱离文档流(实际是半脱离)
应用场景:文字环绕效果;多个块级元素水平排列于一行
应用于:块级元素和行内元素皆可

多个块级元素水平排列成一排

  需要让多个块级元素水平排列成一行,这是经常需要做的,如左右双列布局,那么既然要让多个元素在一行上,就不能用行内元素吗?答案是,有些情况确实不能,比如说需要给这个元素指定宽和高时,行内元素是不满足需求的。还有一种常见到浮动的应用的场景是水平列表项~

核心要点-利用包裹性

  通过设置为浮动让多个元素可以处于一行,这个应用的核心离不开浮动元素的特性之一—包裹性(试想如果没有包裹性了,那么这个元素水平方向的宽度会向父元素的宽度去延展,自己在水平方向就沾满了,无论如何也放不下别人和自己挤在一排了~)

记得善后-清除浮动(不占据文档流位置在作祟)

  虽然应用浮动后,看上去达到目的了,但不要忘了善后,清除浮动,因为如果不清除浮动的话,会严重影响其他元素的布局(因为它本身不占据文档流),带来的后续麻烦可是大大的~~

  上面也提到,浮动元素实际上是半脱离文档流,又为什么说是半脱离呢?这里不是明明说元素浮动以后不占据空间,这不就是脱离文档流了吗?看看下一个应用就知道啦~

文字环绕效果

  虽然这里文字环绕效果放到了后面写,但实际上最开始之所以有float就是为了实现文字环绕效果。

环绕直接兄弟文字

  什么是环绕直接兄弟文字呢?也就是一个浮动元素会环绕它的父元素中的文字,这些文字和它是直接兄弟的关系。

  以下代码是一个没有添加浮动的图像和文字为直接兄弟元素的情况:

  <style>
    div.outer{
      width:400px;
      border:1px solid red;
    }
    div>img{
      width:300px;
    }
  </style>
</head>
<body>
<div class="outer">
  
  <img src="http://p4a8bakov.bkt.clouddn.com/image/css/2018/03/19img_161967094653.jpg">
  我是应该要环绕的文字
</div>

  目前真实的效果是这样的:

  可以看到,此时虽然部门文字和图像是在一行的(由默认的行内元素和行内替换元素的对齐方式确实),但也并不是包裹的效果,接下来为图像添加浮动效果:

div>img{
    float:left
}

  现在效果是这样的,果然文字能够环绕了~:

侄子文字内容环绕

  曾经一度以为浮动元素只是对直接兄弟元素文字才有影响,但实际上它对叔侄文字内容也有同样的影响,什么是叔侄文字内容呢?简单说就是浮动元素的兄弟元素(可以是行内元素,也可以是块级元素,总之有显示标签)的文字内容,像下面这样是不加浮动的图像和它的侄子文字的关系:

    div.outer{
      width:400px;
      border:1px solid red;
      
    }
    div>img{
      width:300px;
    }
    
    div.inner{
      border:3px solid green;
      width:350px;
    }
  </style>
</head>
<body>
<div class="outer">
  
  <img src="http://p4a8bakov.bkt.clouddn.com/image/css/2018/03/19img_161967094653.jpg">
  <div class="inner">我是应该要环绕的文字</div>
  </div>
</body>

  现在文字所在的div只是一个普通的块级元素的表现,效果如下:

  同样地,为图像加上左浮动,效果如下:

  也出现了文字环绕~可以即使文字是其它元素(如div,span等元素)的子元素,依然会受它的叔叔浮动元素的影响,并且这里有一个需要注意的点,div元素我们指定了宽度是300px,这里即使它紧邻浮动元素,它的指定宽度依然有效,只是它的宽度中的一大部分内容被浮动元素覆盖了,所以文字被挤到了div内容区的另一边,即文字并不像往常一样从父元素的最左侧开始了,(为浮动元素腾位置),这里由div的边框就可以看到~~~

半脱离文档流

  从这上两个小节里里,我们除了体会到元素浮动后,它的直接兄弟文字/侄子兄弟文字能够环绕自己外,也能意会一下半脱离文档流:

   一个元素浮动后,看上去好像不占据实际文档流空间,它的计算高度为0,进而导致它的父元素的高度为0,所以后续元素可以忽略它的存在,但是如果这个浮动元素直接兄弟元素/侄子元素是文字的话,这些文字还是会受到它的影响,去环绕这个浮动元素。

什么时候用绝对定位元素

移动端相关

判断是否支持touch事件

  1. 有时我们需要根据是否支持touch事件做不同的处理,如有的电脑只支持鼠标事件,则监听鼠标事件,而手机,平板,触屏笔记本等支持触屏的设备则监听触屏事件,具体区分根据xxx.ontouchstart的值来判断,如果值为undefined,则表示未被初始化,不支持触屏,如果值为null则表示经过了初始化,支持触屏

  2. 通过ontouchstart in document来判断是否支持触屏

手机端手指上下滑动时屏幕有时会动,如何避免?

添加属性设置为绝对定位即可

canvas {
position:fixed;
left:0;
top:0;
}```

理解prototype和__proto__

prototype和__proto__时对象属性的访问过程中至关重要的两个属性,这里主要对这两个属性的特点和意义作一些总结。

从对象的属性说起

首先,假设不存在prototype和__proto__,我们知道JS的是存在Object类型的,和很多面向对象语言一下,类型是对多个同种类对象的封装,如学生A存在年龄,姓名,性别,父母,学校,班级,toString,valueOf等属性,和起床,跑步,上学,下课四个方法,学生B,C同样存在这些属性和方法,为了统一管理这些学生的属性和行为,我们可以提供一个Student类,Student类中包含了所有的这些属性和方法,这是通过Student类就可以创建多个学生对象,假设创建了学生a,b,c,现在内存中a,b,c对象的构成是下图1所示的样子:

图1

多个同类对象的问题

由图1可以看到,在这种未经过任何处理的存储结果下,a,b,c三个学生的属性在内存中确实可以完整的存储和访问,但是稍加思考就能觉得好像哪里不太对,是的,有一些属性实在每个学生里都出现的,比如图中可以看到的run(),study(),gotoschool()以及图中没有画出的toString(),valueOf()方法,这么多同样的属性,实际上是没有任何区别的,那位什么要在每一块内存里都存一遍呢?现在只是三个对象,如果会新建成百上千个这类对象呢,空间浪费岂不大发了?这里为了践行不浪费一粒空间的宗旨,前辈们想到了共享属性,如何让这些没有任何区别的属性内在不同对象之间共享,那就好了!于是就有了以下的图2:

图2
可以看到,在这种结构中,a,b,c三个对象中的属性值相同的几个属性均消失了,代替的是多了一个名为`__proto__`的属性,图中可以看出,`__proto__`是一个对象,这个对象所包含的属性正是学生类对象的共享属性,通过把这几个共享的属性放到同一块内存中构成一个对象p,并且通过新增`__proto__`属性的方式,使得对象可以引用到这个对象p,就实现了*通过引用同样的__prop__属性来共享属性对象*

访问共享属性的钩子——proto

前面已经提到了,通过__proto__对象中可以访问到同类对象的共享属性,比如对象a中并没有直接包含run()方法,但是a.__proto__中包含run()方法,这样当用到a.run()时,js引擎在a的直接属性中未找到该方法,则自动会去a.__proto__中寻找该方法属性,如果依然找不到该方法,则继续去下一层a.proto.__proto__寻找该方法,直到某一层的__proto__未undefined为止,利用这个特性,完全可以对一个对象做多层抽象到多个__proto__中,如上例中的学生类的共享属性中包含run(),study(),gotoschool(),toString(),valueOf()方法,完全可以把run(),toString(),valueOf()抽象成一个共享属性对象,为People类使用,因为这些方法属性都是人类的共有特性,进而再把toString(),valueOf()抽象成一个共享属性对象,为Object类使用,即所有的对象所共有的特性。

__proto__属性是所有对象与生具来的属性

我们已经知道,通过__proto__属性可以访问到对象所属类的共享属性,那么这个属性到底来自哪里?总不能每次新建一个对象的时候都要我们手动为__proto__属性来赋值吧?不用担心,来看一下就好了~
创建对象的方式有两种,一种是直接用对象字面量来创建,一种是用构造函数法来创建,我们分别看一下这两种创建方式创建的对象的属性

图3 字面量创建法
图4 构造函数创建法
图三图四可以看出,无论是哪种方式创建的对象,都自带__proto__属性,也就是说一个对象相关的共享属性对象根本无需我们手动指定,它被JS引擎自动赋值给了这个对象的__proto__属性。

共享属性对象的直接生产者——prototype

目前为止,我们知道,通过一个对象的__proto__属性可以访问到它相关的共享属性对象,那么这个所谓的共享对象到底是什么呢?我们还不得而知。同样的,针对两种方式创建的对象的分别查看一下他们的__proto__属性的详细信息

图5 __proto__详细信息——字面量创建法
图6 __proto__详细信息——构造函数创建法
通过图5,图6的对比可以看出,两种创建方式的__proto__属性好像差距有点大,这是问什么呢?仔细看看,用对象字面量方式创建的对象中的该属性看上去似曾相识?可以在图5中看到,__proto__属性包含toString(),valueOf(),isPropertyOf(),hasOwnProperty()等一系列Object对象的原生方法,那么这里的__proto__属性何Object是不是有什么关系呢?所以~是时候来和Object.prototype属性作一下对比了 ``` //对象字面量方式创建的对象 var a={name:'lily',age:17}; typeof Object;//"function" console.log(a.__proto__===Object.prototype);//true //构造方式创建的对象 var Test=function(){}; typeof Test;//"function" var b=new Test(); console.log(a.__proto__===Object.prototype);//false console.log(a.__proto__===Test.prototype);//true ``` 首先,从对象字面量方式创建对象的执行的结果来看,对象的__proto__属性是等于Object的prototype属性的,但是在构造方式创建的对象中这个等式却是不成立的,是不是代表__proto__属性和prototype属性没什么必然关系了呢?NoNoNo!继续往下看 `console.log(a.__proto__===Test.prototype)`这个结果是`true`的,表示a对象的__proto__和Test的prototype属性建立了`等于`关系,那这又说明了什么呢? 很简单,通过递归可以得出 `o.__proto__===Func.prototype`这个恒等式,其中`o`表示任何方式创建的一个对象,`Func`表示创建这个对象的构造函数,现在是不是可以理解了,该例中b是通过构造函数Test创建的,所以b.__proto__===Test.prototype,而a是字面量对象,字面量对象实际上是JS引擎由Object构造的,所以当然了,a.__proto===Object.prototype

到现在,可以推出函数的prototype属性实际上才是真正存放共享属性对象的宿主,关于prototype属性有以下特点:

  • JS引擎为所有函数默认生成了prototype属性
  • prototype属性的值是一个对象
  • 默认的prototype属性对象都自带Constructor属性,且该属性的值是函数本身(注意重新为prototype赋值后Constructor可能不复存在)。

__proto__和prototype的不同意义

我们知道,一般情况下,prototype是函数所特有的属性,而__proto__是所有对象都有的属性,但是函数也是对象,所以就有了:默认情况下,所有函数都同时存在__proto__和prototype属性,但是这两个属性的意义是不同的(__proto__表示了创建该函数的函数(一般是Function)提供的共享属性(即Function.prototype),而prototype则是为通过该函数本身创建的其它对象提供共享属性,通常这个属性会被人为覆盖或扩展),所以一般有函数.__proto__!==函数.prototype

特殊的函数——Function

刚才说到,一般有函数.__proto__!==函数.prototype,但实际上有一个特殊情况,即Function对象本身,它也是一个函数,并且它是创建构造函数的函数,可以很容易的验证
Function.__proto__===Function.prototype,这个特殊情况不妨这样理解:
Function是所有函数的构造函数,所以肯定存在Function.prototype,反过来因为所有函数的构造函数都是Function,所以Function作为函数也不例外,它也应该能理解为通过Function构造函数创建的,所以又Function.proto===Function.prototype

总结

现在,可以对__proto__和prototype来个一句话总结了~——函数的prototype和 对象的__proto__属性共同配合,才有了对象的原型链机制,其中__proto__属性是我们层层递归的访问抽象类的共享属性的直接钩子(没有这个属性,JS引擎无法查找一个对象的上一层的共享属性),而prototype则使得对象能够自动和共享属性对象绑定成为可能(无需我们手动为__proto__属性赋值,通过JS引擎自动将对象的属性__proto__ 和创建对象的构造函数的prototype相绑定)

React源码学习-新建一个组件

class HelloMessage extends React.Component {
  render() {
    return <div>Hello {this.props.name}</div>;
  }
}

ReactDOM.render(
  <HelloMessage name="John" />hi</HelloMessage>,
  document.getElementById('container')
);

HelloMessage定义了一个组件,下部分的ReactDOM.reander是一个常用的把组件Hello渲染到页面的方法,那么一个组件到底是如何渲染的,又是如何更新的呢?这个问题一直很困惑,所以这里边查资料边总结下~

JSX编译的结果React.createElement

JSX是React的特有的语法,可以用JSX来表示一个元素(虚拟DOM),用JSX表示的组件经过了编译以后等同于调用了React.createElement方法。如示例中的<HelloMessage name="John">hi</HelloMessage>,经过JSX编译后结果如下

React.createElement(
  HelloMessage,
  {name: 'John'},
  "hi",
);

somorphic/classic/element/ReactElement.js:ReactElement.createElement的源码地址

/**
 * Create and return a new ReactElement of the given type.
 * [查看React说明文档](https://facebook.github.io/react/docs/top-level-api.html#react.createelement)
 */
ReactElement.createElement = function(type, config, children) {
  var propName;

  // Reserved names are extracted
  var props = {};

  var key = null;
  var ref = null;
  var self = null;
  var source = null;

  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  var childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    var childArray = Array(childrenLength);
    for (var i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // Resolve default props
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
 ....
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
};

这个方法中,只是仅仅创建了一个指定类型的元素,并没有做任何组件相关的初始化操作,一切生命周期有关的方法都在之后由ReactDom.render方法调用后才执行的,在React.createElement中最重要的是完成了以下几件事

  • 将传入的第二个参数和第三个参数以后的参数作为children合并整合到props属性中。
  • 将定义在元素中的默认属性defaultProps和元素本身的props属性合并
  • 将ReactCurrentOwner.current赋值给owner属性。

ReactElement构造一个元素

在React.createElment的最后,可以看到调用了一个ReactElement方法,看上去像构造函数,但又没有用new操作符,这是为什么呢?不放直接来看下ReactElement方法的源码

/**
 * Factory method to create a new React element. This no longer adheres to
 * the class pattern, so do not use new to call it. Also, no instanceof check
 * will work. Instead test $$typeof field against Symbol.for('react.element') to check
 * if something is a React Element.
 *创建一个新元素的工厂方法,这个心元素不再和类有直接关系,所以不用new调用它(其实里面直接用字 
 面量构造了一个对象,并将`type`字段赋值给`type`属性),同时,不能用`instanceof`来判断是否是一个元 
 素,而是要用它的`$$typeof`字段和`Symbol.for('react.element')`比较,如果相同,说明是一个React元素 
 * @param {*} type
 * @param {*} key
 * @param {string|object} ref
 * @param {*} self A *temporary* helper to detect places where `this` is
 * different from the `owner` when React.createElement is called, so that we
 * can warn. We want to get rid of owner and replace string `ref`s with arrow
 * functions, and as long as `this` and owner are the same, there will be no
 * change in behavior.
 * @param {*} source An annotation object (added by a transpiler or otherwise)
 * indicating filename, line number, and/or other information.
 * @param {*} owner
 * @param {*} props
 * @internal
 */
var ReactElement = function(type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allow us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };

  if (__DEV__) {
   
     .......
    } else {
      element._store.validated = false;
      element._self = self;
      element._source = source;
    }
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

工厂方法功能

同样的在工厂方法ReactElement(...)中,没有做任何初始化有关操作,只是新建了一个对象字面量,并对type, key, ref, self, source, owner, props这七个属性进行了赋值,其中最关键的是为元素对象新增了$$typeof字段为一个Symbol类型的固定值,表明它是一个元素
到这里虽然看上去已经新建了一个元素,但其实还没有执行元素类型的真正构造函数,只是把type等属性赋值给了这个元素,而和元素类型相关的构造函数是在之后渲染阶段才做的

遗留问题

  • ReactCurrentOwner.current有什么用

入口方法ReactDom.render

ReactDom源码地址-/src/renderers/dom/ReactDOM.js

...
var ReactMount = require('ReactMount');
...
var ReactDOM = {
  .....
  render: ReactMount.render,
  ....
};

其中ReactDom方法调用的是ReactMount.render方法

ReactMount.render方法

ReactMount源码地址-/src/renderers/dom/client/ReactMount.js

 /**
   * Renders a React component into the DOM in the supplied `container`.
      将React组件渲染到指定的容器中
   * See https://facebook.github.io/react/docs/top-level-api.html#reactdom.render
   *
   * If the React component was previously rendered into `container`, this will
   * perform an update on it and only mutate the DOM as necessary to reflect the
   * latest React component.
      如果这个React组件之前就在容器中被渲染过,这个函数会进行一个更新操作,只会改变必要的DOM 
以反映最新的React componnet
   *
   * @param {ReactElement} nextElement Component element to render.
   * @param {DOMElement} container DOM element to render into.
   * @param {?function} callback function triggered on completion
   * @return {ReactComponent} Component instance rendered in `container`.
   */
  render: function(nextElement, container, callback) {
    return ReactMount._renderSubtreeIntoContainer(
      null,
      nextElement,
      container,
      callback,
    );
  },

遗留问题

  • 为什么说如果这个React组件之前渲染过的话,会进行一个更新操作,渲染过的组件还会经过这个方法调用吗?即 update时会经过这个函数调用吗?我知道在事件中setSate会触发update,那setState和ReactMount.render有什么关系吗?

  • 既然 ReactMount单独封装了_renderSubtreeIntoContainer方法,而这里给_renderSubtreeIntoContainer传的第一个参数是null,是不是其他地方也会调用到_renderSubtreeIntoContainer方法呢,只是传递参数不一样(可能第一个参数不是null)?哪个地方还会调用该方法呢

内部方法ReactMount._renderSubtreeIntoContainer

ReactMount源码地址-/src/renderers/dom/client/ReactMount.js

_renderSubtreeIntoContainer: function(
    parentComponent,
    nextElement,
    container,
    callback,
  ) {
   ...
    var nextWrappedElement = React.createElement(TopLevelWrapper, {
      child: nextElement,
    });

    var nextContext;
    if (parentComponent) {
      ....
    } else {
      nextContext = emptyObject;
    }

    var prevComponent = getTopLevelWrapperInContainer(container);

    if (prevComponent) {
      var prevWrappedElement = prevComponent._currentElement;
      var prevElement = prevWrappedElement.props.child;
      if (shouldUpdateReactComponent(prevElement, nextElement)) {
        var publicInst = prevComponent._renderedComponent.getPublicInstance();
        var updatedCallback =
          callback &&
          function() {
            callback.call(publicInst);
          };
        ReactMount._updateRootComponent(
          prevComponent,
          nextWrappedElement,
          nextContext,
          container,
          updatedCallback,
        );
        return publicInst;
      } else {
        ReactMount.unmountComponentAtNode(container);
      }
    }

    var reactRootElement = getReactRootElementInContainer(container);
    var containerHasReactMarkup =
      reactRootElement && !!internalGetID(reactRootElement);
    var containerHasNonRootReactChild = hasNonRootReactChild(container);

    ......
      if (!containerHasReactMarkup || reactRootElement.nextSibling) {
        var rootElementSibling = reactRootElement;
        while (rootElementSibling) {
          if (internalGetID(rootElementSibling)) {
            warning(
              false,
              'render(): Target node has markup rendered by React, but there ' +
                'are unrelated nodes as well. This is most commonly caused by ' +
                'white-space inserted around server-rendered markup.',
            );
            break;
          }
          rootElementSibling = rootElementSibling.nextSibling;
        }
      }
    }

         var shouldReuseMarkup =
          containerHasReactMarkup &&
         !prevComponent &&
         !containerHasNonRootReactChild;
542    var component = ReactMount._renderNewRootComponent(
            nextWrappedElement,
            container,
           shouldReuseMarkup,
          nextContext,
        )._renderedComponent.getPublicInstance();
       if (callback) {
          callback.call(component);
       }
552   return component;
  },

可以看到这个方法实际上,既负责组件的更新和卸载,又负责组件的初始化渲染,只是在最初渲染时,第一个parentcomponent是null,导致相关的变量如prevComponent等是假,所以不会进入更新和卸载逻辑,最终会执行542~552行的代码,调用_renderNewRootComponent方法去渲染一个新的组件。

Tips

  • 这个方法创建一个新的指定类型的React元素,这个元素的type可以有三种不同种类,分别是string类型的标签(如div,span),或者是一个用classfunction定义的React组件类型,还可以是一个React Fragment类型,本文主要讲解涉及到ReactComponent组件的创建

遗留问题

  • 这里的shouldReuseMarkup到底有什么用,怎么来的?
  • 什么情况下第一个参数parentcomponent不是null,我猜会是更新的时候,那更新操作,尤其是setState是如何和这个方法联系起来的呢?
  • 这里为什么要用 TopLevelWrapper类型来包装原本的nextElement(将nextElement复制给它的child属性),然后赋值给nextWrappedElement呢?为什么直接用原来的nextElement不行呢? 先来看一下TopLevelWrapper是一个做什么的组件,其他的以后再说~
/**
 * Temporary (?) hack so that we can store all top-level pending updates on
 * composites instead of having to worry about different types of components
 * here.
 */
var topLevelRootCounter = 1;
var TopLevelWrapper = function() {
  this.rootID = topLevelRootCounter++;
};

内部方法ReactMount._renderNewRootComponent

顾名思义,这个方法应该是渲染一个的根组件(新的意思就是创建新组件,而肯定不是更新组件),其中关键代码如下

/**
   * Render a new component into the DOM. Hooked by hooks!
   *
   * @param {ReactElement} nextElement element to render要渲染的元素
   * @param {DOMElement} container container to render into要渲染到的容器
   * @param {boolean} shouldReuseMarkup if we should skip the markup insertion
   * @return {ReactComponent} nextComponent 返回渲染后的组件
   */
  _renderNewRootComponent: function(
    nextElement,
    container,
    shouldReuseMarkup,
    context,
  ) {
   .....

          ReactBrowserEventEmitter.ensureScrollValueMonitoring();
  385   var componentInstance = instantiateReactComponent(nextElement, false);

           // The initial render is synchronous but any updates that happen during
           // rendering, in componentWillMount or componentDidMount, will be batched
           // according to the current batching strategy.

           ReactUpdates.batchedUpdates(
              batchedMountComponentIntoNode,
             componentInstance,
             container,
            shouldReuseMarkup,
             context,
          );

            var wrapperID = componentInstance._instance.rootID;
            instancesByReactRootID[wrapperID] = componentInstance;

              return componentInstance;
  },

385行代码是一个关键步骤,instantiateReactComponent,初始化一个组件,是终于要执行这个元素相关的构造方法了吗,想想有点小激动,等等,这里一会儿新建元素,一会儿初始化组件,这里的组件和元素到底什么关系呢?实际上,在React中,组件和元素是密不可分的,ReactElement是React元素的表示类,它的一系列属性包括teyp,key,refs等足以描述这个元素的相关信息,而ReactComponent是React元素的操作类(或者真正实现基类?),通过ReactComponent的定义才能够实现和控制元素的渲染,更新及一些列生命周期方法,所以一个ReactElement元素是会有属性能够映射到对应的ReactComponent,同时ReactComponent也有属性能够映射到所属的ReactElement,这个之后应该可以看到的,下面还是看看instantiateReactComponent做了什么~

instantiateReactComponent()方法

函数说明

  • 函数作用: 基于元素类型创建组件,根据提供的节点,创建即将被mount的组件示例,也就是执行完这个方法后

还没被挂载

核心代码

同样地,这里只分析组件类型为自定义组件的情况,其中核心代码为

          else {
105         instance = new ReactCompositeComponentWrapper(element);
          }

ReactCompositeComponentWrapper

到这里终于new了一个对象,然而构造函数却不是Element的类型方法,而是把element作为参数传入了
ReactCompositeComponentWrapper构造函数,那么来看一下ReactCompositeComponentWrapper类做了什么吧

...
12  var ReactCompositeComponent = require('ReactCompositeComponent');
.....
20     // To avoid a cyclic dependency, we create the final class in this module//还是不明白为什么在这个模块中创建这个类。。。
     var ReactCompositeComponentWrapper = function(element) {
         this.construct(element);
     };
...
144   Object.assign(
            ReactCompositeComponentWrapper.prototype,
            ReactCompositeComponent,
            {
               _instantiateReactComponent: instantiateReactComponent,
             },
        );
        module.exports = instantiateReactComponent;

即通过Object.assign,ReactCompositeComponnetWrapper的原型拥有了ReactCompositeComponent的方法,并且拥有了_instantiateReactComponent方法,也就是之前我们分析过的instantiateReactComponent方法,而在ReactCompositeComponnetWrapper的构造函数中调用了this.construct(element),这里的construct实际也是ReactCompositeComponent的原始方法

new ReactCompositeComponentWrapper(element)的结果

我们知道这句代码实际调用了ReactCompositeComponentconstruct方法,新建一个ReactCompositeComponentWrapper的实例,并为这个实例作了一系列变量的初始化,最重要的是将element赋值给了新的组件实例变量,但到这里也仅仅是组件实例化而已,组件的render方法和其它生命周期方法还没有出现

关于ReactCompositeComponent

关于ReactCompositeComponent的分析请看[这里]

遗留问题
  • 感觉ReactCompositeComponnetWrapper类就像是继承了ReactCompositeComponent类并且多了一个 _instantiateReactComponent属性而已,那么为什么要这样划分这两个类呢?不能仅有ReactCompositeComponnetWrapper类吗,ReactCompositeComponent类为什么是必要的,他们两个的本质区别?在什么时候用?
  • 第20行注释中说,为了避免循环调用,在这个模块重定义ReactCompositeComponnetWrapper是什么意思?

ReactUpdates.batchedUpdates

instantiateReactComponent()中没有执行的组件渲染和生命周期方法,在这里会被执行,也就是说,和字面意思不同,ReactUpdates.batchedUpdates方法不仅负责组件的更新,在组件首次渲染时,也会由该方法处理渲染过程(或者说,首次渲染也是更新的一种?由空更新到有~)

function batchedUpdates(callback, a, b, c, d, e) {
  ensureInjected();
  return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}

batchedMountComponentIntoNode

  • 入口:batchedUpdates->batchedMountComponentIntoNode->
  • 作用:批量挂载组件
  • 关键流程:
  1. 通过对象池获取ReactReconcileTransaction类型的transaction
  2. 由该transaction控制mountComponnetIntoNode方法的执行,并传入该transaction本身
  3. 释放对象池的transaction
 var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
    /* useCreateElement */
    !shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement,
  );
  transaction.perform(
    mountComponentIntoNode,
    null,
    componentInstance,
    container,
    transaction,
    shouldReuseMarkup,
    context,
  );
  ReactUpdates.ReactReconcileTransaction.release(transaction);

mountComponentIntoNode

  • 入口:batchedUpdates->batchedMountComponentIntoNode->mountComponentIntoNode
  • 作用: 挂载这个组件并插入DOM中
    部门代码:
......
var wrappedElement = wrapperInstance._currentElement.props.child;
 var type = wrappedElement.type;
.....
var markup = ReactReconciler.mountComponent(
    wrapperInstance,
    transaction,
    null,
    ReactDOMContainerInfo(wrapperInstance, container),
    context,
    0 /* parentDebugID */,
  );

  if (markerName) {
    console.timeEnd(markerName);
  }

  wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
  ReactMount._mountImageIntoNode(
    markup,
    container,
    wrapperInstance,
    shouldReuseMarkup,
    transaction,
  );

可以看到,最终执行挂载操作是调用的 ReactReconciler.mountComponent方法,并且将从上一层传来的transaction传给了 ReactReconciler.mountComponent方法,这个transaction有什么用呢? ReactReconciler.mountComponent又是如何将组件挂载呢?不防看这里了解下。

ReactCompositeComponent.js

  • 源码地址:/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
  • 作用: 负责React复合组件的初始化,渲染,监听器注册,挂载,卸载等一系列生命周期方法的实际控制,我们所定义的组件的render,componentDidMount等方法都只是钩子,正是因为ReactCompositeComponent的包装,我们所实现的这些方法猜得以执行。

属性nextMountID

一个会自增的id,当组件实例化的时候会绑定一个id,这个id的值就是这个自增的nextMountID,这用于
控制ReactUpdates更新组件的执行顺序。

/**
 * An incrementing ID assigned to each component when it is mounted. This is
 * used to enforce the order in which `ReactUpdates` updates dirty components.
 *
 * @private
 */
var nextMountID = 1; 

construct方法

  • 作用: 所有复杂组件的基础构造器
  • 被调用: new ReactCompositeComponentWrapper(element),通过它的包装器类型来调用
  • 参数: element,即将被实例化成组件的元素

源码实现

construct: function(element) {
    this._currentElement = element;
    this._rootNodeID = 0;
    this._compositeType = null;
    this._instance = null;
    this._hostParent = null;
    this._hostContainerInfo = null;

    // See ReactUpdateQueue
    this._updateBatchNumber = null;
    this._pendingElement = null;
    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;

    this._renderedNodeType = null;
    this._renderedComponent = null;
    this._context = null;
    this._mountOrder = 0;
    this._topLevelWrapper = null;

    // See ReactUpdates and ReactUpdateQueue.
    this._pendingCallbacks = null;

    // ComponentWillUnmount shall only be called once
    this._calledComponentWillUnmount = false;

    if (__DEV__) {
      this._warnedAboutRefsInRender = false;
    }
  },

可以看到,在这个方法中,只是完成了component实例的一些变量的初始化,其中最终要的就是将传来的element参数赋值给_currentElement变量,即将元素绑定到了其实例化得组件上。

了解元素的居中

水平居中

正常文档流的两种水平居中方式

  正常文档流中的元素元素的水平居中不外乎两种-行内元素水平居中和块级元素水平居中,大多数场景下只要是要求水平居中,最终都是转换为这两种居中方式之一(绝对定位元素除外)

注意水平居中的特点:
   最终居中元素的父元素肯定是一个正常文档流中的块级元素(非块级元素本身就是包裹性的,当然不会居中其子元素了,同理,一个脱离文档流的块级元素也具有包裹性了(如浮动,绝对定位后)...)。
本节先从居中元素是普通文档流中的元素说起,后续会提到浮动元素和绝对定位元素水平居中的不同。

行内元素水平居中

  行内元素利用text-align:center使其在父级块元素中水平居中

应用条件:

  • 要居中的元素的父元素普通文档流中的块级元素
  • 要居中的元素是普通文档流中的行内元素

块级元素水平居中

   块级元素利用左右外边距为automargin-left:auto;margin-right:auto;使其在父级块元素中居中

应用条件:

  • 要居中的元素的父元素普通文档流中的块级元素
  • 要居中的元素是普通文档流中的块级元素
  • 要居中的元素的width值明确的指定了

浮动元素水平居中

  浮动元素水平居中好像本身就是一个悖论,是的,我们无法直接让浮动元素水平居中于父元素,所以,所有浮动元素的水平居中只能利用包含块,将包含块水平居中

  ??可是,浮动元素为什么要水平居中,我们知道,浮动元素就是为了左浮动或右浮动啊??是的,但不要忘了,有时候我们用浮动并不为了单纯的让它左浮或右浮,而是为了让几个块级能够水平排列(常见的如水平导航栏,让ul内的几个li元素水平排列),这时候问题就来了?用了浮动之后,几个元素确实能够在一行水平排列的,如图所示:

  目前的代码是这样的:
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>浮动元素居中</title>
  <style>
    ul{
      border: 1px solid black;
    }
    .clearfix::after{
      content:'';
      display:block;
      clear:both;
    }
    ul>li{
      list-style:none;
      border:1px solid green;
      margin-left:20px;
      float:left;
    }
  </style>
</head>
<body>
  <ul class="clearfix">
    <li>首页</li>
    <li>产品</li>
    <li>帮助</li>
    <li>问题</li>
  </ul>

</body>
</html>

  但现在几个li元素是在ul的左侧的,我并不想让他们简单的居左或居右,而是让他们在页面中水平居中,怎么办呢?麻烦了,因为明明浮动元素就只能左浮动或右浮动啊,既想利用浮动使其处于同一行,又想水平居中(而不是居左或居右),如何解决这个悖论呢? 不防换一种思路,既然浮动元素只能左浮或右浮于父元素,那么不能从让它直接在父元素中居中着手了,我们只要让它的父元素在父元素的父元素中居中就好了啊,可能你会说这有什么用啊,它的父元素是一个块级元素,宽度默认为100%,和其父元素宽度一样,更别提居中了,除非指定一个显示的宽度,在利用左右外边距设置为auto(但这样不免太麻烦,而且还要知道父元素宽度设置为多少能够刚刚包裹住几个li元素,费力不讨好:)),所以找到问题了,我们只要让这个父元素不是一个块级元素就好啦,用display:inline-block,让它成为一个行内元素,自己就能包裹住子元素,就能利用第一种方式text-align:center居中于其父元素啦~~~

  变更css代码如下:

...
    div.wrapper{
      text-align:center
    }
    ul{
      display:inline-block;
      border: 1px solid black;
    }
  ...
 
<body>
  <div class="wrapper">
    <ul class="clearfix">
      ...
    </ul>
  </div>
</body>

  现在效果如下:

  可见现在确实达到了`ul`包裹自`li`列表,又能水平居中的效果,图示中看出li列表有点偏右的,是因为为`li`元素设置过`margin-left`的原因,后续将第一个`li`元素的作外边距设置为0就大功告成啦

绝对定位元素水平居中

  首先,我为什么要让一个绝对定位元素水平居中呢?那么多办法能让普通文档流中的元素水平居中,既然我想让元素水平居中,又为什么要让它绝对定位呢?绝对定位于中间,是不是没有必要啊?不知道你有没有想过这个问题,如果有的话,不妨想想另一种情况,我并不是为了定位于中间而采用绝对定位的,而就是为了让一个元素脱离文档流,不影响其他元素的任何布局,所以必须要用到绝对定位,但恰好这是我还要让它水平居中(比如这种情况实现一个loading效果),这就是现在面临的问题了~~

  要让绝对定位元素水平居中于离其元素最近的已定位的祖先元素,只要同时满足以下几个条件即可(缺一不可):

  • 要水平居中的绝对定位元素的width值需显示指定;
  • 要水平居中的绝对定位元素需设置left:0;right:0(其它值也可以,只要left,right值相等即可);
  • 要水平居中的绝对定位元素需设置margin-left:auto;margin-right:auto;

  首先,一个单纯的绝对定位元素,它的css代码只是简单到设置了一个绝对定位而已,像下面这样:

<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>绝对定位水平居中</title>
  <style>
    div.wrapper{
      width:600px;
      height:100px;
      border:1px solid black;
      position:relative;
    }
    div.inner{
      border:1px solid red;
      
      position:absolute;
      
    }
  </style>
</head>
<body>
  <div class="wrapper">
    <div class="inner">要居中的元素,我现在是绝对定位的</div>
  </div>

</body>
</html>

  现在它的表现是这样的:

  可以看到,此时绝对定位元素只是一个默认在相对定位的元素的左侧并···具有有包裹性的元素(自身的宽度刚好能包裹住其内容)而已。 那么,现在为了验证一下效果,为绝对定位元素添加以下CSS:
left:0;
right:0;
width:100px;
margin-left:auto;
margin-right:auto;

  再来看一下下图,现在已经水平居中了:

  虽然看到,设置了左右外边距为auto,指定宽度值,top,left设置为0后,的确可以把绝对定位的元素居中,但其实是有点好奇的,这么多设置,我随便把其中一个属性设置去掉或者变掉,真的会无效吗?所以现在就来试一下~

未指定width值时

  当我仅仅把CSS中width的属性去掉时,效果如下:

  可以看到,现在绝对定位元素其实勉强也可以说算是水平居中的,因为它的宽度和父元素宽度一样了(都填充满了,也不能不算居中了。。),但正如上所说,它的宽度已经自动填充为父元素的宽度了,这并不是我们要的居中效果,也就是说此时未指定width值,但设置了左右外边距为`auto`,和`left:0;right:0`的话,该绝对定位元素会失去它应有的包裹性而填充父元素宽度(所以为绝对定位元素设置宽度是比不可少的,虽然这时候它的宽度按指定的宽度来的,所以又来了一个问题,这种方式值适应于宽度是固定的绝对定位元素)

缺少其它设置时

  当缺少其它设置时,如没有设置左外边距为0/没有设置右外边距为0/没有设置left:0/没有设置right:0/将left,right属性值设置为不相等的值等等,只要和规则中的几个设置不一致,结果绝对定位元素就会居左或居右,如下图示把right:0;去掉以后的效果,其它的可以自行实验

  关于绝对定位的详细原理,可以查看张鑫旭大神博客里的这篇文章—小tip: margin:auto实现绝对定位元素的水平垂直居中

同时水平和垂直居中

flex居中

为父元素添加三个属性:

display:flex;
justify-content:center;
align-items:center;

利用绝对定位实现父div中子div居中

居中条件

  • 子div高度要指定

Tips

  • left,right,top,bottom都需指定为0(为什么?)

代码如下:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>绝对定位居中</title>
  <style>
    #wrapper{
      width:200px;
      height:200px;
      border:1px solid red;
      position:relative;
    }
    #inner{
      width:80px;
      height:50px;
      position:absolute;
      border:1px solid green;
      left:0;
      right:0;
      top:0;
      bottom:0;
      margin:auto;
    }
  </style>
</head>
<body>
<div id="wrapper">
  <div id="inner">绝对定位实现居中</div>
</div>
</body>
</html>

居中效果如下图

从一次利用块级元素绝对定位引发的bug感悟出元素的包裹性宽度

这篇总结的起因为我在写导航栏时遇到的一个bug,开始时,导航栏的相关代码如下:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
  <style>
    a{
        text-decoration:none;
        color:white;
    }
    li{
      list-style: none;
    }
   .header nav{
        float:right;
        padding-top:7px;
    }
    nav>ul>li {
        float:left;
        margin:0 17px;
    }
    nav>ul>li>a {
        
        border-top:   3px solid transparent; 
        border-bottom: 3px solid transparent;
        padding-top:5px;
        padding-bottom:5px;
        display:block;
        font-weight:bold;
        font-size:12px;
        color:inherit;
    }
  </style>
</head>
<body>
 <div class="header clearfix" id="header">
        <div class="header-inner">
            <nav>
                <ul class="clearfix">
                <li><a href="#">关于</a></li>
                <li><a href="#">技能</a></li>
                <li><a href="#">作品</a></li>
                <li><a href="#">博客</a></li>
                <li><a href="#">日历</a></li>
                <li><a href="#">联系方式</a></li>
                <li><a href="#">其他</a></li>
                </ul>
            </nav>
        </div>
    </div>
</body>
</html>

效果如下图一:

图1
接下来我要实现的效果是在博客这一列表项下添加3个子列表,所以我对html简单了添加了以下内容:
<li>
       <a href="#">博客</a>
      <ul>//新增ul元素
             <li>博客1</li>
             <li>博客2</li>
              <li>博客2</li>
       </ul>
 </li>

现在,导航栏效果如下图2:

图2
可以看到,添加了ul列表后,基本实现了子级列表项,但可以明显的看到,此时原列表项博客,收到了子列表项宽度的影响,被明显的拉宽了,这样显然不优雅,那么有没有什么办法,使其不受子列表项的影响,自身宽度保持不变呢?这时候想到了,将子列表项绝对定位,使其脱离文档流, 这样它的宽度肯定不会影响到父元素的宽度了,于是为字列表项ul元素添加了相对于父元素li的绝对定位,相关CSS代码如下:
nav>ul>li {
        position:relative;
    }
    nav>ul>li>ul{
      position:absolute;
    }

本以为这下可以完成任务了,结果刷新后,效果却如下图3所示

图2
是的,正如图中所示,绝对定位解决了列表项宽度受影响的问题,但却引入了新的问题,子列表项的宽度太窄了!!那么这是什么原因呢?实验后发现这个表现有两方面的综合原因。

块级元素绝对定位后,它本身及其所有后代块级元素会有包裹性

最开始的块级元素及其子元素的代码如下:

 .pos{
      
      border:1px solid red;
    }
    .first{
      border: 1px solid green;
      margin-bottom:10px;
    }
    .seconed{
      border: 1px solid black;
    }
    .seconed div{
      border: 1px solid #888;
      margin-bottom:5px;
    }

<div class="pos"><div class="first">第一个子元素div</div>
    <div class="seconed"><div>第二个子元素的子元素1 </div>
    <div>第二个子元素的子元素2 </div</div>
  </div>

效果如下图4所示:

图4

然后为了观察绝对定位的影响,为最外层的div元素设置为绝对定位,如下代码:

.pos{
      position:absolute;
     ......
    }

之后,效果如图5:

图5

对比图5发现,添加绝对定位以后,不管是其本身还是直接子元素first类的div,或是其孙元素seconed类的div的子元素,其宽度均收缩为所包含文字需要的宽度,不再主动填充外层容器,展现了其宽度的包裹性。

块级元素绝对定位后,它本身的宽度依然不能超过父元素的宽度

这个特性其实是通俗特性的扩展,及无论是否脱离文档流,div元素本身的宽度都不能自动超出父元素的宽度,除非显示指定宽度值。
在最开始的导航栏一例中,设置了绝对定位后,因为脱了了文档流,而恰好其父元素li又设置过float:left属性,因此li的宽度本身就又包裹性,(浮动元素的宽度包裹性和绝对定位元素的宽度包裹性异曲同工),只会包裹住其子元素a标签的内容,(因为ul绝对定位后脱离了文档流,没有绝对定位时还在文档流中,是可以包裹住的,所以绝对定位前ul列表的宽度是正常的),所以ul标签的宽度受限于父元素li的宽度,当然很窄了!!

如何解决层层包裹性带来的文字自动换行的bug?

经过上面的分析,这里bug的主要原因还是 ul绝对定位以后元素自身脱离文档流,导致浮动的父元素li(本身也有包裹性)未将子元素ul宽度包裹住,只包裹了a标签的宽度,所以,虽然此时ul按理说应该能包裹住子元素的宽度的,但是因为其父元素li宽度过窄,导致ul及其子元素的宽度也受限,其文字内容不得不自动换行了。
这里为了解决这个bug,可以换一种思路,只要文字不自动换行是不是就可以了?添加如下样式,即增加
white-space:nowrap;属性设置

    nav>ul>li>ul{
     .....
      white-space:nowrap;
    }

现在效果如图6所示:

图6

bug完美解决了~

读CSS世界学到的小知识

1. 幽灵空白节点

每个行框盒子前面都存在幽灵空白节点,只不是通常这个节点是隐藏的,而在某些特定条件下这个节点会出现,通常会激活幽灵空白节点的有以下几种情况:

  • 行框的首个span元素的display为inline-block;
  • span元素的垂直padding不为0,会使得该span元素所在行框的幽灵空白节点显现
    那么幽灵空白节点被激活了是什么效果呢?额,想象成有一个透明的字符出现在行框的开头位置就行了,只可意会,不可看见,所以是幽灵啦,那么有什么办法可以消除这个幽灵节点带来的副作用(比如说明明感觉高度应该是0,结果却不为0;比如说inline-block元素常出现的下方有空白,这也是由幽灵节点和行内元素对齐方式的共同作用造成的)吗?简单~设置font-size:0即可

2.替换元素和display

替换元素的尺寸由内部元素决定,无论将display设置为inline还是block,这一点不同于非替换元素,所以即使修改替换元素的display属性为block,还是无法让元素在水平方向上100%自适应容器。(用width:100%+border+box-sizing:border-box显示指定;box-sizing:border-box为替换元素而生)

3.内联元素的垂直padding

内联元素的垂直padding是会影响布局和视觉表现的,只是因为内联元素在垂直方向的表现完全受line-height和vertical-align的影响,设置垂直padding不会在视觉上改变两行之间的间距,所以给我们造成了垂直padding没有起作用的假象,但只要为内联元素添加背景色就可以看出,垂直padding是有视觉表现的,但它确实对上下元素的原本布局没有任何影响,只是可能会发现垂直方向的层叠,但它对父元素的表现可能有影响,如当包含pdding后垂直方向占有的尺寸超出父元素且父元素设置overflow:auto时,会出现滚动条,所以说垂直padding对布局和视觉均有影响。。
但内联元素垂直padding只是对布局有影响(某些情况的布局,如父元素的滚动条是否出现),它是不会加入行盒高度的计算的,不仅如此,对于非替换内联元素来说,垂直方向的margin和border也不会加入内联元素的行盒高度的计算。

4.内联元素的clientHeight和clientWidth

内联元素没有可是宽度和可视高度的说法,clientHeight和clientWidth总是为0.

5.非替换内联元素的垂直padding

加入背景色可以看到,垂直发现的padding会影响非替换内联元素的尺寸空间,但对上下元素的布局是没有任何影响的,仅仅是垂直方向发生了重叠

6.不影响其它元素布局的层叠

CSS中,除非替换元素的垂直padding外,relative元素的定位,盒阴影box-shadow以及outline,这些效果均可能会出现层叠,但他们的层叠是由区别的,可以分为两类:一类是纯视觉层叠,不影响外部尺寸,如box-shadow和outline;另外一类则会影响外部尺寸,如非替换内联元素的padding,可以用如下方式来区分:如果父容器overflow:auto,层叠区域超出父容器时,没有滚动条出现,则是纯视觉的,否则会影响尺寸和布局。

7.非替换内联元素的margin

和padding不同,垂直方向的margin对非替换内联元素的尺寸和布局没有丝毫影响,在水平方向上,因为内联元素宽度表现为"包裹性",所以不会影响内联元素的内部尺寸(content+padding+border),当然是会增加它的外部尺寸(content+padding+border+margin)的

8.百分比padding

padding可以设置为百分比,水平方向的百分比padding是相对于父元素的宽度来计算的,这一点毫无疑问,但需要注意的是垂直方向的百分比padding也是相对于父元素的宽度来计算的,利用这点可以用padding实现宽高比固定的图形。

9.百分比margin

和padding一样,margin的百分比值在水平和垂直方向均是相对于宽度计算的,不过相对于padding,margin百分比值的应用价值较低,根本原因在于和padding不同,元素设置margin在垂直方向上无法改变元素自身的内部尺寸,往往需要父元素作为载体,此外,由于margin合并的存在,垂直方向往往需要双倍尺寸才能和padding表现一致。

10.margin合并

11. 格式化高度的特别之处

绝对定位元素具有格式化高度和宽度,尤其是对于父元素height为auto的元素来说,绝对定位元素的格式化高度依然生效,享有很大优势:

.* 普通块级元素,当其父元素height值是auto时,它本身的height值是不支持百分比的,但绝对定位元素的高度则总是支持百分比的

  • 普通元素的高度是不会受到父元素的高度限制的(没有高度自动填充这一说),但绝对定位元素的格式化高度使得它总是能在垂直方向上自动填充(margin+border+padding+height=所相对定位元素的height,即使这个相对定位元素的height是auto也支持)

12.绝对定位元素的垂直auto实现块级元素垂直居中

利用绝对定位元素的格式化高度和垂直auto来实现垂直居中(缺点:IE8及以上版本来支持)

13.line-height和高度

对于纯文本元素,line-height直接决定了最终的高度,而如果同时又替换元素,line-height的表现则弱了很多,只能决定最小高度,原因有二:一是替换元素的高度不受line-height影响,而是vertical-align在作祟

14.替换元素的line-height

line-height不可以影响替换元素(如图片)的高度!但一个常见的场景可能为我们带来困惑:

.box{
lint-height:200px;
background:red;
}
<div class=‘’box”>
<img src="x.jpg" height="50px">
</div>

本例的效果中,只有一张图片,然而.box的高度确是200px,所以我们可能会想,肯定是line-height把图片的高度变高了,进而把.box撑起来了,然而不是这样的,.box确实是被受line-height影响的元素撑起来的,可这个元素不是图片(替换元素),而是行框盒子前面那个宽度为0的幽灵节点!!(替换元素能激活这个幽灵节点)

元素的宽高和文档流

line-height总结

常见误区:
line-height的默认值是1.4?
之前有在不少资料上看是这样说的,但实际上默认值并不一定是1.4,line-height的默认值和字体有关,不同字体对应的默认line-height值不同,一般字体的设计师会指定该字体的行高(即行高是某个值得时候,该字体多行显示时比较优雅),有的是1.4,有的是1.5

DOM api总结

监听滚动 window.scroll

  • 获取用户滚动的高度,window.scrollY,y方向上的滚动距离

window的页面相关属性

  • window.scrollY 获取页面的垂直滚动距离

document获取元素操作

  • document.getElementById 返回指定id的一个元素
  • document.getElementsByClassName 返回指定类名的一个元素数组
  • document.getElementsByTagName 返回指定标签名称的一个元素数组
  • document.querySelector 返回匹配指定选择器的一个元素,如可用于导航栏 href="#id",后续查询中只要获取href的属性值,进行querySelector操作即可(即参数是一个选择器,可以是id选择器,也可以是类选择器等。)
  • document.querySelectorAll 类似document.querySelector ,只是返回的是匹配指定选择器的一个元素数组

根据一个元素元素获取相关元素操作

element.nextSibling获取兄弟元素

element.nextSibling可以获取一个元素的兄弟元素,不过存在一个问题,即有可能获取下一个兄弟元素之前的空白字符,所以通常需要根据nodeType做判断,如可以排除文本元素。

元素的属性值

  • element.tagName 获取元素的标签名称,所获取的标签名称为大写,如一个ul元素的tagName属性值为大写的UL(getElementsByTagName中的参数则需传入真实的tagName)
  • element.offsetY 获取元素相对于页面顶端(不是窗口顶端)的垂直距离,这个距离是固定的,不会随页面滚动而发生变化。

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.