你好2007

你好2007 查看完整档案

南京编辑  |  填写毕业院校  |  填写所在公司/组织 hai2007.gitee.io/sweethome/ 编辑
编辑

我还惊讶地意识到, 在我生命中有很多时刻, 每当我遇到一个遥不可及、令人害怕的情境,并感到惊慌失措时, 我都能够应付——因为我回想起了很久以前自己上过的那一课。

我提醒自己不要看下面遥远的岩石, 而是注意相对轻松、容易的第一小步, 迈出一小步、再一小步,就这样体会每一步带来的成就感, 直到完成了自己想要完成的, 达到了自己的目标,然后再回头看时, 不禁对自己走过的这段漫漫长路感到惊讶和自豪。

               ———— 摘自 莫顿·亨特《走一步,再走一步》

个人动态

你好2007 发布了文章 · 2月23日

前端常用的算法思想理解和演示

递归与分治策略

把一个规模为n的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同,递归的解这些子问题,然后把各个子问题的解合并得到原问题的解。

【题目】

使用快速排序方法排列一个一维数组。

【思路】

对于输入的子数组a[p:r],按照一下3个步骤进行排序:
1)分解divide:以a[p]为基准元素将a[p:r]划分成3段a[p:q-1],a[q]和a[q+1:r],其中a[q]不小于a[p:q-1]中的任何元素且不大于a[q+1:r]中的任何元素,下标q在划分中确定。
2)递归求解conquer:通过递归调用排序,分别对a[p:q-1]和a[q+1:r]进行排序。
3)合并merge:合并a[p:q-1],a[q]和a[q+1:r]返回为最终结果。

【代码实现】

var __array__ = [1, 3, 2, 4, 5, 57, 6, 46, 4, 6, 45];

console.log("排序前:" + __array__);

function sort(x, y) {
    if (x < y) {
        var p = partition(x, y);
        sort(x, p - 1);
        sort(p + 1, y);
    }
}

function partition(p, q) {
    var x = p;
    var y = q;
    var r = p;
    var flag = __array__[r];
    while (true) {
        while (__array__[x] <= flag && x < y) {
            x++;
        }
        while (__array__[y] > flag && y > x) {
            y--;
        }
        if (x >= y) {
            break;
        }
        var temp = __array__[x];
        __array__[x] = __array__[y];
        __array__[y] = temp;
    }
    if (__array__[x] > flag) {
        x--;
    }
    __array__[p] = __array__[x];
    __array__[x] = flag;
    return x;
}

sort(0, __array__.length - 1);

console.log("排序后:" + __array__);

动态规划

和分治法基本思想有共同的地方,不同的是子问题往往不是独立的,有事母问题要借助子问题的解来判断,因此把已经计算好的问题记录在表格中,后续如果需要查询一下,可以避免重复计算,这是动态规划的基本思想。

不过动态规划具体实现起来多种多样,不过都具有相同的填表格式,通常按照下面步骤设计算法:

1)找出最优解的性质,并刻画其结构特征;

2)递归的定义最优值;

3)以自底向上的方式计算出最优值;

4)通过计算最优值时刻意记录的判断结果来构造最优解。

可以使用该算法思想设计算法的问题一般会具有二个决定性的性质:

1)最优子结构性质;

2)子问题重叠性质。

备忘录算法

和上面的算法思想差不多,不同的是备忘录为每个解过的子问题建立备忘录以备需要的时候查看,避免了相同的问题计算多次。

一般来说,当一个问题的所有子问题都至少要解一次时,用动态规划比备忘录要好,因为不会有任务暂存且没有多余的计算;当子问题空间中部分问题不必解时,用备忘录比较好。

不过上面不是绝对的,这样说只是想区别一下二个思想的不同,具体的时候还是要根据业务场景来在保证可行的前提下选择更好的方法。

【题目】

给定n个矩形{A1,A2,...,An},其中Ai与Ai+1是可乘的,由于矩阵满足结合律,不同的加括号方法计算次数不一样,求最优的加括号方法。

【思路】

分别计算有1,2,3,...,n个矩阵的最优解,计算i个时候,全部的i-1的最优解已经记录下来了,保证计算不重复。

【代码实现】

/**
* 初始化数据
*/
var P = [30, 35, 15, 5, 10, 20, 25]; //记录了矩阵的大小
var num = P.length - 1; //矩阵个数
var minNum = [];
var i, j; //全局复杂循环变量

/**
* 初始化数据
*/
for (i = 0; i < num; i++) {
    minNum[i] = [];
    for (j = 0; j < num; j++) {
        if (i == j) {
            minNum[i][j] = 0;
        } else {
            minNum[i][j] = "#";
        }
    }
}
/**
* 计算最优并记录下来
*/
for(i=2;i<=num;i++){//计算的矩阵个数,从二个开始到全部的情况
    for(j=1;j<=num+1-i;j++){//计算矩阵第j到第i+j-1个的情况
        //先初始化认为在第j分割是最优的(在第j分割的意思是j单独一个,j+1->i+j-1是一组)
        var splitIndex=j;
        var splitMin=minNum[j][i+j-2]+P[j-1]*P[j]*P[i+j-1];
        minNum[j-1][i+j-2]=splitMin;
        for(splitIndex=j+1;splitIndex<=i+j-2;splitIndex++){
            splitMin=minNum[j-1][splitIndex-1]+minNum[splitIndex][i+j-2]+P[j-1]*P[splitIndex]*P[i+j-1];
            if(splitMin<minNum[j-1][i+j-2]){
                minNum[j-1][i+j-2]=splitMin;
            }
        }
    }
}

console.log("最优次数:");
console.log(minNum);

贪心算法

算法思想很简单,和字面意思一样,每次都选择对自己最有利的,不过这是有条件的,只有在满足条件下每次选择最有利自己的才可以获取最优解。

贪心选择性质和最优子结构性质是该思想最重要的性质:

1)贪心选择性质:所求问题的整体最优解可以通过一系列局部最优的选择达到。

2)最优子结构性质:当一个问题的最优解包含其子问题的最优解时,称此问题具有此性质。

【题目】

有一批集装箱要装上一艘载重为c的轮船,其中集装箱i的重量为wi,要求在装货体积不受限制的条件下尽力多装集装箱的解。

【思路】

先排序,然后选择从最轻的开始装货物。

【代码实现】

这里就不提供具体代码了,因为感觉没有什么意义,最重要的是要先确定问题满足贪心选择性质,这样在很多时候,可以更容易的解决问题,这点很重要。

回溯法

说的直白点就是深度优先方式系统搜索问题的算法。

算法使用例子

【题目】

有一批共n个集装箱要装上两艘载重方别为c1和c2的轮船上,其中集装箱i的重量为wi,且全部集装箱重量不大于两艘载重之和,问是否有一个装载方案完成装载。

【思路】

对第一艘船,构造一个0/1树,0代表不选择,1代表选择,然后分别去从根节点试图爬到叶节点,去一一记录下来可行的,选择最小的为解,余下的判断第二艘船是否装的下即可。

【代码实现】

var weight1 = 30; //第一艘船载重
var weight2 = 10; //第二艘船载重
var w = [1, 9, 9, 4, 4, 9]; //集装箱

var nowW1 = 0; //当前载重
var nowBest1 = 0; //当前最优装载
var n = w.length; //集装箱个数

function Loading(deep) {
    if (deep > n) { //如果到达根
        if (nowW1 > nowBest1)
            nowBest1 = nowW1;
        return;
    }
    if (nowW1 + w[deep - 1] <= weight1) { //如果1分支可以
        nowW1 += w[deep - 1];
        Loading(deep + 1);
        nowW1 -= w[deep - 1];
    }
    //0分支
    Loading(deep + 1);
}

function main() {
    Loading(1);
    var firstLoad = nowBest1;
    var all = 0;
    for (var i = 0; i < n; i++) {
        all += w[i];
    }
    console.log("第一艘载重:" + firstLoad + "n");
    if (all > weight2 + firstLoad) {
        console.log("失败n");
    } else {
        console.log("成功n");
    }
}

main();

分支限界

对比回溯法就很容易思考,用广度优先的办法,不断扩大当前节点的孩子为当前节点,主要是求解一个最优解,算法相比回溯法要简单些。

【题目】

有一批共n个集装箱要装上两艘载重方别为c1和c2的轮船上,其中集装箱i的重量为wi,且全部集装箱重量不大于两艘载重之和,问是否有一个装载方案完成装载。

【思路】

借助队列,一层层来检查,找到最优解。

【代码实现】

var weight1 = 30; //第一艘船载重
var weight2 = 10; //第二艘船载重
var w = [1, 9, 9, 4, 4, 9]; //集装箱

var nowBest1 = 0; //当前最优装载
var n = w.length; //集装箱个数

var arrayFIFO = [];

arrayFIFO.push([1, 1]); //deep,此时已经载重
arrayFIFO.push([1, 0]);

var nowBest1 = 1;

while (arrayFIFO.length > 0) {
    var nowNode= arrayFIFO.shift();
    currentDeep = nowNode[0];
    currentWeight = nowNode[1];
    if (currentDeep >= n) {
        if (currentWeight > nowBest1) {
            nowBest1 = currentWeight;
        }
    } else {
        arrayFIFO.push([currentDeep + 1, currentWeight]);
        if (currentWeight + w[currentDeep] < weight1) {
            arrayFIFO.push([currentDeep + 1, currentWeight + w[currentDeep]]);
        }
    }
}
allW = 0;
for (val = 0; val < w.length; val++) {
    allW += w[val];
}
console.log("第一艘船载重:" + nowBest1);

if (allW <= nowBest1 + weight2) {
    console.log("成功");
} else {
    console.log("失败");
}
查看原文

赞 1 收藏 0 评论 0

你好2007 发布了文章 · 2月20日

Git版本管理工具常用命令整理和说明

工作区:就是你在电脑里能看到的目录。

暂存区:英文叫stage, 或index。一般存放在 ".git目录下" 下的index文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。

版本库:工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。

第一步:用户名和邮箱。

分别修改用户名和邮箱,其中--global带和不带的区别在于操作文件是.git/config还是~/.gitconfig,如果你使用的是--system则对应的就是/etc/gitconfig文件。

对于是否在最后带username或email,区别在于你是设置还是查看。

git config [--global] user.name [username]

git config [--global] user.email [email]

第二步:主机。

列出所有远程主机,使用-v选项(即:git remote -v),可以参看远程主机的网址,使用show hostname可以查看主机hostname的详细信息。

git remote [show hostname]

添加连接的远程仓库,其中hostname是给仓库起的名称,uri是仓库地址。

git remote add hostname uri

删除连接的远程仓库,其中hostname是给仓库起的名称。

git remote rm hostname

修改远程主机oldhostname名称为newhostname。

git remote rename oldhostname newhostname

第三步:初始化。

使用该命令会在目录中创建新的Git仓库,本地化的。

git init

把uri指定的仓库代码克隆到本地来,如果加了可选参数dir,表示克隆到该文件夹下。

git clone uri [dir]

第四步:提交。

从远程服务器拉取代码更新,hostname是对应的仓库名称,branchname是分支名称。

git pull hostname branchname

【不推荐直接用这个】添加文件到缓冲区,你可以指定添加的文件filename,也可以使用-A把文件夹下全部文件添加到缓冲区。

git fetch hostname branchname

git diff localbranchname hostname/branchname[ -- filepath][ >>filePath]

git merge hostname/branchname

【推荐使用这个比较安全】以上命令的含义:首先从远程的hostname的branchname分支下载最新的版本到hostname/branchname分支上,然后比较本地的branchname分支和hostname/branchname分支的差别,最后进行合并。

最后可选参数 -- filepath是指定对比的文件,不指定就是全部文件。

最后的可选参数 >>filePath表示把差异文件输出到文件,方便查看。

git add filename|-A

提交代码到本地仓库,-m后面的remark表示对这次提交的描述。

git commit [-m remark]

提交代码到远程服务器,hostname是对应的仓库名称,branchname是分支名称。

git push hostname branchname

第五步:日志。

查看日志(-p显示每次提交的内容差异),后面的那些可选参数是对日志过滤以后再显示,-num举个例子:-2表示查看最近2次的提交历史记录,后面的fileName和branchName方便表示查看特定文件和分支的日志,commit代表查看提交日志。

git log [-p] [-num|fileName|branchName|commit]

显示整个本地仓储的commit,包括所有branch的commit,甚至包括已经撤销的commit,只要HEAD发生了变化,就会在reflog里面看得到,git log只包括当前分支的commit。

git reflog

第六步:回退。

将特定的filename文件回滚到hashname提交。

git checkout hashname filename

用暂存区的所有文件直接覆盖本地文件,不给用户任何确认的机会,可以理解放弃本地修改。

git checkout .

如果发现最新的一次提交完了加某个文件,它会把最新的提交打回暂存区,并尝试重新提交。

git commit —amend

把add添加的文件filename从缓冲区删除。

git reset -- filename

恢复到hashname指定的提交时,不保留修改。

git reset --hard hashname

恢复到hashname指定的提交时,保留修改。

git reset --soft hashname

回滚提交,其中HEAD是最新的一次提交别名(git revert是用一次新的commit来回滚之前的commit,git reset是直接删除指定的commit。)。

git revert HEAD|hashname

第七步:比较。

查看二次提交对比修改了什么,可以看见他们之间的全部更改。

git diff commitFromHashname..commitToHashname

执行该命令可以知道仓库目前的状态,是否是最新代码,有没有修改等。

git status

查看当前全部文件的修改

git diff HEAD

仅查看变化(changes 【not staged】to be commited)

git diff

仅查看变化(changes 【staged】to be commited)

git diff --staged

第八步:合并。

合并分支branchname到当前活跃分支。

git merge|rebase branchname

第九步:暂存。

暂存当前正在进行的工作,比如想pull最新代码,又不想加新commit,或者另外一种情况,为了fix一个紧急的bug,先stash,使返回到自己上一个commit,改完bug之后再stash pop,继续原来的工作。

git stash

取出最后暂存的,并从暂存栈移除。

git stash pop

将当前的暂存栈信息打印出来。

git stash list

将暂存栈清空。

git stash clear

将你指定版本号为stash@{num}的工作取出来,不会移除。

git stash apply stash@{num}

第十步:标签。

查看已经打的标签,如果要进行筛选,可以使用-l添加选择(比如git tag -l v1.会选择开头是v1.标签,而git tag -l .2会选择结尾是.2的,以此类推别的,*就是一个通配符)。

git tag [-l 筛选表达式]

打一个名称为tagname并添加该标签的注释为tagremark的标签,这种是普通的标签(如果你有GPG私钥,把-a换成-s就是前面的tag了)。

git tag -a tagname [-m tagremark]

提交标签tagname到远程hostname。

git push hostname tagname

提交全部标签到远程hostname。

git push hostname --tags

删除标签tagname(本地的)。

git tag -d tagname

删除标签tagname(远程的)。

git push hostname -d tag tagname

第十一步:分支。

查看分支状态,如果使用-r(即:git branch -r)表示查看远程分支状态,-a选项查看所有分支。

git branch

创建一个名称叫branchname的分支,此分支初始化拥有的是你创建此分支时活跃分支。

git branch branchname

切换当前活跃分支为branchname。

git checkout branchname

删除本地指定分支branchname。

git branch -d branchname

删除远程指定分支branchname。

git push hostname --delete branchname

修改本地分支oldbranchname的名称为newbranchname。

git branch -m oldbranchname newbranchname

实现在本地删除远程已经不存在的分支。

git fetch --prune
查看原文

赞 3 收藏 2 评论 0

你好2007 发布了文章 · 2月20日

input或textarea中关于光标移动问题

什么是光标

页面上输入框中闪烁的光标其实是一个选区,也就是选区的左边界和右边界直接形成的选区。

非IE浏览器

输入框结点input=document.getElementById('#input')有二个属性: selectionStart、selectionEnd,分别代表选区开始位置,选区结束位置。

通过修改这二个值就可以形成选区,宽度为0也就实现了光标的位置控制和获取。

IE浏览器

IE浏览器提供的API更加丰富:
createTextRange()、 document.selection.createRange()、moveStart() 、moveEnd() 、move() 、collapse() 、text 、select()。

第一步:

//创建一个文本选区对象。
var range = input.createTextRange(); 

第二步:一些操作

range.collapse(boolean);

可以传入一个布尔值作为参数,参数默认值为true,指示向左还是向右压缩

range.moveStart(param1,param2);

第一个参数可选值有 character、word、sentence、textedit. 比如character,即根据字符来偏移。第二个参数代表偏移的多少,正负表示方向。

range.moveEnd(param1,param2);

和上面那个方法参数一样,不同的是这是用来移动结束边界

第三步:

range.select();  //将range包含的区域选中。

需要注意的是:在调用range.select()方法之前,选区对象的内容并不会被添加选中效果

补充:

var range = document.selection.createRange();

这个方法根据当前页面中的选中文字区域来创建一个选区对象,这个选区对象与createTextRange方法的到选区对象的区别在于,它的选区范围为页面选中文字的区域,即它的左右边界不再是默认的左最小右最大。

range.move(param1,param2);

和moveStart参数一样,对于移动光标比较友好。

查看原文

赞 2 收藏 1 评论 0

你好2007 发布了文章 · 2月20日

margin 外边距

第一步:基础说明。

margin的意思很容易明白,就是外边距,用更通俗的话说,就是二个盒子之间间距的设置。

margin有许多需要注意的地方,比如块级元素垂直相邻外边距会合并,行内元素实际上不占上下外边距,左右外边距也不会合并,浮动元素的外边距也不会合并。

普通元素的margin百分百是按照父级元素(正确的说应该是包含块,具体可以看这篇文章关于CSS中设置overflow属性的值为hidden的相关理解)的宽来计算的,而绝对定位的元素的margin百分比是按照第一个定位元素(relative,absolute和fixed)的宽来计算的。

第二步:block元素重叠。

block元素(不考虑float和absolute)在垂直方向发生margin重叠(不考虑writing-mode改变书写方式);margin三种重叠:1.相邻兄弟元素;2.父亲元素和第一个或最后一个孩子元素;3.空的block元素。

第三步:重叠条件。

父子元素重叠条件(margin-top)

  • 父元素非块状格式上下文元素;
  • 父元素和第一个子元素之间没有inline元素分割;
  • 父元素没有border-top或padding-top设置。

父子元素重叠条件(margin-bottom)

  • 父元素非块状格式上下文元素;
  • 父元素没有border-bottom或padding-bottom设置;
  • 父元素和最后一个子元素之间没有inline元素分割;
  • 父元素没有height,min-height和max-height的限制。

空的block元素重叠

  • 元素没有border或padding或inline设置;
  • 没有height或者min-height设置。

第四步:有价值的细节。

重叠计算方法:正正取最大、负负取最小和正负相加。

在书写方向的垂直方向,margin:auto会自动分配剩余空间(剩余空间的意思简单的可以理解为:在没有设置宽之前的长度去掉你设置的宽余下的那段距离)。

绝对定位元素的非定位方向margin无效(貌似是的,不过描述不准确,其实一直有效,只不过现在只可以影响自己,无法改变兄弟了,因此看起来失效了)。

最后一个题外话,margin-collapse可以设置重叠方式(collapse默认,重叠、discard取消margin,等于margin:0和separate分隔,就是不发生重叠)。

查看原文

赞 1 收藏 0 评论 0

你好2007 发布了文章 · 2月20日

Transform + Transitions + Animation

Transform 转换

一些常用的属性:

  • transform: none | transform-functions;【通过设置该属性的值,我们可以对元素使用转换,具体的属性值在下面会专门介绍。】
  • transform-origin: x-axis y-axis z-axis;【设置元素转换的中心点,最直观的例子旋转图片,改变图片选择依赖的旋转中心。】
  • transform-style: flat | preserve-3d;【定义里面转换的元素是在2D平面呈现还是在3D空间呈现,讲的直白些,就是这个元素里面的空间维度是二维还是三维。】
  • perspective: number | none;【属性是定义3D元素距试图的距离,设置以后,其子元素会获得透视效果,需要注意的是该值只对3D转换有效,这也是很容易理解的。此外,还可以通过Transform的属性值的方式设置,二者是有一定区别的,你可以认为,前者是把整个看成一个舞台,后者是每一个都是一个舞台。】
  • perspective-origin: x-axis y-axis;【必须和perspective一起使用,只对3D转换元素有效,简单的理解就是你的眼睛看的焦点。】
  • backface-visibility:hidden | visible;【这个很简单,设置当元素背对着屏幕时候,是否是可见的。】

上面介绍的属性transform: none | transform-functions;其中有很多方法可以使用,具体的请查看文件API,这里没有列举出来的意义,其中perspective(n)方法是为3D转换元素定义透视视图,需要稍微留意一下,其中一些方法比较特殊,以后会单独去介绍。

Transitions 过渡

transition: property duration timing-function delay;

请始终设置 transition-duration 属性,否则时长为 0,就不会产生过渡效果。上面是统一设置,也可以分别设置各个属性。

一些常用的属性:

  • transition-property:规定设置过渡效果的 CSS 属性的名称;
  • transition-duration:规定完成过渡效果需要多少秒或毫秒;
  • transition-timing-function:规定速度效果的速度曲线;
  • transition-delay:定义过渡效果何时开始。

Animation 动画

animation: name duration timing-function delay iteration-count direction;

请始终规定 animation-duration 属性,否则时长为 0,就不会播放动画了。上面是统一设置,也可以分别设置各个属性。

一些常用的属性:

  • animation-name:规定需要绑定到选择器的 keyframe 名称;
  • animation-duration:规定完成动画所花费的时间,以秒或毫秒计;
  • animation-timing-function: 规定动画的速度曲线;
  • animation-delay:规定在动画开始之前的延迟;
  • animation-iteration-count:规定动画应该播放的次数;
  • animation-direction:规定是否应该轮流反向播放动画;
  • animation-fill-mode (可以设置为none | forwards | backwards | both):规定动画在播放之前或之后,其动画效果是否可见;
  • animation-play-state(可以设置为paused|running):规定动画正在运行还是暂停,可以在 JavaScript 中使用该属性,这样就能在播放过程中暂停动画;
  • @keyframes animName{from {} to {}}:定义动画名称为animName的动画关键帧。
查看原文

赞 2 收藏 1 评论 0

你好2007 发布了文章 · 2月20日

z-index 层叠上下文和层叠水平

第一步:基本概念。

层叠上下文是一个概念上的东西,学过编译原理的人应该对这里的上下文很清楚,而层叠不过就是一个词罢了,解释一下就是,根据层叠规则决定位置的一个环境。还需要注意的一点是,具有层叠上下文的元素比普通元素要更靠近眼睛。

层叠水平也是一个概念上的东西,用大白话说就是:在一个层叠上下文的环境下,里面的元素在z轴上的排列顺序的规则,而层叠顺序就是这里说的具体规则,是实践的东西。

第二步:层叠顺序。

需要记住的是,内部的层叠上下文及其子元素均受制于外部的层叠上下文,下面是层叠顺序,从远到近。

  • 层叠上下文background/border;
  • 负z-index;
  • block块状盒子模型;
  • float浮动盒子;
  • z-index为auto或看成0的不依赖z-index的上下文;
  • z-index为auto或看成0;
  • 正z-index。

第三步:产生条件。

根层叠上下文(指的是页面根元素,也就是 元素)。

定位元素与传统层叠上下文(使用了position:absolute、position:fixed或position:relative的定位元素,且z-index的值是数字会产生叠上下文)。

CSS3与新时代的层叠上下文:

  • 一个被设置了display:flex的元素包含的元素对其设置z-index为数值时其会产生层叠上下文;
  • 设置了opacity不为1的元素会产生层叠上下文;
  • 设置了transform不为none的元素会产生层叠上下文;
  • 设置了mix-blend-mode不为normal的元素会产生层叠上下文;
  • 设置了filter不为none的元素会产生层叠上下文;
  • 设置了isolation:isolate的元素会产生层叠上下文;
  • 设置了-webkit-overflow-scrolling的元素会产生层叠上下文(移动端);
  • 设置了will-change的元素会产生层叠上下文。
查看原文

赞 1 收藏 0 评论 0

你好2007 发布了文章 · 2月20日

vertical-align:垂直对齐方式相关说明

第一步:行内盒子模型。

为什么明明说的是垂直对齐方式,开始却要说盒子模型,还是行内盒子模型,因为垂直对齐方式控制的对象就是这个模型,因此我们先了解一下控制的环境,再看如何控制。

关于盒子模型相关的东西,我们会单独去仔细说明,因为非常重要,这里只是给出简单的说明,包括下面的行高等一样:

内容区域(content area):一种围绕着文字看不见的盒子,大小和font-size有关。

内联盒子(inline boxes):不好表达,举例子解释一下,被inline水平标签(em、a和span等)包含的称为"内联盒子",如果是光秃秃的文字就称为"匿名内联盒子"。

行框盒子(line boxes):一行就是一个行框盒子,里面由一个个内联盒子组成,应该不是太难理解。

包含盒子(containing box):由一行行的行框盒子组成,比如<p>单纯的文字被inline水平标签包含的文字</p>

行内盒子模型布局有一个问题,随便提一下:在使用display:inline-block使元素位于一行时元素之间有间隙是由于一些比如换行符等空白字符导致的(解决方法是对外壳设置font-size:0px,里面小格子再恢复font-size为需要的值,当然别的方法也有,比如标签全部一行,不要换行,不过这个感觉好用些)。

还有一个技巧,对于想整体居中,内部左对齐的布局,为了最后一行不足一行的时候不会突兀的居中,可以添加几行和内容元素一样宽,高为零的行内元素,非常好用。

此外,还有几个常用的属性,列举一下:

white-space:属性设置如何处理元素内的空白,比如是否换行等。

第二步:行高。

为什么行高也是要提前说明一下,因为垂直对齐是在一行中进行的,行高代表的是实际高,有必要了解一下,帮助去寻找对齐线条。

行高定义的是二行文字基线(baseline)的距离(不一定准确,更严格说应该是行框盒子的实际高度,不是高度),内容区域(content area)高度+行间距(vertical spacing)=行高,行高由于继承,无处不在,包括单行文本也不例外;其次,高度的表现不是行高而是内容区域高度+行间距,而且内容区域高度只与字号和字体有关,和行高没有任何关系。

简单的说就是:行高决定了内联盒子高度,行间距可大可小、可正可负,始终保证行高等于高。

内容区域高度不一定是font-size,还取决于字体,不同字体不一样,在simsum下二者是一样的。

line-height取值数字时所有可继承根据font-size重新计算,而取百分百和em时,当前元素根据font-size计算之后把计算的结果继承给下面元素,区别有点大。

替换元素比较特别,一般有内在尺寸和宽高比,不可以简单的去理解,遇到要小心,后期关于该元素会专门去讨论。

第三步:正文。

垂直对齐方式是用来设置垂直对齐方式,所有垂直对齐的元素都会影响行高,修改的是自己的什么线垂直对齐父元素的什么线,比如默认自己的基线对齐父元素的基线;如果取值百分百,计算是相对行高计算的。

根据前置知识你应该可以猜到,其是一个inline-block、table-cell(比如td元素)或inline依赖型元素。

如果设置为数值,百分百或em等单位,表示的是在默认基础上偏移的对齐。

第四步:注意点。

inline-block的基线是正常流中最后一个line box的基线,除非,这个line box里面既没有line boxes或者本身overflow属性的计算值而不是visible,这种情况下基线是margin底边缘。

查看原文

赞 1 收藏 0 评论 0

你好2007 发布了文章 · 2月20日

关于CSS中设置overflow属性的值为hidden的相关理解

包裹元素剪裁条件

是不是包裹元素overflow设置为hidden以后,内部元素如果超过包裹元素的话就会被剪裁?答案是不一定,这要看此刻的CSS环境。

根据对CSS2.1规范的理解,可以这样表述:如果一个元素的包含块的overflow属性设置为hidden,那么超过这个包含块部分的内容就会被剪裁。

因此我们需要学会寻找一个元素的包含块,不过在这之前,让我们先简单的知道如果一个元素的overflow属性设置为hidden,被其包裹的内容如果溢出需要同时满足的条件:

  • 拥有overflow:hidden样式的块元素不具有position:relative和position:absolute样式;
  • 内部溢出的元素是通过position:absolute绝对定位或position:fixed固定定位。

寻找包含块

什么是包含块?

根据CSS2.1规范里面的说明,简单的说就是:用来确定一个元素的盒子的位置和尺寸的矩形就叫这个元素的包含块。

寻找规则

因此包含块是一个非常有意义而且涉及属性很多的概念,至于如何确定一个元素的盒子的包含块是谁,遵循下面几条规则:

  • 根元素的包含块

其包含块又叫初始包含块,可以先认为就是可视区域(其实不准确,姑且这样说比较简单)。

  • 非根元素,且其position属性是relative和static的元素的包含块

它的包含块是由最近的祖先块容器盒的内容区域创建的。

  • 非根元素,且其position属性是absolute的元素的包含块

它的包含块由最近的position不为static的祖先元素创建(如果没有找到这样的祖先元素,这个绝对定位的元素的包含块为初始包含块);具体创建方法如下:

[1] 如果这个祖先元素是行内元素,包含块的范围是这个祖先元素中的第一个和最后一个行内盒的padding box围起来的区域;

[2] 如果这个祖先元素不是行内元素,包含块的范围是这个祖先元素的内边距+width区域。

为什么会清除浮动造成的影响

一个有趣的现象是:一个没有设置高度的父元素包裹一个子元素,父元素应该会被子元素撑起来,也就是有高了,不过如果你给子元素设置了浮动,显然原来父元素撑起来的高就塌了,到这里好像都很正常;接着,你给父元素加了一个overflow:hidden,发现父元素的高又有了,好像浮动被清除了一样,为什么会这样?

其实归根结底,这里的浮动没有清除,只是因为overflow属性的值是hidden的时候会形成一个BFC,而BFC就是一个隔离的渲染区域,因此浮动造成的高崩塌会导致对外部布局的影响,为了消除这种不益的表现,计算高度的时候浮动元素也计算进去了。

如果说到这里就停止,你可能会非常好奇,除了这里的情况还有什么情况会产生BFC,而BFC环境下具体会有哪些规则?接着下段来讲。

BFC(块级格式化上下文)

BFC是一个非常有用的环境,如果用一句话来说明它是什么样的存在:一个隔离的渲染区域,容器里面的子元素不会在布局上影响到外面的元素,反之也是如此。

如何触发BFC?

只需要保证满足下面至少一条就会触发BFC:

  • 根元素body;
  • 设置了float值不为none的元素;
  • 设置了overflow属性不为visible的元素;
  • 设置了属性position不为relative和static的元素;
  • 设置了display的值为flex、table-cell、table-caption和inline-block中的任何一个的元素。

可以看出来,就是一个比较独立的块,因为是一个独立的环境,在开发时候适度使用会有效降低开发和维护难度。

BFC环境下的特性或规则

大致可以归纳为三个方面:独立性、垂直分布规则和水平分布规则。

  • 独立性:BFC是页面上一个隔离的容器,和外面的关系是不会互相影响,对浮动而言也是,不会和浮动元素发生重叠,高也和上面提到的一样不会崩塌;
  • 垂直分布规则:BFC的内部里面的一个个盒子在垂直方向一个接着一个排放,位置由margin决定,两个相邻的盒子margin会发生重叠;
  • 水平分布规则:BFC里面的盒子的左外边缘和包含块的左边相接触,简单的理解就是水平方向不会发生margin重叠。
查看原文

赞 1 收藏 0 评论 0

你好2007 发布了文章 · 2月20日

Symbol + Generator函数 + Promise

一:Symbol

1.1 基本使用

简单的说,Symbol就是一个绝对唯一的key值,类似之前obj['key']='value'这种操作的字符串'key',好处是绝对不会重复,避免覆盖之前的值。

Symbol是一个方法,会返回一个唯一的symbol,可以带参数,比如:var sy=Symbol('sy-name'),不过这只是为了方便查看,加不加目前没有区别。

var sy1=Symbol(),sy2=Symbol();

console.log(sy1===sy2);//false

var obj={[sy1]:'这是数据一'};

obj[sy2]='这是数据二';

console.log(obj);//{Symbol(): "这是数据一", Symbol(): "这是数据二"}

1.2 有用的方法

  • Symbol.for():接受一个字符串作为参数,然后在全局搜索有没有以改字符串作为名称的Symbol值,如果有就返回,没有就新建一个返回。 注意:只有该方法建立的Symbol会登记在全局,Symbol()方法建立的不会登记在全局
var sy1 = Symbol.for(),sy2 = Symbol.for();

console.log(sy1 === sy2);//true

var sy3 = Symbol.for('info1'),sy4 = Symbol.for('info1');

console.log(sy3 === sy4);//true

var sy5=Symbol('info2'),sy6 = Symbol.for('info2');

console.log(sy3 === sy6);//false

console.log(sy5 === sy6);//false
  • Symbol.keyFor()

该方法是配合上面的方法来使用的,接受一个Symbol作为参数,会在全局搜索该Symbol对象,返回该对象名称,如果没有就返回undefined。

二:Generator函数

2.1 基本使用

定义函数名称时带一个*的函数就是Generator函数,内部的yield语句是特色,该函数的使用可以用下面的几条概括一下:

  • 调用Generator函数并不会立刻return结果,而是在遇到第一个yield语句前停止下来,返回一个指针(姑且这样称呼);
  • 以后每次调用yield时候(具体看下面例子,直到遇到yield或return时停止继续执行),会返回yield数据,有点类似return;
  • 返回的数据格式从下面的例子应该就明白了,没有yield或return语句会发生什么应该也明白了。
function *firstYieldFun(){

    yield '你好';

    yield '2007';

    return 'name:';

}

var fun=firstYieldFun();

console.log(fun.next());//{value: "你好", done: false}

console.log(fun.next());//{value: "2007", done: false}

console.log(fun.next());//{value: "name:", done: true}

console.log(fun.next());//{value: undefined, done: true}

console.log(fun.next());//{value: undefined, done: true}

简单的说,Generator函数就是一个分段执行的函数,走走停停,yield用来切割代码成一段段的,next()方法用来启动执行下一段这个行为。

forEach方法的参数是一个普通函数,Generator函数不可以作为参数。

2.2 重要的说明

  • yield特殊使用

除了上面的例子yield单独成为一个语句,其还可以用于表达式,函数参数和赋值表达式的右边等。

需要注意的是,yield要明确归属,用小括号包裹,小括号不是必须的,是在归属关系不明确的时候才是必须的,例如:console.log('My name is : '+(yield '你好2007'))

  • next()方法带参数时

yield本身不会返回值,或者说是undefined,不过next()方法如果带参数情况就不一样了,此时就会返回yield带的参数,如下例子说明:

function* secondYieldFun() {

    console.log(yield '你好2007');

}

var fun = secondYieldFun();

console.log(fun.next()); //{value: "你好2007", done: false}

//这是参数
console.log(fun.next('这是参数')); //{value: undefined, done: true}

三:Promise

3.1 基本使用

Promise就是一个对象,有点类似注册事件的感觉,不过又不一样,你提前注册好成功和失败以后应该走的路径,然后你自己根据实际情况决定是失败还是成功,其实和回调没有本质的区别,就是写起来好像好看了些,下面的例子很明了。

var promise1 = new Promise(function(resolve, reject) {

    setTimeout(function() {

        if (false) {

            //Pending --> Resolved
            resolve('成功了');

        } else {

            //Pending --> Rejected
            reject('失败了');

        }

    }, 5000);
});

promise1.then(function(value) {

    console.log('成功:' + value);

}, function(error) {

    console.log('失败:' + error);

});

需要说明一下的是,Promise对象保证着三种状态:pending、Resolved和Rejected,就是进行中、成功和失败的意思。

3.2 Promise.race()和Promise.all()

将多个Promise实例,包装成一个新的Promise实例。 下面介绍的二个方法的参数如果不是对象的实例,就会先调用Promise.resolve方法变成对象的实例再传递进去。

let pro=Promise.race(Promise对象的实例1, Promise对象的实例2 [,Promise对象的实例N])

只要promise对象的实例中有一个率先改变,率先改变的那个的返回状态就会作为pro的状态返回,余下的会继续执行完毕但不会改变状态了。

let pro=Promise.all(Promise对象的实例1, Promise对象的实例2 [,Promise对象的实例N])

结果只有二种情况:

  • 全部成功时,会等待全部执行结束,返回成功;
  • 存在至少一个失败时,会在遇见第一个失败时候返回失败,余下的会继续执行完毕但不会改变状态了。
var pro1 = new Promise((resolve, reject) => setTimeout(() => resolve('第一条'), 3000));

var pro2 = new Promise((resolve, reject) => setTimeout(() => resolve('第二条'), 1000));

var proAll = Promise.all([pro1, pro2]);

proAll.then(val => console.log(val)); // ["第一条", "第二条"]
查看原文

赞 1 收藏 0 评论 0

你好2007 发布了文章 · 2月20日

二进制数据(ArrayBuffer + TypedArray + DataView)

简单的说,ArrayBuffer就代表了内存中的一段二进制数据,不可以直接读写,只可以通过在上面建立TypedArray视图或DataView视图来操作这段二进制数据,TypedArray视图主要用来操作简单类型的二进制数据,DataView用来操作复杂类型的二进制数据。

第一步:ArrayBuffer

new ArrayBuffer(length) //length代表长度,单位是字节

建立一个ArrayBuffer就是这么简单,当然也可以有其它数据或对象转换而来,这里先不提,如此的一个对象,再借助下面将介绍的二个视图就可以好好利用了,不过再此之前,还是先列举一下几个会有用的方法和属性:

  • ArrayBuffer实例对象上有一个属性byteLength,可以知道实例对象的长度;
  • 实例对象上的slice(起点[,终点])方法会把原来实例对象上指定的这段数据复制新建一个新的实例对象返回;
  • ArrayBuffer对象上有一个方法ArrayBuffer.isView(参数),可以用来判断传递进去的参数ArrayBuffer的实例对象。

第二步:TypedArray

视图对象种类

TypedArray是用来操作简单类型的视图,一个视图对应一个确定的类型,并且是连续的,默认为0。

该视图支持的类型如下:

  • Int8Array:8位有符号整数,长度1个字节。
  • Uint8Array:8位无符号整数,长度1个字节。
  • Uint8ClampedArray:8位无符号整数,长度1个字节,溢出处理不同。
  • Int16Array:16位有符号整数,长度2个字节。
  • Uint16Array:16位无符号整数,长度2个字节。
  • Int32Array:32位有符号整数,长度4个字节。
  • Uint32Array:32位无符号整数,长度4个字节。
  • Float32Array:32位浮点数,长度4个字节。
  • Float64Array:64位浮点数,长度8个字节。

建立视图对象

每种类型都有一个构造函数,用来生成对应的视图,因此其实TypedArray其实是这些视图为了方便的一个统一称呼。

上面提到的每个构造函数传递的参数有很多中,下面列举常用的四种:

  • TypedArray(ArrayBuffer实例对象, byteOffset=0, length?)
  • TypedArray(length)
  • TypedArray(typedArray)
  • TypedArray(普通的数组)

视图对象操作数据

除了个别例外(比如concat方法),视图对象的操作和普通数组的操作基本差不多,这里给出一个例子:

var arrayBuffer = new ArrayBuffer(6);//申请6个字节的内存空间
var int8Array = new Int8Array(arrayBuffer, 0, 2);//使用了2字节的空间
var int16Array = new Int16Array(arrayBuffer, 2, 2);//使用了4字节的空间
int8Array[0] = 1;
int16Array[0] = 2;
int16Array[1] = 3;
console.log(int8Array); //Int8Array(2) [1, 0]
console.log(int16Array); //Int16Array(2) [2, 3]

有用的说明

  • TypedArray实例对象的buffer属性会返回对应的ArrayBuffer对象,只读;
  • TypedArray实例对象的byteLength属性返回对象占据的内存字节数(注意这里和length属性不一样,后者是成员个数),只读;
  • TypedArray实例对象的byteOffset属性返回对象从对应的ArrayBuffer对象的那个字节开始建立的,只读。

第三步:DataView

new DataView(ArrayBuffer实例对象[,字节起始位置[,长度]])

和TypedArray有很大区别的是,这里我们在使用的时候可能要去关注一下大端还是小端保存或读取数据。

视图数据操作

如果说的简单点,其实DataView就是一个非常非常厉害视图,提供了很多方法,不像TypedArray视图需要建立对应视图然后读取,你可以调用DataView的实例对象上的方法就可以实现TypedArray哪些各种视图的功能,因此,你可能会涉及到这些方法:

读数据的方法包括:getInt8、getUint8、getInt16、getUint16、getInt32、getUint32、getFloat32、getFloat64。

带二个参数,第一个参数表示读取的开始位置,第二个参数表示是大端读取(false)还是小端读取(true)。

写数据的方法包括:setInt8、setUint8、setInt16、setUint16、setInt32、setUint32、setFloat32、setFloat64。

带三个参数,第一个参数表示写入的开始位置,第二个参数表示写入的数据,第三个参数表示是大端写入(false)还是小端写入(true)。

有用的说明

  • DataView实例对象的buffer属性会返回对应的ArrayBuffer对象,只读;
  • DataView实例对象的byteLength属性返回对象占据的内存字节数(注意这里和length属性不一样,后者是成员个数),只读;
  • DataView实例对象的byteOffset属性返回对象从对应的ArrayBuffer对象的那个字节开始建立的,只读。
查看原文

赞 2 收藏 1 评论 0

认证与成就

  • 获得 18 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2020-11-04
个人主页被 1k 人浏览