Git Product home page Git Product logo

cv's Introduction

cv's People

Contributors

monkeywithacupcake avatar wscats avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cv's Issues

day4

01函数的声明方式

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>函数声明</title>
	<script>
	var sum
		/* 
			函数声明提前
			1.查找js中定义的函数,并保存起来(变量声明)
			2.从上往执行代码
			只声明变量不赋值,则变量为undefined
		*/
		// 在函数声明前执行这个函数
		check();
		// 1.函数声明
		// 可以在任意位置执行
		function check(){
			console.log('666');
		}
		// 2.赋值式(变量声明)
		// 不能在函数赋值前执行
		var sum = function(){
			var a = 10;
			var b = 20;
			console.log(a + b);
		}
		sum();//30
		// 3. 构造函数
		var test = new Function();
		
		// 函数的执行
		// 格式:函数名()
		// check();
		console.log(myName);
		var myName = 'laoxie';
		
	</script>
</head>
<body>

</body>
</html>

给自己点时间再记记这200条Git命令

我平时使用 Git 的时候,很多的 Git 命令我都不是很常用,工作中一般我们会配合一些可视化工具,或者编辑器自带的一些插件去维护 Git 仓库,但是我们也要记得一些常用 Git 命令来应变一些特殊的场景,下面是我收录整理的常用和不常用的一些 Git 命令,希望能帮助到大家更好的掌握 Git 的使用,如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的肯定是我前进的最大动力😁

新建

创建一个新的 git 版本库。这个版本库的配置、存储等信息会被保存到.git 文件夹中

# 初始化当前项目
$ git init

# 新建一个目录,将其初始化为Git代码库
$ git init [project-name]

# 在指定目录创建一个空的 Git 仓库。运行这个命令会创建一个名为 directory,只包含 .git 子目录的空目录。

$ git init --bare <directory>

# 下载一个项目和它的整个代码历史
# 这个命令就是将一个版本库拷贝到另一个目录中,同时也将分支都拷贝到新的版本库中。这样就可以在新的版本库中提交到远程分支
$ git clone [url]

配置

更改设置。可以是版本库的设置,也可以是系统的或全局的

# 显示当前的Git配置
$ git config --list

# 编辑Git配置文件
$ git config -e [--global]

# 输出、设置基本的全局变量
$ git config --global user.email
$ git config --global user.name

$ git config --global user.email "[email protected]"
$ git config --global user.name "My Name"

# 定义当前用户所有提交使用的作者邮箱。
$ git config --global alias.<alias-name> <git-command>

# 为Git命令创建一个快捷方式(别名)。
$ git config --system core.editor <editor>

帮助

git 内置了对命令非常详细的解释,可以供我们快速查阅

# 查找可用命令
$ git help

# 查找所有可用命令
$ git help -a

# 在文档当中查找特定的命令
# git help <命令>
$ git help add
$ git help commit
$ git help init

状态

显示索引文件(也就是当前工作空间)和当前的头指针指向的提交的不同

# 显示分支,未跟踪文件,更改和其他不同
$ git status

# 查看其他的git status的用法
$ git help status

信息

获取某些文件,某些分支,某次提交等 git 信息

# 显示commit历史,以及每次commit发生变更的文件
$ git log --stat

# 搜索提交历史,根据关键词
$ git log -S [keyword]

# 显示某个commit之后的所有变动,每个commit占据一行
$ git log [tag] HEAD --pretty=format:%s

# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件
$ git log [tag] HEAD --grep feature

# 显示某个文件的版本历史,包括文件改名
$ git log --follow [file]
$ git whatchanged [file]

# 显示指定文件相关的每一次diff
$ git log -p [file]

# 显示过去5次提交
$ git log -5 --pretty --oneline

# 显示所有提交过的用户,按提交次数排序
$ git shortlog -sn

# 显示指定文件是什么人在什么时间修改过
$ git blame [file]

# 显示暂存区和工作区的差异
$ git diff

# 显示暂存区和上一个commit的差异
$ git diff --cached [file]

# 显示工作区与当前分支最新commit之间的差异
$ git diff HEAD

# 显示两次提交之间的差异
$ git diff [first-branch]...[second-branch]

# 显示今天你写了多少行代码
$ git diff --shortstat "@{0 day ago}"

# 比较暂存区和版本库差异
$ git diff --staged

# 比较暂存区和版本库差异
$ git diff --cached

# 仅仅比较统计信息
$ git diff --stat

# 显示某次提交的元数据和内容变化
$ git show [commit]

# 显示某次提交发生变化的文件
$ git show --name-only [commit]

# 显示某次提交时,某个文件的内容
$ git show [commit]:[filename]

# 显示当前分支的最近几次提交
$ git reflog

# 查看远程分支
$ git br -r

# 创建新的分支
$ git br <new_branch>

# 查看各个分支最后提交信息
$ git br -v

# 查看已经被合并到当前分支的分支
$ git br --merged

# 查看尚未被合并到当前分支的分支
$ git br --no-merged

添加

添加文件到当前工作空间中。如果你不使用 git add 将文件添加进去,那么这些文件也不会添加到之后的提交之中

# 添加一个文件
$ git add test.js

# 添加一个子目录中的文件
$ git add /path/to/file/test.js

# 支持正则表达式
$ git add ./*.js

# 添加指定文件到暂存区
$ git add [file1] [file2] ...

# 添加指定目录到暂存区,包括子目录
$ git add [dir]

# 添加当前目录的所有文件到暂存区
$ git add .

# 添加每个变化前,都会要求确认
# 对于同一个文件的多处变化,可以实现分次提交
$ git add -p

删除

rm 和上面的 add 命令相反,从工作空间中去掉某个文件

# 移除 HelloWorld.js
$ git rm HelloWorld.js

# 移除子目录中的文件
$ git rm /pather/to/the/file/HelloWorld.js

# 删除工作区文件,并且将这次删除放入暂存区
$ git rm [file1] [file2] ...

# 停止追踪指定文件,但该文件会保留在工作区
$ git rm --cached [file]

分支

管理分支,可以通过下列命令对分支进行增删改查切换等

# 查看所有的分支和远程分支
$ git branch -a

# 创建一个新的分支
$ git branch [branch-name]

# 重命名分支
# git branch -m <旧名称> <新名称>
$ git branch -m [branch-name] [new-branch-name]

# 编辑分支的介绍
$ git branch [branch-name] --edit-description

# 列出所有本地分支
$ git branch

# 列出所有远程分支
$ git branch -r

# 新建一个分支,但依然停留在当前分支
$ git branch [branch-name]

# 新建一个分支,并切换到该分支
$ git checkout -b [branch]

# 新建一个分支,指向指定commit
$ git branch [branch] [commit]

# 新建一个分支,与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch]

# 切换到指定分支,并更新工作区
$ git checkout [branch-name]

# 切换到上一个分支
$ git checkout -

# 建立追踪关系,在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch]

# 合并指定分支到当前分支
$ git merge [branch]

# 选择一个commit,合并进当前分支
$ git cherry-pick [commit]

# 删除分支
$ git branch -d [branch-name]

# 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]

# 切换到某个分支
$ git co <branch>

# 创建新的分支,并且切换过去
$ git co -b <new_branch>

# 基于branch创建新的new_branch
$ git co -b <new_branch> <branch>

# 把某次历史提交记录checkout出来,但无分支信息,切换到其他分支会自动删除
$ git co $id

# 把某次历史提交记录checkout出来,创建成一个分支
$ git co $id -b <new_branch>

# 删除某个分支
$ git br -d <branch>

# 强制删除某个分支 (未被合并的分支被删除的时候需要强制)
$ git br -D <branch>

检出

将当前工作空间更新到索引所标识的或者某一特定的工作空间

# 检出一个版本库,默认将更新到master分支
$ git checkout
# 检出到一个特定的分支
$ git checkout branchName
# 新建一个分支,并且切换过去,相当于"git branch <名字>; git checkout <名字>"
$ git checkout -b newBranch

远程同步

远程同步的远端分支

# 下载远程仓库的所有变动
$ git fetch [remote]

# 显示所有远程仓库
$ git remote -v

# 显示某个远程仓库的信息
$ git remote show [remote]

# 增加一个新的远程仓库,并命名
$ git remote add [shortname] [url]

# 查看远程服务器地址和仓库名称
$ git remote -v

# 添加远程仓库地址
$ git remote add origin git@ github:xxx/xxx.git

# 设置远程仓库地址(用于修改远程仓库地址)
$ git remote set-url origin git@ github.com:xxx/xxx.git

# 删除远程仓库
$ git remote rm <repository>

# 上传本地指定分支到远程仓库
# 把本地的分支更新到远端origin的master分支上
# git push <远端> <分支>
# git push 相当于 git push origin master
$ git push [remote] [branch]

# 强行推送当前分支到远程仓库,即使有冲突
$ git push [remote] --force

# 推送所有分支到远程仓库
$ git push [remote] --all

撤销

# 恢复暂存区的指定文件到工作区
$ git checkout [file]

# 恢复某个commit的指定文件到暂存区和工作区
$ git checkout [commit] [file]

# 恢复暂存区的所有文件到工作区
$ git checkout .

# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
$ git reset [file]

# 重置暂存区与工作区,与上一次commit保持一致
$ git reset --hard

# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
$ git reset [commit]

# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
$ git reset --hard [commit]

# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
$ git reset --keep [commit]

# 新建一个commit,用来撤销指定commit
# 后者的所有变化都将被前者抵消,并且应用到当前分支
$ git revert [commit]

# 恢复最后一次提交的状态
$ git revert HEAD

# 暂时将未提交的变化移除,稍后再移入
$ git stash
$ git stash pop

# 列所有stash
$ git stash list

# 恢复暂存的内容
$ git stash apply

# 删除暂存区
$ git stash drop

commit

将当前索引的更改保存为一个新的提交,这个提交包括用户做出的更改与信息

# 提交暂存区到仓库区附带提交信息
$ git commit -m [message]

# 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]

# 提交工作区自上次commit之后的变化,直接到仓库区
$ git commit -a

# 提交时显示所有diff信息
$ git commit -v

# 使用一次新的commit,替代上一次提交
# 如果代码没有任何新变化,则用来改写上一次commit的提交信息
$ git commit --amend -m [message]

# 重做上一次commit,并包括指定文件的新变化
$ git commit --amend [file1] [file2] ...

diff

显示当前工作空间和提交的不同

# 显示工作目录和索引的不同
$ git diff

# 显示索引和最近一次提交的不同
$ git diff --cached

# 显示工作目录和最近一次提交的不同
$ git diff HEAD

grep

可以在版本库中快速查找

可选配置:

# 感谢Travis Jeffery提供的以下用法:
# 在搜索结果中显示行号
$ git config --global grep.lineNumber true

# 是搜索结果可读性更好
$ git config --global alias.g "grep --break --heading --line-number"
# 在所有的java中查找variableName
$ git grep 'variableName' -- '*.java'

# 搜索包含 "arrayListName" 和, "add" 或 "remove" 的所有行
$ git grep -e 'arrayListName' --and \( -e add -e remove \)

log

显示这个版本库的所有提交

# 显示所有提交
$ git log

# 显示某几条提交信息
$ git log -n 10

# 仅显示合并提交
$ git log --merges

# 查看该文件每次提交记录
$ git log <file>

# 查看每次详细修改内容的diff
$ git log -p <file>

# 查看最近两次详细修改内容的diff
$ git log -p -2

#查看提交统计信息
$ git log --stat

merge

合并就是将外部的提交合并到自己的分支中

# 将其他分支合并到当前分支
$ git merge branchName

# 在合并时创建一个新的合并后的提交
# 不要 Fast-Foward 合并,这样可以生成 merge 提交
$ git merge --no-ff branchName

mv

重命名或移动一个文件

# 重命名
$ git mv test.js test2.js

# 移动
$ git mv test.js ./new/path/test.js

# 改名文件,并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]

# 强制重命名或移动
# 这个文件已经存在,将要覆盖掉
$ git mv -f myFile existingFile

tag

# 列出所有tag
$ git tag

# 新建一个tag在当前commit
$ git tag [tag]

# 新建一个tag在指定commit
$ git tag [tag] [commit]

# 删除本地tag
$ git tag -d [tag]

# 删除远程tag
$ git push origin :refs/tags/[tagName]

# 查看tag信息
$ git show [tag]

# 提交指定tag
$ git push [remote] [tag]

# 提交所有tag
$ git push [remote] --tags

# 新建一个分支,指向某个tag
$ git checkout -b [branch] [tag]

pull

从远端版本库合并到当前分支

# 从远端origin的master分支更新版本库
# git pull <远端> <分支>
$ git pull origin master

# 抓取远程仓库所有分支更新并合并到本地,不要快进合并
$ git pull --no-ff

ci

$ git ci <file>
$ git ci .
# 将git add, git rm和git ci等操作都合并在一起做
$ git ci -a
$ git ci -am "some comments"
# 修改最后一次提交记录
$ git ci --amend

rebase (谨慎使用)

将一个分支上所有的提交历史都应用到另一个分支上
不要在一个已经公开的远端分支上使用 rebase.

# 将experimentBranch应用到master上面
# git rebase <basebranch> <topicbranch>
$ git rebase master experimentBranch

reset (谨慎使用)

将当前的头指针复位到一个特定的状态。这样可以使你撤销 merge、pull、commits、add 等
这是个很强大的命令,但是在使用时一定要清楚其所产生的后果

# 使 staging 区域恢复到上次提交时的状态,不改变现在的工作目录
$ git reset

# 使 staging 区域恢复到上次提交时的状态,覆盖现在的工作目录
$ git reset --hard

# 将当前分支恢复到某次提交,不改变现在的工作目录
# 在工作目录中所有的改变仍然存在
$ git reset dha78as

# 将当前分支恢复到某次提交,覆盖现在的工作目录
# 并且删除所有未提交的改变和指定提交之后的所有提交
$ git reset --hard dha78as

其他

# 生成一个可供发布的压缩包
$ git archive

# 打补丁
$ git apply ../sync.patch

# 测试补丁能否成功
$ git apply --check ../sync.patch

# 查看Git的版本
$ git --version

参考文档

day3

var i = 0;
function drawTable() {
	document.write("<tr><th>123</th><th>123</th></tr>");
	i++
	if(i <= 10) {
		drawTable()
	}
}
drawTable()

JavaScript工具函数大全(持续更新)

为元素添加on方法

Element.prototype.on = Element.prototype.addEventListener;

NodeList.prototype.on = function (event, fn) {
    []['forEach'].call(this, function (el) {
        el.on(event, fn);
    });
    return this;
};

为元素添加trigger方法

Element.prototype.trigger = function(type, data) {
  var event = document.createEvent("HTMLEvents");
  event.initEvent(type, true, true);
  event.data = data || {};
  event.eventName = type;
  event.target = this;
  this.dispatchEvent(event);
  return this;
};

NodeList.prototype.trigger = function(event) {
  []["forEach"].call(this, function(el) {
    el["trigger"](event);
  });
  return this;
};

转义html标签

function HtmlEncode(text) {
  return text
    .replace(/&/g, "&")
    .replace(/\"/g, '"')
    .replace(/</g, "<")
    .replace(/>/g, ">");
}

HTML标签转义

// HTML 标签转义
// @param {Array.<DOMString>} templateData 字符串类型的tokens
// @param {...} ..vals 表达式占位符的运算结果tokens
//
function SaferHTML(templateData) {
  var s = templateData[0];
  for (var i = 1; i < arguments.length; i++) {
    var arg = String(arguments[i]);
    // Escape special characters in the substitution.
    s += arg
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;");
    // Don't escape special characters in the template.
    s += templateData[i];
  }
  return s;
}
// 调用
var html = SaferHTML`<p>这是关于字符串模板的介绍</p>`;

跨浏览器绑定事件

function addEventSamp(obj, evt, fn) {
  if (!oTarget) {
    return;
  }
  if (obj.addEventListener) {
    obj.addEventListener(evt, fn, false);
  } else if (obj.attachEvent) {
    obj.attachEvent("on" + evt, fn);
  } else {
    oTarget["on" + sEvtType] = fn;
  }
}

加入收藏夹

function addFavorite(sURL, sTitle) {
  try {
    window.external.addFavorite(sURL, sTitle);
  } catch (e) {
    try {
      window.sidebar.addPanel(sTitle, sURL, "");
    } catch (e) {
      alert("加入收藏失败,请使用Ctrl+D进行添加");
    }
  }
}

提取页面代码中所有网址

var aa = document.documentElement.outerHTML
  .match(
    /(url\(|src=|href=)[\"\']*([^\"\'\(\)\<\>\[\] ]+)[\"\'\)]*|(http:\/\/[\w\-\.]+[^\"\'\(\)\<\>\[\] ]+)/gi
  )
  .join("\r\n")
  .replace(/^(src=|href=|url\()[\"\']*|[\"\'\>\) ]*$/gim, "");
alert(aa);

动态加载脚本文件

function appendscript(src, text, reload, charset) {
  var id = hash(src + text);
  if (!reload && in_array(id, evalscripts)) return;
  if (reload && $(id)) {
    $(id).parentNode.removeChild($(id));
  }

  evalscripts.push(id);
  var scriptNode = document.createElement("script");
  scriptNode.type = "text/javascript";
  scriptNode.id = id;
  scriptNode.charset = charset
    ? charset
    : BROWSER.firefox
    ? document.characterSet
    : document.charset;
  try {
    if (src) {
      scriptNode.src = src;
      scriptNode.onloadDone = false;
      scriptNode.onload = function() {
        scriptNode.onloadDone = true;
        JSLOADED[src] = 1;
      };
      scriptNode.onreadystatechange = function() {
        if (
          (scriptNode.readyState == "loaded" ||
            scriptNode.readyState == "complete") &&
          !scriptNode.onloadDone
        ) {
          scriptNode.onloadDone = true;
          JSLOADED[src] = 1;
        }
      };
    } else if (text) {
      scriptNode.text = text;
    }
    document.getElementsByTagName("head")[0].appendChild(scriptNode);
  } catch (e) {}
}

返回顶部的通用方法

function backTop(btnId) {
  var btn = document.getElementById(btnId);
  var d = document.documentElement;
  var b = document.body;
  window.onscroll = set;
  btn.style.display = "none";
  btn.onclick = function() {
    btn.style.display = "none";
    window.onscroll = null;
    this.timer = setInterval(function() {
      d.scrollTop -= Math.ceil((d.scrollTop + b.scrollTop) * 0.1);
      b.scrollTop -= Math.ceil((d.scrollTop + b.scrollTop) * 0.1);
      if (d.scrollTop + b.scrollTop == 0)
        clearInterval(btn.timer, (window.onscroll = set));
    }, 10);
  };
  function set() {
    btn.style.display = d.scrollTop + b.scrollTop > 100 ? "block" : "none";
  }
}
backTop("goTop");

实现base64解码

function base64_decode(data) {
  var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  var o1,
    o2,
    o3,
    h1,
    h2,
    h3,
    h4,
    bits,
    i = 0,
    ac = 0,
    dec = "",
    tmp_arr = [];
  if (!data) {
    return data;
  }
  data += "";
  do {
    h1 = b64.indexOf(data.charAt(i++));
    h2 = b64.indexOf(data.charAt(i++));
    h3 = b64.indexOf(data.charAt(i++));
    h4 = b64.indexOf(data.charAt(i++));
    bits = (h1 << 18) | (h2 << 12) | (h3 << 6) | h4;
    o1 = (bits >> 16) & 0xff;
    o2 = (bits >> 8) & 0xff;
    o3 = bits & 0xff;
    if (h3 == 64) {
      tmp_arr[ac++] = String.fromCharCode(o1);
    } else if (h4 == 64) {
      tmp_arr[ac++] = String.fromCharCode(o1, o2);
    } else {
      tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
    }
  } while (i < data.length);
  dec = tmp_arr.join("");
  dec = utf8_decode(dec);
  return dec;
}

确认是否是键盘有效输入值

function checkKey(iKey) {
  if (iKey == 32 || iKey == 229) {
    return true;
  } /*空格和异常*/
  if (iKey > 47 && iKey < 58) {
    return true;
  } /*数字*/
  if (iKey > 64 && iKey < 91) {
    return true;
  } /*字母*/
  if (iKey > 95 && iKey < 108) {
    return true;
  } /*数字键盘1*/
  if (iKey > 108 && iKey < 112) {
    return true;
  } /*数字键盘2*/
  if (iKey > 185 && iKey < 193) {
    return true;
  } /*符号1*/
  if (iKey > 218 && iKey < 223) {
    return true;
  } /*符号2*/
  return false;
}

全角半角转换

//iCase: 0全到半,1半到全,其他不转化
function chgCase(sStr, iCase) {
  if (
    typeof sStr != "string" ||
    sStr.length <= 0 ||
    !(iCase === 0 || iCase == 1)
  ) {
    return sStr;
  }
  var i,
    oRs = [],
    iCode;
  if (iCase) {
    /*半->全*/
    for (i = 0; i < sStr.length; i += 1) {
      iCode = sStr.charCodeAt(i);
      if (iCode == 32) {
        iCode = 12288;
      } else if (iCode < 127) {
        iCode += 65248;
      }
      oRs.push(String.fromCharCode(iCode));
    }
  } else {
    /*全->半*/
    for (i = 0; i < sStr.length; i += 1) {
      iCode = sStr.charCodeAt(i);
      if (iCode == 12288) {
        iCode = 32;
      } else if (iCode > 65280 && iCode < 65375) {
        iCode -= 65248;
      }
      oRs.push(String.fromCharCode(iCode));
    }
  }
  return oRs.join("");
}

版本对比

function compareVersion(v1, v2) {
  v1 = v1.split(".");
  v2 = v2.split(".");

  var len = Math.max(v1.length, v2.length);

  while (v1.length < len) {
    v1.push("0");
  }

  while (v2.length < len) {
    v2.push("0");
  }

  for (var i = 0; i < len; i++) {
    var num1 = parseInt(v1[i]);
    var num2 = parseInt(v2[i]);

    if (num1 > num2) {
      return 1;
    } else if (num1 < num2) {
      return -1;
    }
  }
  return 0;
}

压缩CSS样式代码

function compressCss(s) {
  //压缩代码
  s = s.replace(/\/\*(.|\n)*?\*\//g, ""); //删除注释
  s = s.replace(/\s*([\{\}\:\;\,])\s*/g, "$1");
  s = s.replace(/\,[\s\.\#\d]*\{/g, "{"); //容错处理
  s = s.replace(/;\s*;/g, ";"); //清除连续分号
  s = s.match(/^\s*(\S+(\s+\S+)*)\s*$/); //去掉首尾空白
  return s == null ? "" : s[1];
}

获取当前路径

var currentPageUrl = "";
if (typeof this.href === "undefined") {
  currentPageUrl = document.location.toString().toLowerCase();
} else {
  currentPageUrl = this.href.toString().toLowerCase();
}

字符串长度截取

function cutstr(str, len) {
    var temp,
        icount = 0,
        patrn = /[^\x00-\xff]/
        strre = "";
    for (var i = 0; i < str.length; i++) {
        if (icount < len - 1) {
            temp = str.substr(i, 1);
                if (patrn.exec(temp) == null) {
                   icount = icount + 1
            } else {
                icount = icount + 2
            }
            strre += temp
            } else {
            break;
        }
    }
    return strre + "..."
}

时间日期格式转换

Date.prototype.format = function(formatStr) {
  var str = formatStr;
  var Week = ["日", "一", "二", "三", "四", "五", "六"];
  str = str.replace(/yyyy|YYYY/, this.getFullYear());
  str = str.replace(
    /yy|YY/,
    this.getYear() % 100 > 9
      ? (this.getYear() % 100).toString()
      : "0" + (this.getYear() % 100)
  );
  str = str.replace(
    /MM/,
    this.getMonth() + 1 > 9
      ? (this.getMonth() + 1).toString()
      : "0" + (this.getMonth() + 1)
  );
  str = str.replace(/M/g, this.getMonth() + 1);
  str = str.replace(/w|W/g, Week[this.getDay()]);
  str = str.replace(
    /dd|DD/,
    this.getDate() > 9 ? this.getDate().toString() : "0" + this.getDate()
  );
  str = str.replace(/d|D/g, this.getDate());
  str = str.replace(
    /hh|HH/,
    this.getHours() > 9 ? this.getHours().toString() : "0" + this.getHours()
  );
  str = str.replace(/h|H/g, this.getHours());
  str = str.replace(
    /mm/,
    this.getMinutes() > 9
      ? this.getMinutes().toString()
      : "0" + this.getMinutes()
  );
  str = str.replace(/m/g, this.getMinutes());
  str = str.replace(
    /ss|SS/,
    this.getSeconds() > 9
      ? this.getSeconds().toString()
      : "0" + this.getSeconds()
  );
  str = str.replace(/s|S/g, this.getSeconds());
  return str;
};

// 或
Date.prototype.format = function(format) {
  var o = {
    "M+": this.getMonth() + 1, //month
    "d+": this.getDate(), //day
    "h+": this.getHours(), //hour
    "m+": this.getMinutes(), //minute
    "s+": this.getSeconds(), //second
    "q+": Math.floor((this.getMonth() + 3) / 3), //quarter
    S: this.getMilliseconds() //millisecond
  };
  if (/(y+)/.test(format))
    format = format.replace(
      RegExp.$1,
      (this.getFullYear() + "").substr(4 - RegExp.$1.length)
    );
  for (var k in o) {
    if (new RegExp("(" + k + ")").test(format))
      format = format.replace(
        RegExp.$1,
        RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)
      );
  }
  return format;
};
alert(new Date().format("yyyy-MM-dd hh:mm:ss"));

跨浏览器删除事件

function delEvt(obj, evt, fn) {
  if (!obj) {
    return;
  }
  if (obj.addEventListener) {
    obj.addEventListener(evt, fn, false);
  } else if (oTarget.attachEvent) {
    obj.attachEvent("on" + evt, fn);
  } else {
    obj["on" + evt] = fn;
  }
}

判断是否以某个字符串结束

String.prototype.endWith = function(s) {
  var d = this.length - s.length;
  return d >= 0 && this.lastIndexOf(s) == d;
};

返回脚本内容

function evalscript(s) {
  if (s.indexOf("<script") == -1) return s;
  var p = /<script[^\>]*?>([^\x00]*?)<\/script>/gi;
  var arr = [];
  while ((arr = p.exec(s))) {
    var p1 = /<script[^\>]*?src=\"([^\>]*?)\"[^\>]*?(reload=\"1\")?(?:charset=\"([\w\-]+?)\")?><\/script>/i;
    var arr1 = [];
    arr1 = p1.exec(arr[0]);
    if (arr1) {
      appendscript(arr1[1], "", arr1[2], arr1[3]);
    } else {
      p1 = /<script(.*?)>([^\x00]+?)<\/script>/i;
      arr1 = p1.exec(arr[0]);
      appendscript("", arr1[2], arr1[1].indexOf("reload=") != -1);
    }
  }
  return s;
}

格式化CSS样式代码

function formatCss(s) {
  //格式化代码
  s = s.replace(/\s*([\{\}\:\;\,])\s*/g, "$1");
  s = s.replace(/;\s*;/g, ";"); //清除连续分号
  s = s.replace(/\,[\s\.\#\d]*{/g, "{");
  s = s.replace(/([^\s])\{([^\s])/g, "$1 {\n\t$2");
  s = s.replace(/([^\s])\}([^\n]*)/g, "$1\n}\n$2");
  s = s.replace(/([^\s]);([^\s\}])/g, "$1;\n\t$2");
  return s;
}

获取cookie值

function getCookie(name) {
  var arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)"));
  if (arr != null) return unescape(arr[2]);
  return null;
}

获得URL中GET参数值

// 用法:如果地址是 test.htm?t1=1&t2=2&t3=3, 那么能取得:GET["t1"], GET["t2"], GET["t3"]
function getGet() {
  querystr = window.location.href.split("?");
  if (querystr[1]) {
    GETs = querystr[1].split("&");
    GET = [];
    for (i = 0; i < GETs.length; i++) {
      tmp_arr = GETs.split("=");
      key = tmp_arr[0];
      GET[key] = tmp_arr[1];
    }
  }
  return querystr[1];
}

获取移动设备初始化大小

function getInitZoom() {
  if (!this._initZoom) {
    var screenWidth = Math.min(screen.height, screen.width);
    if (this.isAndroidMobileDevice() && !this.isNewChromeOnAndroid()) {
      screenWidth = screenWidth / window.devicePixelRatio;
    }
    this._initZoom = screenWidth / document.body.offsetWidth;
  }
  return this._initZoom;
}

获取页面高度

function getPageHeight() {
  var g = document,
    a = g.body,
    f = g.documentElement,
    d = g.compatMode == "BackCompat" ? a : g.documentElement;
  return Math.max(f.scrollHeight, a.scrollHeight, d.clientHeight);
}

获取页面scrollLeft

function getPageScrollLeft() {
  var a = document;
  return a.documentElement.scrollLeft || a.body.scrollLeft;
}

获取页面scrollTop

function getPageScrollTop() {
  var a = document;
  return a.documentElement.scrollTop || a.body.scrollTop;
}

获取页面可视高度

function getPageViewHeight() {
  var d = document,
    a = d.compatMode == "BackCompat" ? d.body : d.documentElement;
  return a.clientHeight;
}

获取页面可视宽度

function getPageViewWidth() {
  var d = document,
    a = d.compatMode == "BackCompat" ? d.body : d.documentElement;
  return a.clientWidth;
}

获取页面宽度

function getPageWidth() {
  var g = document,
    a = g.body,
    f = g.documentElement,
    d = g.compatMode == "BackCompat" ? a : g.documentElement;
  return Math.max(f.scrollWidth, a.scrollWidth, d.clientWidth);
}

获取移动设备屏幕宽度

function getScreenWidth() {
  var smallerSide = Math.min(screen.width, screen.height);
  var fixViewPortsExperiment =
    rendererModel.runningExperiments.FixViewport ||
    rendererModel.runningExperiments.fixviewport;
  var fixViewPortsExperimentRunning =
    fixViewPortsExperiment && fixViewPortsExperiment.toLowerCase() === "new";
  if (fixViewPortsExperiment) {
    if (this.isAndroidMobileDevice() && !this.isNewChromeOnAndroid()) {
      smallerSide = smallerSide / window.devicePixelRatio;
    }
  }
  return smallerSide;
}

获取网页被卷去的位置

function getScrollXY() {
  return document.body.scrollTop
    ? {
        x: document.body.scrollLeft,
        y: document.body.scrollTop
      }
    : {
        x: document.documentElement.scrollLeft,
        y: document.documentElement.scrollTop
      };
}

获取URL上的参数

// 获取URL中的某参数值,不区分大小写
// 获取URL中的某参数值,不区分大小写,
// 默认是取'hash'里的参数,
// 如果传其他参数支持取‘search’中的参数
// @param {String} name 参数名称
export function getUrlParam(name, type = "hash") {
  let newName = name,
    reg = new RegExp("(^|&)" + newName + "=([^&]*)(&|$)", "i"),
    paramHash = window.location.hash.split("?")[1] || "",
    paramSearch = window.location.search.split("?")[1] || "",
    param;

  type === "hash" ? (param = paramHash) : (param = paramSearch);

  let result = param.match(reg);

  if (result != null) {
    return result[2].split("/")[0];
  }
  return null;
}

检验URL链接是否有效

function getUrlState(URL) {
  var xmlhttp = new ActiveXObject("microsoft.xmlhttp");
  xmlhttp.Open("GET", URL, false);
  try {
    xmlhttp.Send();
  } catch (e) {
  } finally {
    var result = xmlhttp.responseText;
    if (result) {
      if (xmlhttp.Status == 200) {
        return true;
      } else {
        return false;
      }
    } else {
      return false;
    }
  }
}

获取窗体可见范围的宽与高

function getViewSize() {
  var de = document.documentElement;
  var db = document.body;
  var viewW = de.clientWidth == 0 ? db.clientWidth : de.clientWidth;
  var viewH = de.clientHeight == 0 ? db.clientHeight : de.clientHeight;
  return Array(viewW, viewH);
}

获取移动设备最大化大小

function getZoom() {
  var screenWidth =
    Math.abs(window.orientation) === 90
      ? Math.max(screen.height, screen.width)
      : Math.min(screen.height, screen.width);
  if (this.isAndroidMobileDevice() && !this.isNewChromeOnAndroid()) {
    screenWidth = screenWidth / window.devicePixelRatio;
  }
  var FixViewPortsExperiment =
    rendererModel.runningExperiments.FixViewport ||
    rendererModel.runningExperiments.fixviewport;
  var FixViewPortsExperimentRunning =
    FixViewPortsExperiment &&
    (FixViewPortsExperiment === "New" || FixViewPortsExperiment === "new");
  if (FixViewPortsExperimentRunning) {
    return screenWidth / window.innerWidth;
  } else {
    return screenWidth / document.body.offsetWidth;
  }
}

判断是否安卓移动设备访问

function isAndroidMobileDevice() {
  return /android/i.test(navigator.userAgent.toLowerCase());
}

判断是否苹果移动设备访问

function isAppleMobileDevice() {
  return /iphone|ipod|ipad|Macintosh/i.test(navigator.userAgent.toLowerCase());
}

判断是否为数字类型

function isDigit(value) {
  var patrn = /^[0-9]*$/;
  if (patrn.exec(value) == null || value == "") {
    return false;
  } else {
    return true;
  }
}

是否是某类手机型号

// 用devicePixelRatio和分辨率判断
const isIphonex = () => {
  // X XS, XS Max, XR
  const xSeriesConfig = [
    {
      devicePixelRatio: 3,
      width: 375,
      height: 812
    },
    {
      devicePixelRatio: 3,
      width: 414,
      height: 896
    },
    {
      devicePixelRatio: 2,
      width: 414,
      height: 896
    }
  ];
  // h5
  if (typeof window !== "undefined" && window) {
    const isIOS = /iphone/gi.test(window.navigator.userAgent);
    if (!isIOS) return false;
    const { devicePixelRatio, screen } = window;
    const { width, height } = screen;
    return xSeriesConfig.some(
      item =>
        item.devicePixelRatio === devicePixelRatio &&
        item.width === width &&
        item.height === height
    );
  }
  return false;
};

判断是否移动设备

function isMobile() {
  if (typeof this._isMobile === "boolean") {
    return this._isMobile;
  }
  var screenWidth = this.getScreenWidth();
  var fixViewPortsExperiment =
    rendererModel.runningExperiments.FixViewport ||
    rendererModel.runningExperiments.fixviewport;
  var fixViewPortsExperimentRunning =
    fixViewPortsExperiment && fixViewPortsExperiment.toLowerCase() === "new";
  if (!fixViewPortsExperiment) {
    if (!this.isAppleMobileDevice()) {
      screenWidth = screenWidth / window.devicePixelRatio;
    }
  }
  var isMobileScreenSize = screenWidth < 600;
  var isMobileUserAgent = false;
  this._isMobile = isMobileScreenSize && this.isTouchScreen();
  return this._isMobile;
}

判断吗是否手机号码

function isMobileNumber(e) {
  var i =
      "134,135,136,137,138,139,150,151,152,157,158,159,187,188,147,182,183,184,178",
    n = "130,131,132,155,156,185,186,145,176",
    a = "133,153,180,181,189,177,173,170",
    o = e || "",
    r = o.substring(0, 3),
    d = o.substring(0, 4),
    s =
      !!/^1\d{10}$/.test(o) &&
      (n.indexOf(r) >= 0
        ? "联通"
        : a.indexOf(r) >= 0
        ? "电信"
        : "1349" == d
        ? "电信"
        : i.indexOf(r) >= 0
        ? "移动"
        : "未知");
  return s;
}

判断是否是移动设备访问

function isMobileUserAgent() {
  return /iphone|ipod|android.*mobile|windows.*phone|blackberry.*mobile/i.test(
    window.navigator.userAgent.toLowerCase()
  );
}

判断鼠标是否移出事件

function isMouseOut(e, handler) {
  if (e.type !== "mouseout") {
    return false;
  }
  var reltg = e.relatedTarget
    ? e.relatedTarget
    : e.type === "mouseout"
    ? e.toElement
    : e.fromElement;
  while (reltg && reltg !== handler) {
    reltg = reltg.parentNode;
  }
  return reltg !== handler;
}

判断是否Touch屏幕

function isTouchScreen() {
  return (
    "ontouchstart" in window ||
    (window.DocumentTouch && document instanceof DocumentTouch)
  );
}

判断是否为网址

function isURL(strUrl) {
  var regular = /^\b(((https?|ftp):\/\/)?[-a-z0-9]+(\.[-a-z0-9]+)*\.(?:com|edu|gov|int|mil|net|org|biz|info|name|museum|asia|coop|aero|[a-z][a-z]|((25[0-5])|(2[0-4]\d)|(1\d\d)|([1-9]\d)|\d))\b(\/[-a-z0-9_:\@&?=+,.!\/~%\$]*)?)$/i;
  if (regular.test(strUrl)) {
    return true;
  } else {
    return false;
  }
}

判断是否打开视窗

function isViewportOpen() {
  return !!document.getElementById("wixMobileViewport");
}

加载样式文件

function loadStyle(url) {
  try {
    document.createStyleSheet(url);
  } catch (e) {
    var cssLink = document.createElement("link");
    cssLink.rel = "stylesheet";
    cssLink.type = "text/css";
    cssLink.href = url;
    var head = document.getElementsByTagName("head")[0];
    head.appendChild(cssLink);
  }
}

替换地址栏

function locationReplace(url) {
  if (history.replaceState) {
    history.replaceState(null, document.title, url);
    history.go(0);
  } else {
    location.replace(url);
  }
}

解决offsetX兼容性问题

// 针对火狐不支持offsetX/Y
function getOffset(e) {
  var target = e.target, // 当前触发的目标对象
    eventCoord,
    pageCoord,
    offsetCoord;

  // 计算当前触发元素到文档的距离
  pageCoord = getPageCoord(target);

  // 计算光标到文档的距离
  eventCoord = {
    X: window.pageXOffset + e.clientX,
    Y: window.pageYOffset + e.clientY
  };

  // 相减获取光标到第一个定位的父元素的坐标
  offsetCoord = {
    X: eventCoord.X - pageCoord.X,
    Y: eventCoord.Y - pageCoord.Y
  };
  return offsetCoord;
}

function getPageCoord(element) {
  var coord = { X: 0, Y: 0 };
  // 计算从当前触发元素到根节点为止,
  // 各级 offsetParent 元素的 offsetLeft 或 offsetTop 值之和
  while (element) {
    coord.X += element.offsetLeft;
    coord.Y += element.offsetTop;
    element = element.offsetParent;
  }
  return coord;
}

打开一个窗体通用方法

function openWindow(url, windowName, width, height) {
  var x = parseInt(screen.width / 2.0) - width / 2.0;
  var y = parseInt(screen.height / 2.0) - height / 2.0;
  var isMSIE = navigator.appName == "Microsoft Internet Explorer";
  if (isMSIE) {
    var p = "resizable=1,location=no,scrollbars=no,width=";
    p = p + width;
    p = p + ",height=";
    p = p + height;
    p = p + ",left=";
    p = p + x;
    p = p + ",top=";
    p = p + y;
    retval = window.open(url, windowName, p);
  } else {
    var win = window.open(
      url,
      "ZyiisPopup",
      "top=" +
        y +
        ",left=" +
        x +
        ",scrollbars=" +
        scrollbars +
        ",dialog=yes,modal=yes,width=" +
        width +
        ",height=" +
        height +
        ",resizable=no"
    );
    eval("try { win.resizeTo(width, height); } catch(e) { }");
    win.focus();
  }
}

将键值对拼接成URL带参数

export default const fnParams2Url = obj=> {
      let aUrl = []
      let fnAdd = function(key, value) {
        return key + '=' + value
      }
      for (var k in obj) {
        aUrl.push(fnAdd(k, obj[k]))
      }
      return encodeURIComponent(aUrl.join('&'))
 }

去掉url前缀

function removeUrlPrefix(a) {
  a = a
    .replace(//g, ":")
    .replace(//g, ".")
    .replace(//g, "/");
  while (
    trim(a)
      .toLowerCase()
      .indexOf("http://") == 0
  ) {
    a = trim(a.replace(/http:\/\//i, ""));
  }
  return a;
}

替换全部

String.prototype.replaceAll = function(s1, s2) {
  return this.replace(new RegExp(s1, "gm"), s2);
};

resize的操作

(function() {
  var fn = function() {
    var w = document.documentElement
        ? document.documentElement.clientWidth
        : document.body.clientWidth,
      r = 1255,
      b = Element.extend(document.body),
      classname = b.className;
    if (w < r) {
      //当窗体的宽度小于1255的时候执行相应的操作
    } else {
      //当窗体的宽度大于1255的时候执行相应的操作
    }
  };
  if (window.addEventListener) {
    window.addEventListener("resize", function() {
      fn();
    });
  } else if (window.attachEvent) {
    window.attachEvent("onresize", function() {
      fn();
    });
  }
  fn();
})();

滚动到顶部

// 使用document.documentElement.scrollTop 或 document.body.scrollTop 获取到顶部的距离,从顶部
// 滚动一小部分距离。使用window.requestAnimationFrame()来滚动。
// @example
// scrollToTop();
function scrollToTop() {
  var c = document.documentElement.scrollTop || document.body.scrollTop;

  if (c > 0) {
    window.requestAnimationFrame(scrollToTop);
    window.scrollTo(0, c - c / 8);
  }
}

设置cookie值

function setCookie(name, value, Hours) {
  var d = new Date();
  var offset = 8;
  var utc = d.getTime() + d.getTimezoneOffset() * 60000;
  var nd = utc + 3600000 * offset;
  var exp = new Date(nd);
  exp.setTime(exp.getTime() + Hours * 60 * 60 * 1000);
  document.cookie =
    name +
    "=" +
    escape(value) +
    ";path=/;expires=" +
    exp.toGMTString() +
    ";domain=360doc.com;";
}

设为首页

function setHomepage() {
  if (document.all) {
    document.body.style.behavior = "url(#default#homepage)";
    document.body.setHomePage("http://w3cboy.com");
  } else if (window.sidebar) {
    if (window.netscape) {
      try {
        netscape.security.PrivilegeManager.enablePrivilege(
          "UniversalXPConnect"
        );
      } catch (e) {
        alert(
          "该操作被浏览器拒绝,如果想启用该功能,请在地址栏内输入 about:config,然后将项 signed.applets.codebase_principal_support 值该为true"
        );
      }
    }
    var prefs = Components.classes[
      "@mozilla.org/preferences-service;1"
    ].getService(Components.interfaces.nsIPrefBranch);
    prefs.setCharPref("browser.startup.homepage", "http://w3cboy.com");
  }
}

按字母排序,对每行进行数组排序

function setSort() {
  var text = K1.value
    .split(/[\r\n]/)
    .sort()
    .join("\r\n"); //顺序
  var test = K1.value
    .split(/[\r\n]/)
    .sort()
    .reverse()
    .join("\r\n"); //反序
  K1.value = K1.value != text ? text : test;
}

延时执行

// 比如 sleep(1000) 意味着等待1000毫秒,还可从 Promise、Generator、Async/Await 等角度实现。
// Promise
const sleep = time => {
  return new Promise(resolve => setTimeout(resolve, time));
};

sleep(1000).then(() => {
  console.log(1);
});


// Generator
function* sleepGenerator(time) {
  yield new Promise(function(resolve, reject) {
    setTimeout(resolve, time);
  });
}

sleepGenerator(1000)
  .next()
  .value.then(() => {
    console.log(1);
  });

//async
function sleep(time) {
  return new Promise(resolve => setTimeout(resolve, time));
}

async function output() {
  let out = await sleep(1000);
  console.log(1);
  return out;
}

output();

function sleep(callback, time) {
  if (typeof callback === "function") {
    setTimeout(callback, time);
  }
}

function output() {
  console.log(1);
}

sleep(output, 1000);

判断是否以某个字符串开头

String.prototype.startWith = function(s) {
  return this.indexOf(s) == 0;
};

清除脚本内容

function stripscript(s) {
  return s.replace(/<script.*?>.*?<\/script>/gi, "");
}

时间个性化输出功能

/*
1、< 60s, 显示为“刚刚”
2、>= 1min && < 60 min, 显示与当前时间差“XX分钟前”
3、>= 60min && < 1day, 显示与当前时间差“今天 XX:XX”
4、>= 1day && < 1year, 显示日期“XX月XX日 XX:XX”
5、>= 1year, 显示具体日期“XXXX年XX月XX日 XX:XX”
*/
function timeFormat(time) {
  var date = new Date(time),
    curDate = new Date(),
    year = date.getFullYear(),
    month = date.getMonth() + 10,
    day = date.getDate(),
    hour = date.getHours(),
    minute = date.getMinutes(),
    curYear = curDate.getFullYear(),
    curHour = curDate.getHours(),
    timeStr;

  if (year < curYear) {
    timeStr = year + "年" + month + "月" + day + "日 " + hour + ":" + minute;
  } else {
    var pastTime = curDate - date,
      pastH = pastTime / 3600000;

    if (pastH > curHour) {
      timeStr = month + "月" + day + "日 " + hour + ":" + minute;
    } else if (pastH >= 1) {
      timeStr = "今天 " + hour + ":" + minute + "分";
    } else {
      var pastM = curDate.getMinutes() - minute;
      if (pastM > 1) {
        timeStr = pastM + "分钟前";
      } else {
        timeStr = "刚刚";
      }
    }
  }
  return timeStr;
}

全角转换为半角函数

function toCDB(str) {
  var result = "";
  for (var i = 0; i < str.length; i++) {
    code = str.charCodeAt(i);
    if (code >= 65281 && code <= 65374) {
      result += String.fromCharCode(str.charCodeAt(i) - 65248);
    } else if (code == 12288) {
      result += String.fromCharCode(str.charCodeAt(i) - 12288 + 32);
    } else {
      result += str.charAt(i);
    }
  }
  return result;
}

半角转换为全角函数

function toDBC(str) {
  var result = "";
  for (var i = 0; i < str.length; i++) {
    code = str.charCodeAt(i);
    if (code >= 33 && code <= 126) {
      result += String.fromCharCode(str.charCodeAt(i) + 65248);
    } else if (code == 32) {
      result += String.fromCharCode(str.charCodeAt(i) + 12288 - 32);
    } else {
      result += str.charAt(i);
    }
  }
  return result;
}

金额大写转换函数

function transform(tranvalue) {
  try {
    var i = 1;
    var dw2 = new Array("", "万", "亿"); //大单位
    var dw1 = new Array("拾", "佰", "仟"); //小单位
    var dw = new Array(
      "零",
      "壹",
      "贰",
      "叁",
      "肆",
      "伍",
      "陆",
      "柒",
      "捌",
      "玖"
    ); 
    //整数部分用
    //以下是小写转换成大写显示在合计大写的文本框中
    //分离整数与小数
    var source = splits(tranvalue);
    var num = source[0];
    var dig = source[1];
    //转换整数部分
    var k1 = 0; //计小单位
    var k2 = 0; //计大单位
    var sum = 0;
    var str = "";
    var len = source[0].length; //整数的长度
    for (i = 1; i <= len; i++) {
      var n = source[0].charAt(len - i); //取得某个位数上的数字
      var bn = 0;
      if (len - i - 1 >= 0) {
        bn = source[0].charAt(len - i - 1); //取得某个位数前一位上的数字
      }
      sum = sum + Number(n);
      if (sum != 0) {
        str = dw[Number(n)].concat(str); //取得该数字对应的大写数字,并插入到str字符串的前面
        if (n == "0") sum = 0;
      }
      if (len - i - 1 >= 0) {
        //在数字范围内
        if (k1 != 3) {
          //加小单位
          if (bn != 0) {
            str = dw1[k1].concat(str);
          }
          k1++;
        } else {
          //不加小单位,加大单位
          k1 = 0;
          var temp = str.charAt(0);
          if (temp == "万" || temp == "亿")
            //若大单位前没有数字则舍去大单位
            str = str.substr(1, str.length - 1);
          str = dw2[k2].concat(str);
          sum = 0;
        }
      }
      if (k1 == 3) {
        //小单位到千则大单位进一
        k2++;
      }
    }
    //转换小数部分
    var strdig = "";
    if (dig != "") {
      var n = dig.charAt(0);
      if (n != 0) {
        strdig += dw[Number(n)] + "角"; //加数字
      }
      var n = dig.charAt(1);
      if (n != 0) {
        strdig += dw[Number(n)] + "分"; //加数字
      }
    }
    str += "元" + strdig;
  } catch (e) {
    return "0元";
  }
  return str;
}
//拆分整数与小数
function splits(tranvalue) {
  var value = new Array("", "");
  temp = tranvalue.split(".");
  for (var i = 0; i < temp.length; i++) {
    value = temp;
  }
  return value;
}

清除空格

String.prototype.trim = function() {
  var reExtraSpace = /^\s*(.*?)\s+$/;
  return this.replace(reExtraSpace, "$1");
};

// 清除左空格
function ltrim(s) {
  return s.replace(/^(\s*| *)/, "");
}

// 清除右空格
function rtrim(s) {
  return s.replace(/(\s*| *)$/, "");
}

随机数时间戳

function uniqueId() {
  var a = Math.random,
    b = parseInt;
  return (
    Number(new Date()).toString() + b(10 * a()) + b(10 * a()) + b(10 * a())
  );
}

实现utf8解码

function utf8_decode(str_data) {
  var tmp_arr = [],
    i = 0,
    ac = 0,
    c1 = 0,
    c2 = 0,
    c3 = 0;
  str_data += "";
  while (i < str_data.length) {
    c1 = str_data.charCodeAt(i);
    if (c1 < 128) {
      tmp_arr[ac++] = String.fromCharCode(c1);
      i++;
    } else if (c1 > 191 && c1 < 224) {
      c2 = str_data.charCodeAt(i + 1);
      tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
      i += 2;
    } else {
      c2 = str_data.charCodeAt(i + 1);
      c3 = str_data.charCodeAt(i + 2);
      tmp_arr[ac++] = String.fromCharCode(
        ((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)
      );
      i += 3;
    }
  }
  return tmp_arr.join("");
}

如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的肯定是我前进的最大动力😁

  • 附笔记链接,阅读往期更多优质文章可移步查看,喜欢的可以给我点赞鼓励哦:#27

前端面试必备——基本排序算法

排序算法是面试及笔试中必考点,本文通过动画方式演示,通过实例讲解,最后给出JavaScript版的排序算法

插入排序

算法描述

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤 2~5

现有一组数组 arr = [5, 6, 3, 1, 8, 7, 2, 4]

[5] 6 3 1 8 7 2 4  //第一个元素被认为已经被排序

[5,6]  3 1 8 7 2 4 //6与5比较,放在5的右边

[3,5,6]  1 8 7 2 4 //3与6和5比较,都小,则放入数组头部

[1,3,5,6]   8 7 2 4 //1与3,5,6比较,则放入头部

[1,3,5,6,8]   7 2 4

[1,3,5,6,7,8]  2 4

[1,2,3,5,6,7,8] 4

[1,2,3,4,5,6,7,8] 

编程思路

双层循环,外循环控制未排序的元素,内循环控制已排序的元素,将未排序元素设为标杆,与已排序的元素进行比较,小于则交换位置,大于则位置不动

function insertSort(arr){
    var tmp;
    for(var i=1;i<arr.length;i++){
        tmp  = arr[i];
        for(var j=i;j>=0;j--){
            if(arr[j-1]>tmp){
                arr[j]=arr[j-1];
            }else{
                arr[j]=tmp;
                break;
            }
        }
    }
    return arr
}

时间复杂度O(n^2)

选择排序

算法描述

直接从待排序数组中选择一个最小(或最大)数字,放入新数组中。

[1] 5 6 3  8 7 2 4 
[1,2] 5 6 3  8 7  4 
[1,2,3] 5 6  8 7 2 4 
[1,2,3,4] 5 6 8 7
[1,2,3,4,5] 6  8 7 
[1,2,3,4,5,6] 8 7  
[1,2,3,4,5,6,7] 8  
[1,2,3,4,5,6,7,8] 

编程思路

先假设第一个元素为最小的,然后通过循环找出最小元素,然后同第一个元素交换,接着假设第二个元素,重复上述操作即可

function selectionSort(array) {
  var length = array.length,
      i,
      j,
      minIndex,
      minValue,
      temp;
  for (i = 0; i < length - 1; i++) {
    minIndex = i;
    minValue = array[minIndex];
    for (j = i + 1; j < length; j++) {//通过循环选出最小的
      if (array[j] < minValue) {
        minIndex = j;
        minValue = array[minIndex];
      }
    }
    // 交换位置
    temp = array[i];
    array[i] = minValue;
    array[minIndex] = temp;
  }
  return array
}

时间复杂度

O(n^2)

归并排序

算法描述:

  1. 把 n 个记录看成 n 个长度为 l 的有序子表
  2. 进行两两归并使记录关键字有序,得到 n/2 个长度为 2 的有序子表
  3. 重复第 2 步直到所有记录归并成一个长度为 n 的有序表为止。
5 6 3 1 8 7 2 4

[5,6] [3,1] [8,7] [2,4]

[5,6] [1,3] [7,8] [2,4]

[5,6,1,3] [7,8,2,4]

[1,3,5,6] [2,4,7,8]

[1,2,3,4,5,6,7,8]

编程思路

将数组一直等分,然后合并

function merge(left, right) {
  var tmp = [];

  while (left.length && right.length) {
    if (left[0] < right[0])
      tmp.push(left.shift());
    else
      tmp.push(right.shift());
  }

  return tmp.concat(left, right);
}

function mergeSort(a) {
  if (a.length === 1) 
    return a;

  var mid = Math.floor(a.length / 2)
    , left = a.slice(0, mid)
    , right = a.slice(mid);

  return merge(mergeSort(left), mergeSort(right));
}

时间复杂度

O(nlogn)

快速排序

算法描述:

在数据集之中,选择一个元素作为”基准”(pivot)。
所有小于”基准”的元素,都移到”基准”的左边;所有大于”基准”的元素,都移到”基准”的右边。这个操作称为分区 (partition)操作,分区操作结束后,基准元素所处的位置就是最终排序后它的位置。
对”基准”左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。

5 6 3 1 8 7 2 4

pivot
|
5 6 3 1 9 7 2 4
|
storeIndex

5 6 3 1 9 7 2 4//将5同6比较,大于则不更换
|
storeIndex

3 6 5 1 9 7 2 4//将5同3比较,小于则更换
  |
  storeIndex

3 6 1 5 9 7 2 4//将5同1比较,小于则不更换
    |
   storeIndex
...

3 6 1 4 9 7 2 5//将5同4比较,小于则更换
      |
      storeIndex

3 6 1 4 5 7 2 9//将标准元素放到正确位置
      |
storeIndex pivot

上述讲解了分区的过程,然后就是对每个子区进行同样做法
function quickSort(arr){
    if(arr.length<=1) return arr;
    var partitionIndex=Math.floor(arr.length/2);
    var tmp=arr[partitionIndex];
    var left=[];
    var right=[];
    for(var i=0;i<arr.length;i++){
        if(arr[i]<tmp){
            left.push(arr[i])
        }else{
            right.push(arr[i])
        }
    }
    return quickSort(left).concat([tmp],quickSort(right))
}

上述版本会造成堆栈溢出,所以建议使用下面版本

原地分区版:主要区别在于先进行分区处理,将数组分为左小右大

function quickSort(arr){
    function swap(arr,right,left){
        var tmp = arr[right];
        arr[right]=arr[left];
        arr[left]=tmp;
    }
    function partition(arr,left,right){//分区操作,
        var pivotValue=arr[right]//最右面设为标准
        var storeIndex=left;
        for(var i=left;i<right;i++){
            if(arr[i]<=pivotValue){
                swap(arr,storeIndex,i);
                storeIndex++;
            }
        }
        swap(arr,right,storeIndex);
        return storeIndex//返回标杆元素的索引值
    }
    function sort(arr,left,right){
        if(left>right) return;
        var storeIndex=partition(arr,left,right);
        sort(arr,left,storeIndex-1);
        sort(arr,storeIndex+1,right);
    }
    sort(arr,0,arr.length-1);
    return arr;
}

时间复杂度

O(nlogn)

冒泡排序

算法描述:

    1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
    1. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
    1. 针对所有的元素重复以上的步骤,除了最后一个。
    1. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
5 6 3 1 8 7 2 4

[5 6] 3 1 8 7 2 4 //比较5和6

5 [6 3] 1 8 7 2 4

5 3 [6 1] 8 7 2 4

5 3 1 [6 8] 7 2 4

5 3 1 6 [8 7] 2 4

5 3 1 6 7 [8 2] 4

5 3 1 6 7 2 [8 4]

5 3 1 6 7 2 4 8  // 这样最后一个元素已经在正确位置,所以下一次开始时候就不需要再比较最后一个

编程思路

外循环控制需要比较的元素,比如第一次排序后,最后一个元素就不需要比较了,内循环则负责两两元素比较,将元素放到正确位置上

function bubbleSort(arr){
    var len=arr.length;
    for(var i=len-1;i>0;i--){
        for(var j=0;j<i;j++){
            if(arr[j]<arr[j+1]){
                var tmp = arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=tmp
            }
        }
    }
    return arr;
}

时间复杂度

O(n^2)

参考资料

day13

//正则
//字面量
var box = /box/gi;
var str = "This is a Box boX"
//test
console.log(box.test(str));
console.log(/box/gi.test(str));
//exec
console.log(/box/gi.exec(str));
console.log(box.exec(str));

//string方法
//match
console.log(str.match(/box/gi));
//replace
console.log(str.replace(/box/gi, "xxx"));
//search
console.log(str.search(/box/gi));
//split
//console.log('a,b ,c , d, e'.split(', '));
console.log(str.split(/b/gi))

//常用
var pattern = /g..gle/; //一个点.匹配一个任意的字符
var str = "goagle";
console.log(str.match(pattern));

var pattern = /ge*gle/; //.* 匹配0到多个字符
var str = "gegle"
console.log(pattern.test(str));

var pattern = /g[^A-Z]*gle/;
var str = "gAgle";
console.log(pattern.test(str));

var pattern = /g[^a-z]*gle/; //可以有任意多个非0-9的字符
var str = "google";
console.log(pattern.test(str));

var pattern = /[a-z][A-Z]+le/;
var str = "aaGle";
console.log(pattern.test(str));

var pattern = /g\w*gle/;
var str = "gooA3_gle";
console.log(pattern.test(str));

var pattern = /g\d{2,4}gle/;
var str = "g3gle";
console.log(pattern.test(str));

var pattern = /goo\s+gle/;
var str = "goo  gle";
console.log(pattern.exec(str));

var pattern = /google|baidu|bing/; // | : 匹配三个中的其中一个字符串
var str = "googl2e bai3du;bingl"
console.log(pattern.exec(str));

var pattern = /google{4,8}/; //匹配分组中的字符出现4-8次
var str = "googleeeeeeeeee"
console.log(pattern.exec(str));

var pattern = /8(.*)8/;
var str = "this is 8google8 baab 8ggg8";
console.log(str.match(pattern));
console.log(pattern.exec(str))

var pattern = /[a-z]+$/i;
var str = "google 2016 gg";
console.log(str.match(pattern)); //"google"

var pattern = /^[a-z]+\s[0-9]{4}$/i;
var str = "google 2016";
console.log(pattern.exec(str)); //"google 2016"

var pattern = /^([a-z]+)\s([0-9]{4})$/i;
var str = "google 2016";
console.log(pattern.exec(str)); //"google 2016,google,2016"

//分组
var pattern = /(\d+)([A-Z])([a-z])/; //一个或多个数字,和一个a-z的字母
var str = "123Babc";
console.log(pattern.exec(str)); //"123a,123,a"

var pattern = /(\d+)([A-Z])(?:[a-z])/;
var str = "123Babc";
console.log(pattern.exec(str)); //"123a,123"

var pattern = /\.\[\/b\]/;
var str = ".[/bb]"
console.log(pattern.test(str));

var pattern = /\{\d+\}/;
var str = "{9}"
console.log(pattern.test(str));

前端面试必备——十大经典排序算法

冒泡排序

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

作为最简单的排序算法之一,冒泡排序给我的感觉就像 Abandon 在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。

1. 算法步骤

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

  3. 针对所有的元素重复以上的步骤,除了最后一个。

  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

2. 动图演示

bubblesort

3. 什么时候最快

当输入的数据已经是正序时(都已经是正序了,我还要你冒泡排序有何用啊)。

4. 什么时候最慢

当输入的数据是反序时(写一个 for 循环反序输出数据不就行了,干嘛要用你冒泡排序呢,我是闲的吗)。

5. JavaScript 代码实现

function bubbleSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len - 1; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {        // 相邻元素两两对比
                var temp = arr[j+1];        // 元素交换
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

6. Python 代码实现

def bubbleSort(arr):
    for i in range(1, len(arr)):
        for j in range(0, len(arr)-i):
            if arr[j] > arr[j+1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr

7. Go 代码实现

func bubbleSort(arr []int) []int {
	length := len(arr)
	for i := 0; i < length; i++ {
		for j := 0; j < length-1-i; j++ {
			if arr[j] > arr[j+1] {
				arr[j], arr[j+1] = arr[j+1], arr[j]
			}
		}
	}
	return arr
}

选择排序

选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。

1. 算法步骤

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置

  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

  3. 重复第二步,直到所有元素均排序完毕。

2. 动图演示

selectionsort

3. JavaScript 代码实现

function selectionSort(arr) {
    var len = arr.length;
    var minIndex, temp;
    for (var i = 0; i < len - 1; i++) {
        minIndex = i;
        for (var j = i + 1; j < len; j++) {
            if (arr[j] < arr[minIndex]) {     // 寻找最小的数
                minIndex = j;                 // 将最小数的索引保存
            }
        }
        temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
    return arr;
}

4. Python 代码实现

def selectionSort(arr):
    for i in range(len(arr)-1):
        for j in range(i+1, len(arr)):
            if arr[j] < arr[i]:
                arr[i], arr[j] = arr[j], arr[i]
    return arr

5. Go 代码实现

func selectionSort(arr []int) []int {
	length := len(arr)
	for i := 0; i < length-1; i++ {
		min := i
		for j := i + 1; j < length; j++ {
			if arr[min] > arr[j] {
				min = j
			}
		}
		arr[i], arr[min] = arr[min], arr[i]
	}
	return arr
}

插入排序

插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。

1. 算法步骤

  1. 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

2. 动图演示

insertionsort

3. JavaScript 代码实现

function insertionSort(arr) {
    var len = arr.length;
    var preIndex, current;
    for (var i = 1; i < len; i++) {
        preIndex = i - 1;
        current = arr[i];
        while(preIndex >= 0 && arr[preIndex] > current) {
            arr[preIndex+1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex+1] = current;
    }
    return arr;
}

4. Python 代码实现

def insertionSort(arr):
    for i in range(len(arr)):
        preIndex = i-1
        current = arr[i]
        while preIndex >= 0 and arr[preIndex] > current:
            arr[preIndex+1] = arr[preIndex]
            preIndex-=1
        arr[preIndex+1] = current
    return arr

5. Go 代码实现

func insertionSort(arr []int) []int {
	for i := range arr {
		preIndex := i - 1
		current := arr[i]
		for preIndex >= 0 && arr[preIndex] > current {
			arr[preIndex+1] = arr[preIndex]
			preIndex -= 1
		}
		arr[preIndex+1] = current
	}
	return arr
}

希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

希尔排序的基本**是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

1. 算法步骤

  1. 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;

  2. 按增量序列个数 k,对序列进行 k 趟排序;

  3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

2. JavaScript 代码实现

function shellSort(arr) {
    var len = arr.length,
        temp,
        gap = 1;
    while(gap < len/3) {          //动态定义间隔序列
        gap =gap*3+1;
    }
    for (gap; gap > 0; gap = Math.floor(gap/3)) {
        for (var i = gap; i < len; i++) {
            temp = arr[i];
            for (var j = i-gap; j >= 0 && arr[j] > temp; j-=gap) {
                arr[j+gap] = arr[j];
            }
            arr[j+gap] = temp;
        }
    }
    return arr;
}

3. Python 代码实现

def shellSort(arr):
    import math
    gap=1
    while(gap < len(arr)/3):
        gap = gap*3+1
    while gap > 0:
        for i in range(gap,len(arr)):
            temp = arr[i]
            j = i-gap
            while j >=0 and arr[j] > temp:
                arr[j+gap]=arr[j]
                j-=gap
            arr[j+gap] = temp
        gap = math.floor(gap/3)
    return arr
}

4. Go 代码实现

func shellSort(arr []int) []int {
	length := len(arr)
	gap := 1
	for gap < gap/3 {
		gap = gap*3 + 1
	}
	for gap > 0 {
		for i := gap; i < length; i++ {
			temp := arr[i]
			j := i - gap
			for j >= 0 && arr[j] > temp {
				arr[j+gap] = arr[j]
				j -= gap
			}
			arr[j+gap] = temp
		}
		gap = gap / 3
	}
	return arr
}

归并排序

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

作为一种典型的分而治之**的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
  • 自下而上的迭代;

和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。

2. 算法步骤

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;

  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

  4. 重复步骤 3 直到某一指针达到序列尾;

  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

3. 动图演示

mergesort

4. JavaScript 代码实现

function mergeSort(arr) {  // 采用自上而下的递归方法
    var len = arr.length;
    if(len < 2) {
        return arr;
    }
    var middle = Math.floor(len / 2),
        left = arr.slice(0, middle),
        right = arr.slice(middle);
    return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right)
{
    var result = [];

    while (left.length && right.length) {
        if (left[0] <= right[0]) {
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }

    while (left.length)
        result.push(left.shift());

    while (right.length)
        result.push(right.shift());

    return result;
}

5. Python 代码实现

def mergeSort(arr):
    import math
    if(len(arr)<2):
        return arr
    middle = math.floor(len(arr)/2)
    left, right = arr[0:middle], arr[middle:]
    return merge(mergeSort(left), mergeSort(right))

def merge(left,right):
    result = []
    while left and right:
        if left[0] <= right[0]:
            result.append(left.pop(0));
        else:
            result.append(right.pop(0));
    while left:
        result.append(left.pop(0));
    while right:
        result.append(right.pop(0));
    return result

6. Go 代码实现

func mergeSort(arr []int) []int {
	length := len(arr)
	if length < 2 {
		return arr
	}
	middle := length / 2
	left := arr[0:middle]
	right := arr[middle:]
	return merge(mergeSort(left), mergeSort(right))
}

func merge(left []int, right []int) []int {
	var result []int
	for len(left) != 0 && len(right) != 0 {
		if left[0] <= right[0] {
			result = append(result, left[0])
			left = left[1:]
		} else {
			result = append(result, right[0])
			right = right[1:]
		}
	}

	for len(left) != 0 {
		result = append(result, left[0])
		left = left[1:]
	}

	for len(right) != 0 {
		result = append(result, right[0])
		right = right[1:]
	}

	return result
}

快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。

快速排序又是一种分而治之**在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好。

1. 算法步骤

  1. 从数列中挑出一个元素,称为 “基准”(pivot);

  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

2. 动图演示

quicksort

3. JavaScript 代码实现

function quickSort(arr, left, right) {
    var len = arr.length,
        partitionIndex,
        left = typeof left != 'number' ? 0 : left,
        right = typeof right != 'number' ? len - 1 : right;

    if (left < right) {
        partitionIndex = partition(arr, left, right);
        quickSort(arr, left, partitionIndex-1);
        quickSort(arr, partitionIndex+1, right);
    }
    return arr;
}

function partition(arr, left ,right) {     // 分区操作
    var pivot = left,                      // 设定基准值(pivot)
        index = pivot + 1;
    for (var i = index; i <= right; i++) {
        if (arr[i] < arr[pivot]) {
            swap(arr, i, index);
            index++;
        }        
    }
    swap(arr, pivot, index - 1);
    return index-1;
}

function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
functiion paritition2(arr, low, high) {
  let pivot = arr[low];
  while (low < high) {
    while (low < high && arr[high] > pivot) {
      --high;
    }
    arr[low] = arr[high];
    while (low < high && arr[low] <= pivot) {
      ++low;
    }
    arr[high] = arr[low];
  }
  arr[low] = pivot;
  return low;
}

function quickSort2(arr, low, high) {
  if (low < high) {
    let pivot = paritition2(arr, low, high);
    quickSort2(arr, low, pivot - 1);
    quickSort2(arr, pivot + 1, high);
  }
  return arr;
}

4. Python 代码实现

def quickSort(arr, left=None, right=None):
    left = 0 if not isinstance(left,(int, float)) else left
    right = len(arr)-1 if not isinstance(right,(int, float)) else right
    if left < right:
        partitionIndex = partition(arr, left, right)
        quickSort(arr, left, partitionIndex-1)
        quickSort(arr, partitionIndex+1, right)
    return arr

def partition(arr, left, right):
    pivot = left
    index = pivot+1
    i = index
    while  i <= right:
        if arr[i] < arr[pivot]:
            swap(arr, i, index)
            index+=1
        i+=1
    swap(arr,pivot,index-1)
    return index-1

def swap(arr, i, j):
    arr[i], arr[j] = arr[j], arr[i]

5. Go 代码实现

func quickSort(arr []int) []int {
	return _quickSort(arr, 0, len(arr)-1)
}

func _quickSort(arr []int, left, right int) []int {
	if left < right {
		partitionIndex := partition(arr, left, right)
		_quickSort(arr, left, partitionIndex-1)
		_quickSort(arr, partitionIndex+1, right)
	}
	return arr
}

func partition(arr []int, left, right int) int {
	pivot := left
	index := pivot + 1

	for i := index; i <= right; i++ {
		if arr[i] < arr[pivot] {
			swap(arr, i, index)
			index += 1
		}
	}
	swap(arr, pivot, index-1)
	return index - 1
}

func swap(arr []int, i, j int) {
	arr[i], arr[j] = arr[j], arr[i]
}

6. C++版

 //标准分割函数
 Paritition1(int A[], int low, int high) {
   int pivot = A[low];
   while (low < high) {
     while (low < high && A[high] >= pivot) {
       --high;
     }
     A[low] = A[high];
     while (low < high && A[low] <= pivot) {
       ++low;
     }
     A[high] = A[low];
   }
   A[low] = pivot;
   return low;
 }

 void QuickSort(int A[], int low, int high) //快排母函数
 {
   if (low < high) {
     int pivot = Paritition1(A, low, high); 
     QuickSort(A, low, pivot - 1);
     QuickSort(A, pivot + 1, high);
   }
 }

堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  1. 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  2. 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

堆排序的平均时间复杂度为 Ο(nlogn)。

1. 算法步骤

  1. 创建一个堆 H[0……n-1];

  2. 把堆首(最大值)和堆尾互换;

  3. 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;

  4. 重复步骤 2,直到堆的尺寸为 1。

2. 动图演示

heapsort

3. JavaScript 代码实现

var len;    // 因为声明的多个函数都需要数据长度,所以把len设置成为全局变量

function buildMaxHeap(arr) {   // 建立大顶堆
    len = arr.length;
    for (var i = Math.floor(len/2); i >= 0; i--) {
        heapify(arr, i);
    }
}

function heapify(arr, i) {     // 堆调整
    var left = 2 * i + 1,
        right = 2 * i + 2,
        largest = i;

    if (left < len && arr[left] > arr[largest]) {
        largest = left;
    }

    if (right < len && arr[right] > arr[largest]) {
        largest = right;
    }

    if (largest != i) {
        swap(arr, i, largest);
        heapify(arr, largest);
    }
}

function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

function heapSort(arr) {
    buildMaxHeap(arr);

    for (var i = arr.length-1; i > 0; i--) {
        swap(arr, 0, i);
        len--;
        heapify(arr, 0);
    }
    return arr;
}

4. Python 代码实现

def buildMaxHeap(arr):
    import math
    for i in range(math.floor(len(arr)/2),-1,-1):
        heapify(arr,i)

def heapify(arr, i):
    left = 2*i+1
    right = 2*i+2
    largest = i
    if left < arrLen and arr[left] > arr[largest]:
        largest = left
    if right < arrLen and arr[right] > arr[largest]:
        largest = right

    if largest != i:
        swap(arr, i, largest)
        heapify(arr, largest)

def swap(arr, i, j):
    arr[i], arr[j] = arr[j], arr[i]

def heapSort(arr):
    global arrLen
    arrLen = len(arr)
    buildMaxHeap(arr)
    for i in range(len(arr)-1,0,-1):
        swap(arr,0,i)
        arrLen -=1
        heapify(arr, 0)
    return arr

5. Go 代码实现

func heapSort(arr []int) []int {
	arrLen := len(arr)
	buildMaxHeap(arr, arrLen)
	for i := arrLen - 1; i >= 0; i-- {
		swap(arr, 0, i)
		arrLen -= 1
		heapify(arr, 0, arrLen)
	}
	return arr
}

func buildMaxHeap(arr []int, arrLen int) {
	for i := arrLen / 2; i >= 0; i-- {
		heapify(arr, i, arrLen)
	}
}

func heapify(arr []int, i, arrLen int) {
	left := 2*i + 1
	right := 2*i + 2
	largest := i
	if left < arrLen && arr[left] > arr[largest] {
		largest = left
	}
	if right < arrLen && arr[right] > arr[largest] {
		largest = right
	}
	if largest != i {
		swap(arr, i, largest)
		heapify(arr, largest, arrLen)
	}
}

func swap(arr []int, i, j int) {
	arr[i], arr[j] = arr[j], arr[i]
}

计数排序

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

1. 动图演示

countingsort

2. JavaScript 代码实现

function countingSort(arr, maxValue) {
    var bucket = new Array(maxValue+1),
        sortedIndex = 0;
        arrLen = arr.length,
        bucketLen = maxValue + 1;

    for (var i = 0; i < arrLen; i++) {
        if (!bucket[arr[i]]) {
            bucket[arr[i]] = 0;
        }
        bucket[arr[i]]++;
    }

    for (var j = 0; j < bucketLen; j++) {
        while(bucket[j] > 0) {
            arr[sortedIndex++] = j;
            bucket[j]--;
        }
    }

    return arr;
}

3. Python 代码实现

def countingSort(arr, maxValue):
    bucketLen = maxValue+1
    bucket = [0]*bucketLen
    sortedIndex =0
    arrLen = len(arr)
    for i in range(arrLen):
        if not bucket[arr[i]]:
            bucket[arr[i]]=0
        bucket[arr[i]]+=1
    for j in range(bucketLen):
        while bucket[j]>0:
            arr[sortedIndex] = j
            sortedIndex+=1
            bucket[j]-=1
    return arr

4. Go 代码实现

func countingSort(arr []int, maxValue int) []int {
	bucketLen := maxValue + 1
	bucket := make([]int, bucketLen) // 初始为0的数组

	sortedIndex := 0
	length := len(arr)

	for i := 0; i < length; i++ {
		bucket[arr[i]] += 1
	}

	for j := 0; j < bucketLen; j++ {
		for bucket[j] > 0 {
			arr[sortedIndex] = j
			sortedIndex += 1
			bucket[j] -= 1
		}
	}

	return arr
}

桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:

  1. 在额外空间充足的情况下,尽量增大桶的数量
  2. 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中

同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。

1. 什么时候最快

当输入的数据可以均匀的分配到每一个桶中。

2. 什么时候最慢

当输入的数据被分配到了同一个桶中。

3. JavaScript 代码实现

function bucketSort(arr, bucketSize) {
    if (arr.length === 0) {
      return arr;
    }

    var i;
    var minValue = arr[0];
    var maxValue = arr[0];
    for (i = 1; i < arr.length; i++) {
      if (arr[i] < minValue) {
          minValue = arr[i];                // 输入数据的最小值
      } else if (arr[i] > maxValue) {
          maxValue = arr[i];                // 输入数据的最大值
      }
    }

    //桶的初始化
    var DEFAULT_BUCKET_SIZE = 5;            // 设置桶的默认数量为5
    bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
    var bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;   
    var buckets = new Array(bucketCount);
    for (i = 0; i < buckets.length; i++) {
        buckets[i] = [];
    }

    //利用映射函数将数据分配到各个桶中
    for (i = 0; i < arr.length; i++) {
        buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
    }

    arr.length = 0;
    for (i = 0; i < buckets.length; i++) {
        insertionSort(buckets[i]);                      // 对每个桶进行排序,这里使用了插入排序
        for (var j = 0; j < buckets[i].length; j++) {
            arr.push(buckets[i][j]);                      
        }
    }

    return arr;
}

基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

1. 基数排序 vs 计数排序 vs 桶排序

基数排序有两种方法:

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

  • 基数排序:根据键值的每位数字来分配桶;
  • 计数排序:每个桶只存储单一键值;
  • 桶排序:每个桶存储一定范围的数值;

2. LSD 基数排序动图演示

radixsort

3. JavaScript 代码实现

//LSD Radix Sort
var counter = [];
function radixSort(arr, maxDigit) {
    var mod = 10;
    var dev = 1;
    for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
        for(var j = 0; j < arr.length; j++) {
            var bucket = parseInt((arr[j] % mod) / dev);
            if(counter[bucket]==null) {
                counter[bucket] = [];
            }
            counter[bucket].push(arr[j]);
        }
        var pos = 0;
        for(var j = 0; j < counter.length; j++) {
            var value = null;
            if(counter[j]!=null) {
                while ((value = counter[j].shift()) != null) {
                      arr[pos++] = value;
                }
          }
        }
    }
    return arr;
}

参考文档

大厂面试题分享:如何让(a===1&&a===2&&a===3)的值为true?

当我第一次看到这一题目的时候,我是比较震惊的,分析了下很不合我们编程的常理,并认为不大可能,变量a要在同一情况下要同时等于1,2和3这三个值,这是天方夜谭吧,不亚于哥德巴赫1+1=1的猜想吧,不过一切皆有可能,出于好奇心,想了许久之后我还是决定尝试解决的办法。

我的思路来源于更早前遇到的另外一题相似的面试题:

// 设置一个函数输出一下的值
f(1) = 1;
f(1)(2) = 2;
f(1)(2)(3) = 6;

当时的解决办法是使用toString或者valueOf实现的,那我们先回顾下toStringvalueOf方法,方便我们更深入去了解这类型的问题:

比如我们有一个对象,在不重写toString()方法和valueOf()方法的情况下,在 Node 或者浏览器输出的结果是这样的

class Person {
  constructor() {
    this.name = name;
  }
}

const best = new Person("Kobe");
console.log(best); // log: Person {name: "Kobe"}
console.log(best.toString()); // log: [object Object]
console.log(best.valueOf()); // log: Person {name: "Kobe"}
console.log(best + "GiGi"); // log: [object Object]GiGi
best Person
best.toString() [object Object]
best.valueOf() Person
best + 'GiGi' [object Object]GiGi

从上面的输出我们可以观察到一个细节,toString()输出的是[object Object],而valueOf()输出的是Person对象本身,而当运算到best + 'GiGi'的时候竟然是输出了[object Object]GiGi,我们可以初步推断是对象调用的toString()方法得到的字符串进行计算的,难道是运算符+的鬼斧神工吗?

为了验证我们上一步的推断,我们稍微做一点改变,把 valueOf 方法进行一次复写:

class Person {
  constructor(name) {
    this.name = name;
  }
  // 复写 valueOf 方法
  valueOf() {
    return this.name;
  }
}
best Person
best.toString() [object Object]
best.valueOf() Person
best + 'GiGi' KobeGiGi

这次跟上面只有一处产生了不一样的结果,那就是最后的best + 'GiGi'前后两次结果在复写了valueOf()方法之后发生了改变,从中我们可以看出来,对象的本质其实没有发生根本的改变,但是当它被用作直接运算的时候,它的值是从复写的valueOf()中获取的,并继续参与后续的运算。

当然不要忘了我们还有个toString()方法,所以我们也复写它,看看结果会不会也受影响:

class Person {
  constructor(name) {
    this.name = name;
  }
  valueOf() {
    return this.name;
  }
  toString() {
    return `Bye ${this.name}`;
  }
}
best Person
best.toString() Bye Kobe
best.valueOf() Kobe
best + 'GiGi' KobeGiGi

我们发现 best + 'GiGi'还是没有发生任何改变,还是使用我们上一次复写valueOf()的结果

其实我们重写了valueOf方法,不是一定调用valueOf()的返回值进行计算的。而是valueOf返回的值是基本数据类型时才会按照此值进行计算,如果不是基本数据类型,则将使用toString()方法返回的值进行计算。

class Person {
  constructor(name) {
    this.name = name;
  }
  valueOf() {
    return this.name;
  }
  toString() {
    return `Bye ${this.name}`;
  }
}
const best = new Person({ name: "Kobe" });

console.log(best); // log: Person name: {name: "Kobe"}
console.log(best.toString()); // log: Bye [object Object]
console.log(best.valueOf()); // log: Person {name: "Kobe"}
console.log(best + "GiGi"); // log: [object Object]10
best Person
best.toString() Bye [object Object]
best.valueOf() {name: "Kobe"}
best + 'GiGi' Bye [object Object]GiGi

看上面的例子,现在传入的name是一个对象new Person({ name: "Kobe" }),并不是基本数据类型,所以当执行加法运算的时候取toString()方法返回的值进行计算,当然如果没有valueOf()方法,就会去执行toString()方法。

所以铺垫了这么久,我们就要揭开答案,我们正是使用上面这些原理去解答这一题:

class A {
  constructor(value) {
    this.value = value;
  }
  toString() {
    return this.value++;
  }
}
const a = new A(1);
if (a == 1 && a == 2 && a == 3) {
  console.log("Hi Eno!");
}

这里就比较简单,直接改写toString()方法,由于没有valueOf(),当他做运算判断a == 1的时候会执行toString()的结果。

class A {
  constructor(value) {
    this.value = value;
  }
  valueOf() {
    return this.value++;
  }
}
const a = new A(1);
if (a == 1 && a == 2 && a == 3) {
  console.log("Hi Eno!");
}

当然,你也可以不使用toString,换成valueOf也行,效果也是一样的:

class A {
  constructor(value) {
    this.value = value;
  }
  valueOf() {
    return this.value++;
  }
}

const a = new A(1);
console.log(a);
if (a == 1 && a == 2 && a == 3) {
  console.log("Hi Eno!");
}

所以,当一个对象在做运算的时候(比如加减乘除,判断相等)时候,往往会有valueOf()或者toString的调用问题,这个对象的变量背后通常隐藏着一个函数。

当然下面这题原理其实也是一样的,附上解法:

// 设置一个函数输出一下的值
f(1) = 1;
f(1)(2) = 2;
f(1)(2)(3) = 6;

function f() {
  let args = [...arguments];
  let add = function() {
    args.push(...arguments);
    return add;
  };
  add.toString = function() {
    return args.reduce((a, b) => {
      return a + b;
    });
  };
  return add;
}
console.log(f(1)(2)(3)); // 6

当然还没有结束,这里还会有一些特别的解法,其实在使用对象的时候,如果对象是一个数组的话,那么上面的逻辑还是会成立,但此时的toString()会变成隐式调用join()方法,换句话说,对象中如果是数组,当你不重写其它的toString()方法,其默认实现就是调用数组的join()方法返回值作为toString()的返回值,所以这题又多了一个新的解法,就是在不复写toString()的前提下,复写join()方法,把它变成shift()方法,它能让数组的第一个元素从其中删除,并返回第一个元素的值。

class A extends Array {
  join = this.shift;
}
const a = new A(1, 2, 3);
if (a == 1 && a == 2 && a == 3) {
  console.log("Hi Eno!");
}

我们的探寻之路还没结束,细心的同学会发现我们题目是如何让(a===1&&a===2&&a===3)的值为 true,但是上面都是讨论宽松相等==的情况,在严格相等===的情况下,上面的结果会不同吗?

答案是不一样的,你们可以试试把刚才上面的宽松条件改成严格调试再试一次就知道结果了。

class A extends Array {
  join = this.shift;
}
const a = new A(1, 2, 3);
// == 改成 === 后:
if (a === 1 && a === 2 && a === 3) {
  console.log("Hi Eno!"); // Hi Eno!此时再也没出现过了
}

那么此时的情况又要怎么去解决呢?我们可以考虑一下使用Object.defineProperty来解决,这个因为Vue而被众人熟知的方法,也是现在面试中一个老生常谈的知识点了,我们可以使用它来劫持a变量,当我们获取它的值得时候让它自增,那么问题就可以迎刃而解了:

var value = 1;
Object.defineProperty(window, "a", {
  get() {
    return this.value++;
  }
});

if (a === 1 && a === 2 && a === 3) {
  console.log("Hi Eno!");
}

上面我们就是劫持全局window上面的a,当a每一次做判断的时候都会触发get属性获取值,并且每一次获取值都会触发一次函数实行一次自增,判断三次就自增三次,所以最后会让公式成立。

当然这里还有其他方法,这里再举例一个,比如使用隐藏字符去做障眼法瞒过面试官的:

var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if (aᅠ == 1 && a == 2 && ᅠa == 3) {
  console.log("Hi Eno!");
}

上面这种解法的迷惑性很强,如果不细心会以为是三个一样的a,其实本质上是定义三个不一样的a值,a的前后都有隐藏的字符,所以调试的时候,请复制粘贴上面的代码调试,自己在Chrome手打的话可以用特殊手段让 a 后面放一个或者两个红点实现,并在回车的时候,调试工具会把这些痕迹给隐藏,从而瞒天过海,秀到一时半刻还没反应过来的面试官。

最后,祝愿大家在新的一年找到一份如意的工作,上面的代码在实际情况中基本是不会被运用到的,但是用来探索JS的无限可能是具有启发性的,也建议面试官不要使用这类面试题去难为面试者~

交流

文章同步持续更新,可以微信搜索「 前端遨游 」关注公众号方便你往后阅读,往期文章收录在 https://github.com/Wscats/article
欢迎您的关注和交流😁

截屏2020-03-15上午11 16 34

正则表达式

<style>body {

width: 45em;
border: 1px solid #ddd;
outline: 1300px solid #fff;
margin: 16px auto;
}

body .markdown-body
{
padding: 30px;
}

@font-face {
font-family: fontawesome-mini;
src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAzUABAAAAAAFNgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAABbAAAABwAAAAcZMzaOEdERUYAAAGIAAAAHQAAACAAOQAET1MvMgAAAagAAAA+AAAAYHqhde9jbWFwAAAB6AAAAFIAAAFa4azkLWN2dCAAAAI8AAAAKAAAACgFgwioZnBnbQAAAmQAAAGxAAACZVO0L6dnYXNwAAAEGAAAAAgAAAAIAAAAEGdseWYAAAQgAAAFDgAACMz7eroHaGVhZAAACTAAAAAwAAAANgWEOEloaGVhAAAJYAAAAB0AAAAkDGEGa2htdHgAAAmAAAAAEwAAADBEgAAQbG9jYQAACZQAAAAaAAAAGgsICJBtYXhwAAAJsAAAACAAAAAgASgBD25hbWUAAAnQAAACZwAABOD4no+3cG9zdAAADDgAAABsAAAAmF+yXM9wcmVwAAAMpAAAAC4AAAAusPIrFAAAAAEAAAAAyYlvMQAAAADLVHQgAAAAAM/u9uZ4nGNgZGBg4ANiCQYQYGJgBEJuIGYB8xgABMMAPgAAAHicY2Bm42OcwMDKwMLSw2LMwMDQBqGZihmiwHycoKCyqJjB4YPDh4NsDP+BfNb3DIuAFCOSEgUGRgAKDgt4AAB4nGNgYGBmgGAZBkYGEAgB8hjBfBYGCyDNxcDBwMTA9MHhQ9SHrA8H//9nYACyQyFs/sP86/kX8HtB9UIBIxsDXICRCUgwMaACRoZhDwA3fxKSAAAAAAHyAHABJQB/AIEAdAFGAOsBIwC/ALgAxACGAGYAugBNACcA/wCIeJxdUbtOW0EQ3Q0PA4HE2CA52hSzmZDGe6EFCcTVjWJkO4XlCGk3cpGLcQEfQIFEDdqvGaChpEibBiEXSHxCPiESM2uIojQ7O7NzzpkzS8qRqnfpa89T5ySQwt0GzTb9Tki1swD3pOvrjYy0gwdabGb0ynX7/gsGm9GUO2oA5T1vKQ8ZTTuBWrSn/tH8Cob7/B/zOxi0NNP01DoJ6SEE5ptxS4PvGc26yw/6gtXhYjAwpJim4i4/plL+tzTnasuwtZHRvIMzEfnJNEBTa20Emv7UIdXzcRRLkMumsTaYmLL+JBPBhcl0VVO1zPjawV2ys+hggyrNgQfYw1Z5DB4ODyYU0rckyiwNEfZiq8QIEZMcCjnl3Mn+pED5SBLGvElKO+OGtQbGkdfAoDZPs/88m01tbx3C+FkcwXe/GUs6+MiG2hgRYjtiKYAJREJGVfmGGs+9LAbkUvvPQJSA5fGPf50ItO7YRDyXtXUOMVYIen7b3PLLirtWuc6LQndvqmqo0inN+17OvscDnh4Lw0FjwZvP+/5Kgfo8LK40aA4EQ3o3ev+iteqIq7wXPrIn07+xWgAAAAABAAH//wAPeJyFlctvG1UUh+/12DPN1B7P3JnYjj2Ox4/MuDHxJH5N3UdaEUQLqBIkfQQioJWQ6AMEQkIqsPGCPwA1otuWSmTBhjtps2ADWbJg3EpIXbGouqSbCraJw7kzNo2dRN1cnXN1ZvT7zuuiMEI7ncizyA0URofRBJpCdbQuIFShYY+GZRrxMDVtih5TwQPHtXDFFSIKoWIbuREBjLH27Ny4MsbVx+uOJThavebgVrNRLAiYx06rXsvhxLgWx9xpfHdrs/ekc2Pl2cpPCVEITQpwbj8VQhfXSq2m+Wxqaq2D73Kne5e3NjHqQNj3CRYlJlgUl/jRNP+2Gs2pNYRQiOnmUaQDqm30KqKiTTWPWjboxnTWpvgxjXo0KrtZXAHt7hwIz0YVcj88JnKlJKi3NPAwLyDwZudSmJSMMJFDYaOkaol6XtESx3Gt1VTytdZJ3DCLeaVhVnCBH1fycHTxFXwPX+l2e3d6H/TufGGmMTLTnbSJUdo00zuBswMO/nl3YLeL/wnu9/limCuD3vC54h5NBVz6Li414AI8Vx3iiosKcQXUbrvhFFiYb++HN4DaF4XzFW0fIN4XDWJ3a3XQoq9V8WiyRmdsatV9xUcHims1JloH0YUa090G3Tro3mC6c01f+YwCPquINr1PTaCP6rVTOOmf0GE2dBc7zWIhji3/5MchSuBHgDbU99RMWt3YUNMZMJmx92YP6NsHx/5/M1yvInpnkIOM3Z8fA3JQ2lW1RFC1KaBPDFXNAHYYvGy73aYZZZ3HifbeuiVZCpwA3oQBs0wGPYJbJfg60xrKEbKiNtTe1adwrpBRwlAuQ3q3VRaX0QmQ9a49BTSCuF1MLfQ6+tinOubRBZuWPNoMevGMT+V41KitO1is3D/tpMcq1JHZqDHGs8DoYGDkxJgKjHROeTCmhZvzPm9pod+ltKm4PN7Dyvvldlpsg8D+4AUJZ3F/JBstZz7cbFRxsaAGV6yX/dkcycWf8eS3QlQea+YLjdm3yrOnrhFpUyKVvFE4lpv4bO3Svx/6F/4xmiDu/RT5iI++lko18mY1oX+5UGKR6kmVjM/Zb76yfHtxy+h/SyQ0lLdpdKy/lWB6szatetQJ8nZ80A2Qt6ift6gJeavU3BO4gtxs/KCtNPVibCtYCWY3SIlSBPKXZALXiIR9oZeJ1AuMyxLpHIy/yO7vSiSE+kZvk0ihJ30HgHfzZtEMmvV58x6dtqns0XTAW7Vdm4HJ04OCp/crOO7rd9SGxQAE/mVA9xRN+kVSMRFF6S9JFGUtthkjBA5tFCWc2l4V43Ex9GmUP3SI37Jjmir9KqlaDJ4S4JB3vuM/jzyH1+8MuoZ+QGzfnvPoJb96cZlWjMcKLfgDwB7E634JTY+asjsPzS5CiVnEWY+KsrsIN5rn3mAPjqmQBxGjcGKB9f9ZxY3mYC2L85CJ2FXIxKKyHk+dg0FHbuEc7D5NzWUX32WxFcWNGRAbvwSx0RmIXVDuYySafluQBmzA/ssqJAMLnli+WIC90Gw4lm85wcp0qjArEDPJJV/sSx4P9ungTpgMw5gVC1XO4uULq0s3v1rqLi0vX/z65vlH50f8T/RHmSPTk5xxWBWOluMT6WiOy+tdvWxlV/XQb3o3c6Ssr+r6I708GsX9/nzp1tKFh0s3v7m4vAy/Hnb/KMOvc1wump6Il48K6mGDy02X9Yd65pa+nQIjk76lWxCkG8NBCP0HQS9IpAAAeJxjYGRgYGBhcCrq214Qz2/zlUGenQEEzr/77oug/zewFbB+AHI5GJhAogBwKQ0qeJxjYGRgYH3/P46BgZ0BBNgKGBgZUAEPAE/7At0AAAB4nGNngAB2IGYjhBsYBAAIYADVAAAAAAAAAAAAAFwAyAEeAaACCgKmAx4DggRmAAAAAQAAAAwAagAEAAAAAAACAAEAAgAWAAABAAChAAAAAHiclZI7bxQxFIWPd/JkUYQChEhIyAVKgdBMskm1QkKrRETpQiLRUczueB/K7HhlOxttg8LvoKPgP9DxFxANDR0tHRWi4NjrPIBEgh1p/dm+vufcawNYFWsQmP6e4jSyQB2fI9cwj++RE9wTjyPP4LYoI89iWbyLPIe6+Bh5Hs9rryMv4GbtW+RF3EhuRa7jbrIbeQkPkjdUETOLnL0Kip4FVvAhco1RXyMnSPEz8gzWxE7kWTwUp5HnsCLeR57HW/El8gJWa58iL+JO7UfkOh4l9yMv4UnyEtvQGGECgwF66MNBooF1bGCL1ELB/TYU+ZBRlvsKQ44Se6jQ4a7hef+fh72Crv25kp+8lNWGmeKoOI5jJLb1aGIGvb6TjfWNLdkqdFvJw4l1amjlXtXRZqRN7lSRylZZyhBqpVFWmTEXgWfUrpi/hZOQXdOd4rKuXOtEWT3k5IArPRzTUU5tHKjecZkTpnVbNOnt6jzN8240GD4xtikvZW56043rPMg/dS+dlOceXoR+WPbJ55Dsekq1lJpnypsMUsYOdCW30o103Ytu/lvh+5RWFLfBjm9/N8hJntPhvx92rnoE/kyHdGasGy754kw36vsVf/lFeBi+0COu+cfgQr42G3CRpeLoZ53gmfe3X6rcKt5oVxnptHR9JS8ehVUd5wvvahN2uqxOOpMXapibI5k7Zwbt4xBSaTfoKBufhAnO/uqNcfK8OTs0OQ6l7JIqFjDhYj5WcjevCnI/1DDiI8j4ndWb/5YzDZWh79yomWXeXj7Nnw70/2TIeFPTrlSh89k1ObOSRVZWZfgF0r/zJQB4nG2JUQuCQBCEd07TTg36fb2IyBaLd3vWaUh/vmSJnvpgmG8YcmS8X3Shf3R7QA4OBUocUKHGER5NNbOOEvwc1txnuWkTRb/aPjimJ5vXabI+3VfOiyS15UWvyezM2xiGOPyuMohOH8O8JiO4Af+FsAGNAEuwCFBYsQEBjlmxRgYrWCGwEFlLsBRSWCGwgFkdsAYrXFhZsBQrAAA=) format('woff');
}

@font-face {
font-family: octicons-anchor;
src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAYcAA0AAAAACjQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAABMAAAABwAAAAca8vGTk9TLzIAAAFMAAAARAAAAFZG1VHVY21hcAAAAZAAAAA+AAABQgAP9AdjdnQgAAAB0AAAAAQAAAAEACICiGdhc3AAAAHUAAAACAAAAAj//wADZ2x5ZgAAAdwAAADRAAABEKyikaNoZWFkAAACsAAAAC0AAAA2AtXoA2hoZWEAAALgAAAAHAAAACQHngNFaG10eAAAAvwAAAAQAAAAEAwAACJsb2NhAAADDAAAAAoAAAAKALIAVG1heHAAAAMYAAAAHwAAACABEAB2bmFtZQAAAzgAAALBAAAFu3I9x/Nwb3N0AAAF/AAAAB0AAAAvaoFvbwAAAAEAAAAAzBdyYwAAAADP2IQvAAAAAM/bz7t4nGNgZGFgnMDAysDB1Ml0hoGBoR9CM75mMGLkYGBgYmBlZsAKAtJcUxgcPsR8iGF2+O/AEMPsznAYKMwIkgMA5REMOXicY2BgYGaAYBkGRgYQsAHyGMF8FgYFIM0ChED+h5j//yEk/3KoSgZGNgYYk4GRCUgwMaACRoZhDwCs7QgGAAAAIgKIAAAAAf//AAJ4nHWMMQrCQBBF/0zWrCCIKUQsTDCL2EXMohYGSSmorScInsRGL2DOYJe0Ntp7BK+gJ1BxF1stZvjz/v8DRghQzEc4kIgKwiAppcA9LtzKLSkdNhKFY3HF4lK69ExKslx7Xa+vPRVS43G98vG1DnkDMIBUgFN0MDXflU8tbaZOUkXUH0+U27RoRpOIyCKjbMCVejwypzJJG4jIwb43rfl6wbwanocrJm9XFYfskuVC5K/TPyczNU7b84CXcbxks1Un6H6tLH9vf2LRnn8Ax7A5WQAAAHicY2BkYGAA4teL1+yI57f5ysDNwgAC529f0kOmWRiYVgEpDgYmEA8AUzEKsQAAAHicY2BkYGB2+O/AEMPCAAJAkpEBFbAAADgKAe0EAAAiAAAAAAQAAAAEAAAAAAAAKgAqACoAiAAAeJxjYGRgYGBhsGFgYgABEMkFhAwM/xn0QAIAD6YBhwB4nI1Ty07cMBS9QwKlQapQW3VXySvEqDCZGbGaHULiIQ1FKgjWMxknMfLEke2A+IJu+wntrt/QbVf9gG75jK577Lg8K1qQPCfnnnt8fX1NRC/pmjrk/zprC+8D7tBy9DHgBXoWfQ44Av8t4Bj4Z8CLtBL9CniJluPXASf0Lm4CXqFX8Q84dOLnMB17N4c7tBo1AS/Qi+hTwBH4rwHHwN8DXqQ30XXAS7QaLwSc0Gn8NuAVWou/gFmnjLrEaEh9GmDdDGgL3B4JsrRPDU2hTOiMSuJUIdKQQayiAth69r6akSSFqIJuA19TrzCIaY8sIoxyrNIrL//pw7A2iMygkX5vDj+G+kuoLdX4GlGK/8Lnlz6/h9MpmoO9rafrz7ILXEHHaAx95s9lsI7AHNMBWEZHULnfAXwG9/ZqdzLI08iuwRloXE8kfhXYAvE23+23DU3t626rbs8/8adv+9DWknsHp3E17oCf+Z48rvEQNZ78paYM38qfk3v/u3l3u3GXN2Dmvmvpf1Srwk3pB/VSsp512bA/GG5i2WJ7wu430yQ5K3nFGiOqgtmSB5pJVSizwaacmUZzZhXLlZTq8qGGFY2YcSkqbth6aW1tRmlaCFs2016m5qn36SbJrqosG4uMV4aP2PHBmB3tjtmgN2izkGQyLWprekbIntJFing32a5rKWCN/SdSoga45EJykyQ7asZvHQ8PTm6cslIpwyeyjbVltNikc2HTR7YKh9LBl9DADC0U/jLcBZDKrMhUBfQBvXRzLtFtjU9eNHKin0x5InTqb8lNpfKv1s1xHzTXRqgKzek/mb7nB8RZTCDhGEX3kK/8Q75AmUM/eLkfA+0Hi908Kx4eNsMgudg5GLdRD7a84npi+YxNr5i5KIbW5izXas7cHXIMAau1OueZhfj+cOcP3P8MNIWLyYOBuxL6DRylJ4cAAAB4nGNgYoAALjDJyIAOWMCiTIxMLDmZedkABtIBygAAAA==) format('woff');
}

.markdown-body {
font-family: sans-serif;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
color: #333333;
overflow: hidden;
font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif;
font-size: 16px;
line-height: 1.6;
word-wrap: break-word;
}

.markdown-body a {
background: transparent;
}

.markdown-body a:active,
.markdown-body a:hover {
outline: 0;
}

.markdown-body b,
.markdown-body strong {
font-weight: bold;
}

.markdown-body mark {
background: #ff0;
color: #000;
font-style: italic;
font-weight: bold;
}

.markdown-body sub,
.markdown-body sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
.markdown-body sup {
top: -0.5em;
}
.markdown-body sub {
bottom: -0.25em;
}

.markdown-body h1 {
font-size: 2em;
margin: 0.67em 0;
}

.markdown-body img {
border: 0;
}

.markdown-body hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}

.markdown-body pre {
overflow: auto;
}

.markdown-body code,
.markdown-body kbd,
.markdown-body pre,
.markdown-body samp {
font-family: monospace, monospace;
font-size: 1em;
}

.markdown-body input {
color: inherit;
font: inherit;
margin: 0;
}

.markdown-body html input[disabled] {
cursor: default;
}

.markdown-body input {
line-height: normal;
}

.markdown-body input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}

.markdown-body table {
border-collapse: collapse;
border-spacing: 0;
}

.markdown-body td,
.markdown-body th {
padding: 0;
}

.markdown-body .codehilitetable {
border: 0;
border-spacing: 0;
}

.markdown-body .codehilitetable tr {
border: 0;
}

.markdown-body .codehilitetable pre,
.markdown-body .codehilitetable div.codehilite {
margin: 0;
}

.markdown-body .linenos,
.markdown-body .code,
.markdown-body .codehilitetable td {
border: 0;
padding: 0;
}

.markdown-body td:not(.linenos) .linenodiv {
padding: 0 !important;
}

.markdown-body .code {
width: 100%;
}

.markdown-body .linenos div pre,
.markdown-body .linenodiv pre,
.markdown-body .linenodiv {
border: 0;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
-webkit-border-top-left-radius: 3px;
-webkit-border-bottom-left-radius: 3px;
-moz-border-radius-topleft: 3px;
-moz-border-radius-bottomleft: 3px;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}

.markdown-body .code div pre,
.markdown-body .code div {
border: 0;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
-webkit-border-top-right-radius: 3px;
-webkit-border-bottom-right-radius: 3px;
-moz-border-radius-topright: 3px;
-moz-border-radius-bottomright: 3px;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}

.markdown-body * {
-moz-box-sizing: border-box;
box-sizing: border-box;
}

.markdown-body input {
font: 13px Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
line-height: 1.4;
}

.markdown-body a {
color: #4183c4;
text-decoration: none;
}

.markdown-body a:hover,
.markdown-body a:focus,
.markdown-body a:active {
text-decoration: underline;
}

.markdown-body hr {
height: 0;
margin: 15px 0;
overflow: hidden;
background: transparent;
border: 0;
border-bottom: 1px solid #ddd;
}

.markdown-body hr:before,
.markdown-body hr:after {
display: table;
content: " ";
}

.markdown-body hr:after {
clear: both;
}

.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 15px;
margin-bottom: 15px;
line-height: 1.1;
}

.markdown-body h1 {
font-size: 30px;
}

.markdown-body h2 {
font-size: 21px;
}

.markdown-body h3 {
font-size: 16px;
}

.markdown-body h4 {
font-size: 14px;
}

.markdown-body h5 {
font-size: 12px;
}

.markdown-body h6 {
font-size: 11px;
}

.markdown-body blockquote {
margin: 0;
}

.markdown-body ul,
.markdown-body ol {
padding: 0;
margin-top: 0;
margin-bottom: 0;
}

.markdown-body ol ol,
.markdown-body ul ol {
list-style-type: lower-roman;
}

.markdown-body ul ul ol,
.markdown-body ul ol ol,
.markdown-body ol ul ol,
.markdown-body ol ol ol {
list-style-type: lower-alpha;
}

.markdown-body dd {
margin-left: 0;
}

.markdown-body code,
.markdown-body pre,
.markdown-body samp {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px;
}

.markdown-body pre {
margin-top: 0;
margin-bottom: 0;
}

.markdown-body kbd {
background-color: #e7e7e7;
background-image: -moz-linear-gradient(#fefefe, #e7e7e7);
background-image: -webkit-linear-gradient(#fefefe, #e7e7e7);
background-image: linear-gradient(#fefefe, #e7e7e7);
background-repeat: repeat-x;
border-radius: 2px;
border: 1px solid #cfcfcf;
color: #000;
padding: 3px 5px;
line-height: 10px;
font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
display: inline-block;
}

.markdown-body>*:first-child {
margin-top: 0 !important;
}

.markdown-body>*:last-child {
margin-bottom: 0 !important;
}

.markdown-body .headeranchor-link {
position: absolute;
top: 0;
bottom: 0;
left: 0;
display: block;
padding-right: 6px;
padding-left: 30px;
margin-left: -30px;
}

.markdown-body .headeranchor-link:focus {
outline: none;
}

.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
position: relative;
margin-top: 1em;
margin-bottom: 16px;
font-weight: bold;
line-height: 1.4;
}

.markdown-body h1 .headeranchor,
.markdown-body h2 .headeranchor,
.markdown-body h3 .headeranchor,
.markdown-body h4 .headeranchor,
.markdown-body h5 .headeranchor,
.markdown-body h6 .headeranchor {
display: none;
color: #000;
vertical-align: middle;
}

.markdown-body h1:hover .headeranchor-link,
.markdown-body h2:hover .headeranchor-link,
.markdown-body h3:hover .headeranchor-link,
.markdown-body h4:hover .headeranchor-link,
.markdown-body h5:hover .headeranchor-link,
.markdown-body h6:hover .headeranchor-link {
height: 1em;
padding-left: 8px;
margin-left: -30px;
line-height: 1;
text-decoration: none;
}

.markdown-body h1:hover .headeranchor-link .headeranchor,
.markdown-body h2:hover .headeranchor-link .headeranchor,
.markdown-body h3:hover .headeranchor-link .headeranchor,
.markdown-body h4:hover .headeranchor-link .headeranchor,
.markdown-body h5:hover .headeranchor-link .headeranchor,
.markdown-body h6:hover .headeranchor-link .headeranchor {
display: inline-block;
}

.markdown-body h1 {
padding-bottom: 0.3em;
font-size: 2.25em;
line-height: 1.2;
border-bottom: 1px solid #eee;
}

.markdown-body h2 {
padding-bottom: 0.3em;
font-size: 1.75em;
line-height: 1.225;
border-bottom: 1px solid #eee;
}

.markdown-body h3 {
font-size: 1.5em;
line-height: 1.43;
}

.markdown-body h4 {
font-size: 1.25em;
}

.markdown-body h5 {
font-size: 1em;
}

.markdown-body h6 {
font-size: 1em;
color: #777;
}

.markdown-body p,
.markdown-body blockquote,
.markdown-body ul,
.markdown-body ol,
.markdown-body dl,
.markdown-body table,
.markdown-body pre,
.markdown-body .admonition {
margin-top: 0;
margin-bottom: 16px;
}

.markdown-body hr {
height: 4px;
padding: 0;
margin: 16px 0;
background-color: #e7e7e7;
border: 0 none;
}

.markdown-body ul,
.markdown-body ol {
padding-left: 2em;
}

.markdown-body ul ul,
.markdown-body ul ol,
.markdown-body ol ol,
.markdown-body ol ul {
margin-top: 0;
margin-bottom: 0;
}

.markdown-body li>p {
margin-top: 16px;
}

.markdown-body dl {
padding: 0;
}

.markdown-body dl dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: bold;
}

.markdown-body dl dd {
padding: 0 16px;
margin-bottom: 16px;
}

.markdown-body blockquote {
padding: 0 15px;
color: #777;
border-left: 4px solid #ddd;
}

.markdown-body blockquote>:first-child {
margin-top: 0;
}

.markdown-body blockquote>:last-child {
margin-bottom: 0;
}

.markdown-body table {
display: block;
width: 100%;
overflow: auto;
word-break: normal;
word-break: keep-all;
}

.markdown-body table th {
font-weight: bold;
}

.markdown-body table th,
.markdown-body table td {
padding: 6px 13px;
border: 1px solid #ddd;
}

.markdown-body table tr {
background-color: #fff;
border-top: 1px solid #ccc;
}

.markdown-body table tr:nth-child(2n) {
background-color: #f8f8f8;
}

.markdown-body img {
max-width: 100%;
-moz-box-sizing: border-box;
box-sizing: border-box;
}

.markdown-body code,
.markdown-body samp {
padding: 0;
padding-top: 0.2em;
padding-bottom: 0.2em;
margin: 0;
font-size: 85%;
background-color: rgba(0,0,0,0.04);
border-radius: 3px;
}

.markdown-body code:before,
.markdown-body code:after {
letter-spacing: -0.2em;
content: "\00a0";
}

.markdown-body pre>code {
padding: 0;
margin: 0;
font-size: 100%;
word-break: normal;
white-space: pre;
background: transparent;
border: 0;
}

.markdown-body .codehilite {
margin-bottom: 16px;
}

.markdown-body .codehilite pre,
.markdown-body pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f7f7f7;
border-radius: 3px;
}

.markdown-body .codehilite pre {
margin-bottom: 0;
word-break: normal;
}

.markdown-body pre {
word-wrap: normal;
}

.markdown-body pre code {
display: inline;
max-width: initial;
padding: 0;
margin: 0;
overflow: initial;
line-height: inherit;
word-wrap: normal;
background-color: transparent;
border: 0;
}

.markdown-body pre code:before,
.markdown-body pre code:after {
content: normal;
}

/* Admonition */
.markdown-body .admonition {
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
position: relative;
border-radius: 3px;
border: 1px solid #e0e0e0;
border-left: 6px solid #333;
padding: 10px 10px 10px 30px;
}

.markdown-body .admonition table {
color: #333;
}

.markdown-body .admonition p {
padding: 0;
}

.markdown-body .admonition-title {
font-weight: bold;
margin: 0;
}

.markdown-body .admonition>.admonition-title {
color: #333;
}

.markdown-body .attention>.admonition-title {
color: #a6d796;
}

.markdown-body .caution>.admonition-title {
color: #d7a796;
}

.markdown-body .hint>.admonition-title {
color: #96c6d7;
}

.markdown-body .danger>.admonition-title {
color: #c25f77;
}

.markdown-body .question>.admonition-title {
color: #96a6d7;
}

.markdown-body .note>.admonition-title {
color: #d7c896;
}

.markdown-body .admonition:before,
.markdown-body .attention:before,
.markdown-body .caution:before,
.markdown-body .hint:before,
.markdown-body .danger:before,
.markdown-body .question:before,
.markdown-body .note:before {
font: normal normal 16px fontawesome-mini;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
line-height: 1.5;
color: #333;
position: absolute;
left: 0;
top: 0;
padding-top: 10px;
padding-left: 10px;
}

.markdown-body .admonition:before {
content: "\f056\00a0";
color: 333;
}

.markdown-body .attention:before {
content: "\f058\00a0";
color: #a6d796;
}

.markdown-body .caution:before {
content: "\f06a\00a0";
color: #d7a796;
}

.markdown-body .hint:before {
content: "\f05a\00a0";
color: #96c6d7;
}

.markdown-body .danger:before {
content: "\f057\00a0";
color: #c25f77;
}

.markdown-body .question:before {
content: "\f059\00a0";
color: #96a6d7;
}

.markdown-body .note:before {
content: "\f040\00a0";
color: #d7c896;
}

.markdown-body .admonition::after {
content: normal;
}

.markdown-body .attention {
border-left: 6px solid #a6d796;
}

.markdown-body .caution {
border-left: 6px solid #d7a796;
}

.markdown-body .hint {
border-left: 6px solid #96c6d7;
}

.markdown-body .danger {
border-left: 6px solid #c25f77;
}

.markdown-body .question {
border-left: 6px solid #96a6d7;
}

.markdown-body .note {
border-left: 6px solid #d7c896;
}

.markdown-body .admonition>*:first-child {
margin-top: 0 !important;
}

.markdown-body .admonition>*:last-child {
margin-bottom: 0 !important;
}

/* progress bar*/
.markdown-body .progress {
display: block;
width: 300px;
margin: 10px 0;
height: 24px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
background-color: #ededed;
position: relative;
box-shadow: inset -1px 1px 3px rgba(0, 0, 0, .1);
}

.markdown-body .progress-label {
position: absolute;
text-align: center;
font-weight: bold;
width: 100%; margin: 0;
line-height: 24px;
color: #333;
text-shadow: 1px 1px 0 #fefefe, -1px -1px 0 #fefefe, -1px 1px 0 #fefefe, 1px -1px 0 #fefefe, 0 1px 0 #fefefe, 0 -1px 0 #fefefe, 1px 0 0 #fefefe, -1px 0 0 #fefefe, 1px 1px 2px #000;
-webkit-font-smoothing: antialiased !important;
white-space: nowrap;
overflow: hidden;
}

.markdown-body .progress-bar {
height: 24px;
float: left;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
background-color: #96c6d7;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5), inset 0 -1px 0 rgba(0, 0, 0, .1);
background-size: 30px 30px;
background-image: -webkit-linear-gradient(
135deg, rgba(255, 255, 255, .4) 27%,
transparent 27%,
transparent 52%, rgba(255, 255, 255, .4) 52%,
rgba(255, 255, 255, .4) 77%,
transparent 77%, transparent
);
background-image: -moz-linear-gradient(
135deg,
rgba(255, 255, 255, .4) 27%, transparent 27%,
transparent 52%, rgba(255, 255, 255, .4) 52%,
rgba(255, 255, 255, .4) 77%, transparent 77%,
transparent
);
background-image: -ms-linear-gradient(
135deg,
rgba(255, 255, 255, .4) 27%, transparent 27%,
transparent 52%, rgba(255, 255, 255, .4) 52%,
rgba(255, 255, 255, .4) 77%, transparent 77%,
transparent
);
background-image: -o-linear-gradient(
135deg,
rgba(255, 255, 255, .4) 27%, transparent 27%,
transparent 52%, rgba(255, 255, 255, .4) 52%,
rgba(255, 255, 255, .4) 77%, transparent 77%,
transparent
);
background-image: linear-gradient(
135deg,
rgba(255, 255, 255, .4) 27%, transparent 27%,
transparent 52%, rgba(255, 255, 255, .4) 52%,
rgba(255, 255, 255, .4) 77%, transparent 77%,
transparent
);
}

.markdown-body .progress-100plus .progress-bar {
background-color: #a6d796;
}

.markdown-body .progress-80plus .progress-bar {
background-color: #c6d796;
}

.markdown-body .progress-60plus .progress-bar {
background-color: #d7c896;
}

.markdown-body .progress-40plus .progress-bar {
background-color: #d7a796;
}

.markdown-body .progress-20plus .progress-bar {
background-color: #d796a6;
}

.markdown-body .progress-0plus .progress-bar {
background-color: #c25f77;
}

.markdown-body .candystripe-animate .progress-bar{
-webkit-animation: animate-stripes 3s linear infinite;
-moz-animation: animate-stripes 3s linear infinite;
animation: animate-stripes 3s linear infinite;
}

@-webkit-keyframes animate-stripes {
0% {
background-position: 0 0;
}

100% {
background-position: 60px 0;
}
}

@-moz-keyframes animate-stripes {
0% {
background-position: 0 0;
}

100% {
background-position: 60px 0;
}
}

@Keyframes animate-stripes {
0% {
background-position: 0 0;
}

100% {
background-position: 60px 0;
}
}

.markdown-body .gloss .progress-bar {
box-shadow:
inset 0 4px 12px rgba(255, 255, 255, .7),
inset 0 -12px 0 rgba(0, 0, 0, .05);
}

/* Multimarkdown Critic Blocks */
.markdown-body .critic_mark {
background: #ff0;
}

.markdown-body .critic_delete {
color: #c82829;
text-decoration: line-through;
}

.markdown-body .critic_insert {
color: #718c00 ;
text-decoration: underline;
}

.markdown-body .critic_comment {
color: #8e908c;
font-style: italic;
}

.markdown-body .headeranchor {
font: normal normal 16px octicons-anchor;
line-height: 1;
display: inline-block;
text-decoration: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

.headeranchor:before {
content: '\f05c';
}

.markdown-body .task-list-item {
list-style-type: none;
}

.markdown-body .task-list-item+.task-list-item {
margin-top: 3px;
}

.markdown-body .task-list-item input {
margin: 0 4px 0.25em -20px;
vertical-align: middle;
}

/* Media */
@media only screen and (min-width: 480px) {
.markdown-body {
font-size:14px;
}
}

@media only screen and (min-width: 768px) {
.markdown-body {
font-size:16px;
}
}

@media print {
.markdown-body * {
background: transparent !important;
color: black !important;
filter:none !important;
-ms-filter: none !important;
}

.markdown-body {
font-size:12pt;
max-width:100%;
outline:none;
border: 0;
}

.markdown-body a,
.markdown-body a:visited {
text-decoration: underline;
}

.markdown-body .headeranchor-link {
display: none;
}

.markdown-body a[href]:after {
content: " (" attr(href) ")";
}

.markdown-body abbr[title]:after {
content: " (" attr(title) ")";
}

.markdown-body .ir a:after,
.markdown-body a[href^="javascript:"]:after,
.markdown-body a[href^="#"]:after {
content: "";
}

.markdown-body pre {
white-space: pre;
white-space: pre-wrap;
word-wrap: break-word;
}

.markdown-body pre,
.markdown-body blockquote {
border: 1px solid #999;
padding-right: 1em;
page-break-inside: avoid;
}

.markdown-body .progress,
.markdown-body .progress-bar {
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}

.markdown-body .progress {
border: 1px solid #ddd;
}

.markdown-body .progress-bar {
height: 22px;
border-right: 1px solid #ddd;
}

.markdown-body tr,
.markdown-body img {
page-break-inside: avoid;
}

.markdown-body img {
max-width: 100% !important;
}

.markdown-body p,
.markdown-body h2,
.markdown-body h3 {
orphans: 3;
widows: 3;
}

.markdown-body h2,
.markdown-body h3 {
page-break-after: avoid;
}
}
</style><title>RegExp</title>

正则表达式

了解正则表达式

  • 什么是正则表达式
    正则表达式(regular expression)是一个描述字符模式的对象。

  • 为什么要使用正则表达式
    正则表达式能够进行强大的“模式匹配”和“文本检索与替换”功能。前端往往有大量的表单数据校验的工作,采用正则表达式会使得数据校验的工作量大大减轻

创建正则表达式

使用RegExp构造函数,

  • 第一个参数就是我们的模式“字符串”

    var reg= new RegExp('study');
    

    //使用特殊字符
    var reg= new RegExp('\d\w+');\d\w+

  • 第二个参数可选,模式修饰符

    • i: case-insensitive,表示忽略大小写
    • g: global,表示全局匹配
    • m: multiline,表示多行匹配
    var reg = new RegExp('study', 'ig');
    

还可以用直接量方式直接声明

var reg = /study/gi;
  • 直接量是字符匹配,不支持变量

使用正则表达式

支持正则表达式的字符串方法

  • search
    返回第一次匹配时所在的索引值,如果匹配不到则返回-1
  • match
    • 默认匹配字符串,返回一个数组
      • 0:所匹配的字符
      • index:匹配第一个字符所在的索引
      • input:对字符串的引用
    • 全局匹配(g),返回一个匹配所有字符串数组
    • 如果匹配不到则返回null
  • replace
    替换字符串
  • split
    'a,b ,c , d, e'.split(/\s*,\s* /);

正则表达式的属性和方法

  • 测试正则表达式用test方法,返回布尔值
    • 格式:正则表达式.test(字符串)
    • 用<正则表达式>测试<字符串>是否匹配,返回true/false
  • 测试正则表达式exec方法
    /xx/.exec(字符串)
    

匹配规则

  • 所有字母和数字都是按照字面量进行匹配,和字符串匹配等效
    /good/gi

  • 字符类(只记小写字母即可)

    • . : 除换行以外的字符
    • \w : 代表数字或字母或下划线
    • \W : 非数字字母和下划线字符
    • \d : 数字
    • \D : 非数字
    • \s : 代表一个空格
    • \S : 空格以外的字符
    • \b : 匹配一个单词边界,也就是指单词和空格间的位置
    • \B : 匹配非单词边界。

      PS:以上所有字符类都只是匹配“一个”字符

  • 特殊符号

    ^ $ . * + ? = ! : | \ / () [] {}

    • []: 代表任意“单个字符” ,里面的内容表示“或”的关系

      • -: 代表范围
      • ^: 代表非
    • (): 表示分组(n是以最左边括号出现的顺序排列)

      • $1: 表示第一个分组
      • $n: 表示第n个分组(不能写在正则表达式里)
      • \n: 在正则分组后面使用,表示对第n个分组的引用(一定要写在正则表达式里)

        PS: 编写的正则分组数量越少越好

    • |: 表示或者

    • 锚点定位

      • ^: 表示以什么开头
      • $: 表示以什么结尾
    • 表示数量,对前一个字符计数,

      • *: 代表0个或0个以上 <===>{0,}
      • +: 代表1个或1个以上 <===>{1,}
      • ?: 代表0个或1个 <===>{0,1}
      • {}:
        \d{5}: 匹配5个数字
        \d{5,10}: 匹配5个到10个数字
        \d{5,}: 匹配5个或5个以上的数字
        

      PS:
      1)数量词*,+,{5,},会尽可能多的去匹配结果(贪婪)
      2)在后面加一个?表示尽可能少的去匹配结果(非贪婪)
      google,goooogle ==> /go+/


[案例]

  • 快速替换html标签
  • 去除首尾空格
  • 邮政编码检测
  • 文件格式检测
  • 邮箱格式检测

[练习]

  1. 提取手机号码
  2. 表单验证
    • 验证账号
      • 不能为空,
      • 不能使用特殊字符(数字、字母、下划线),
      • 长度6-20
    • 昵称只能输入中文

      Unicode编码中的汉字范围 /^[\u2E80-\u9FFF]+$/

    • 电子邮件
    • 密码
      • 长度小于20
      • 不能包含空格
    • 身份证
      18/15位
      445655 19900707 2165
      445655 19900707 211x
      
    • 手机号码
    • 生日
      1999/05/08
      1999-5-8
      19990508
      

[作业]

Alt text

  1. 表单验证
    • 登录名
      • 必填
      • 数字或字母组合
      • 不能少于3位
    • 昵称
      • 中英文皆可
    • 电子邮件
      • 必填
    • 密码
      • 必填
      • 至少6位
      • 显示密码强度
        • 弱:只有数字
        • 一般:数字字母组合
        • 强:数字字母特殊字符组合

try catch引发的性能优化深度思考

关键代码拆解成如下图所示(无关部分已省略):

demo

起初我认为可能是这个 getRowDataItemNumberFormat 函数里面某些方法执行太慢,从 formatData.replaceunescape(已废弃,官方建议使用 decodeURI 或者 decodeURIComponent 替代) 方法都怀疑了一遍,发现这些方法都不是该函数运行慢的原因。为了深究原因,我给 style.formatData 传入了不同的值,发现这个函数的运行效率出现不同的表现。开始有点疑惑为什么 style.formatData 的值导致这个函数的运行效率差别如此之大。

进一步最终定位发现如果 style.formatData 为 undefined 的时候,效率骤降,如果 style.formatData 为合法的字符串的时候,效率是正常值。我开始意识到这个问题的原因在那里了,把目光转向了 try catch 代码块,这是一个很可疑的地方,在很早之前曾经听说过不合理的 try catch 是会影响性能的,但是之前从没遇到过,结合了一些资料,我发现比较少案例去探究这类代码片段的性能,我决定写代码去验证下:

window.a = 'a';
window.c = undefined;
function getRowDataItemNumberFormatTryCatch() {
    console.time('getRowDataItemNumberFormatTryCatch');
    for (let i = 0; i < 3000; i++) {
        try {
            a.replace(/%022/g, '"');
        }
        catch (error) {
        }
    }
    console.timeEnd('getRowDataItemNumberFormatTryCatch');
}

我尝试把 try catch 放入一个 for 循环中,让它运行 3000 次,看看它的耗时为多少,我的电脑执行该代码的时间大概是 0.2 ms 左右,这是一个比较快的值,但是这里 a.replace 是正常运行的,也就是 a 是一个字符串能正常运行 replace 方法,所以这里的耗时是正常的。我对他稍微做了一下改变,如下:

function getRowDataItemNumberFormatTryCatch2() {
    console.time('getRowDataItemNumberFormatTryCatch');
    for (let i = 0; i < 3000; i++) {
        try {
            c.replace(/%022/g, '"');
        }
        catch (error) {
        }
    }
    console.timeEnd('getRowDataItemNumberFormatTryCatch');
}

这段代码跟上面代码唯一的区别是,c.replace 此时应该是会报错的,因为 cundefined,这个错误会被 try catch 捕捉到,而上面的代码耗时出现了巨大的变化,上升到 40 ms,相差了将近 200 倍!并且上述代码和首图的 getRowDataItemNumberFormat 函数代码均出现了 Minor GC,注意这个 Minor GC 也是会耗时的。

demo

这可以解释一部分原因了,我们上面运行的代码是一个性能比较关键的部分,不应该使用 try catch 结构,因为该结构是相当独特的。与其他构造不同,它运行时会在当前作用域中创建一个新变量。每次 catch 执行该子句都会发生这种情况,将捕获的异常对象分配给一个变量。

即使在同一作用域内,此变量也不存在于脚本的其他部分中。它在 catch 子句的开头创建,然后在子句末尾销毁。因为此变量是在运行时创建和销毁的(这些都需要额外的耗时!),并且这是 JavaScript 语言的一种特殊情况,所以某些浏览器不能非常有效地处理它,并且在捕获异常的情况下,将捕获处理程序放在性能关键的循环中可能会导致性能问题,这是我们为什么上面会出现 Minor GC 并且会有严重耗时的原因。

如果可能,应在代码中的较高级别上进行异常处理,在这种情况下,异常处理可能不会那么频繁发生,或者可以通过首先检查是否允许所需的操作来避免。上面的 getRowDataItemNumberFormatTryCatch2 函数示例显示的循环,如果里面所需的属性不存在,则该循环可能引发多个异常,为此性能更优的写法应该如下:

function getRowDataItemNumberFormatIf() {
    console.time('getRowDataItemNumberFormatIf');
    for (let i = 0; i < 3000; i++) {
        if (c) {
            c.replace(/%022/g, '"');
        }
    }
    console.timeEnd('getRowDataItemNumberFormatIf')
}

上面的这段代码语义上跟 try catch 其实是相似的,但运行效率迅速下降至 0.04ms,所以 try catch 应该通过检查属性或使用其他适当的单元测试来完全避免使用此构造,因为这些构造会极大地影响性能,因此应尽量减少使用它们。

如果一个函数被重复调用,或者一个循环被重复求值,那么最好避免其中包含这些构造。它们最适合仅执行一次或仅执行几次且不在性能关键代码内执行的代码。尽可能将它们与其他代码隔离,以免影响其性能。

例如,可以将它们放在顶级函数中,或者运行它们一次并存储结果,这样你以后就可以再次使用结果而不必重新运行代码。

demo

getRowDataItemNumberFormat 在经过上述思路改造后,运行效率得到了质的提升,在实测 300 多次循环中减少的时间如下图,足足优化了将近 2s 多的时间,如果是 3000 次的循环,那么它的优化比例会更高:

demo
demo

由于上面的代码是从项目中改造出来演示的,可能并不够直观,所以我重新写了另外一个相似的例子,代码如下,这里面的逻辑和上面的 getRowDataItemNumberFormat 函数讲道理是一致的,但是我让其发生错误的时候进入 catch 逻辑执行任务。

事实上 plus1plus2 函数的代码逻辑是一致的,只有代码语义是不相同,一个是返回 1,另一个是错误抛出1,一个求和方法在 try 片段完成,另一个求和方法再 catch 完成,我们可以粘贴这段代码在浏览器分别去掉不同的注释观察结果。

我们发现 try 片段中的代码运行大约使用了 0.1 ms,而 catch 完成同一个求和逻辑却执行了大约 6 ms,这符合我们上面代码观察的预期,如果把计算范围继续加大,那么这个差距将会更加明显,实测如果计算 300000 次,那么将会由原来的 60 倍差距扩大到 500 倍,那就是说我们执行的 catch 次数越少折损效率越少,而如果我们执行的 catch 次数越多那么折损的效率也会越多。

所以在不得已的情况下使用 try catch 代码块,也要尽量保证少进入到 catch 控制流分支中。

const plus1 = () => 1;
const plus2 = () => { throw 1 };
console.time('sum');
let sum = 0;
for (let i = 0; i < 3000; i++) {
    try {
        // sum += plus1(); // 正确时候 约 0.1ms
        sum += plus2(); // 错误时候 约 6ms
    } catch (error) {
        sum += error;
    }
}
console.timeEnd('sum');

上面的种种表现进一步引发了我对项目性能的一些思考,我搜了下我们这个项目至少存在 800 多个 try catch,糟糕的是我们无法保证所有的 try catch 是不损害代码性能并且有意义的,这里面肯定会隐藏着很多上述类的 try catch 代码块。

从性能的角度来看,目前 V8 引擎确实在积极的通过 try catch 来优化这类代码片段,在以前浏览器版本中上面整个循环即使发生在 try catch 代码块内,它的速度也会变慢,因为以前浏览器版本会默认禁用 try catch 内代码的优化来方便我们调试异常。

try catch 需要遍历某种结构来查找 catch 处理代码,并且通常以某种方式分配异常(例如:需要检查堆栈,查看堆信息,执行分支和回收堆栈)。尽管现在大部分浏览器已经优化了,我们也尽量要避免去写出上面相似的代码,比如以下代码:

try {
    container.innerHTML = "I'm alloyteam";
}
catch (error) {
    // todo
}

上面这类代码我个人更建议写成如下形式,如果你实际上抛出并捕获了一个异常,它可能会变慢,但是由于在大多数情况下上面的代码是没有异常的,因此整体结果会比异常更快。

这是因为代码控制流中没有分支会降低运行速度,换句话说就是这个代码执行没错误的时候,没有在 catch 中浪费你的代码执行时间,我们不应该编写过多的 try catch 这会在我们维护和检查代码的时候提升不必要的成本,有可能分散并浪费我们的注意力。

当我们预感代码片段有可能出错,更应该是集中注意力去处理 successerror 的场景,而非使用 try catch 来保护我们的代码,更多时候 try catch 反而会让我们忽略了代码存在的致命问题。

if (container) container.innerHTML = "I'm alloyteam";
else // todo

在简单代码中应当减少甚至不用 try catch ,我们可以优先考虑 if else 代替,在某些复杂不可测的代码中也应该减少 try catch(比如异步代码),我们看过很多 asyncawait 的示例代码都是结合 try catch 的,在很多性能场景下我认为它并不合理,个人觉得下面的写法应该是更干净,整洁和高效的。

因为 JavaScript 是事件驱动的,虽然一个错误不会停止整个脚本,但如果发生任何错误,它都会出错,捕获和处理该错误几乎没有任何好处,代码主要部分中的 try catch 代码块是无法捕获事件回调中发生的错误。

通常更合理的做法是在回调方法通过第一个参数传递错误信息,或者考虑使用 Promisereject() 来进行处理,也可以参考 node 中的常见写法如下:

;(async () => {
    const [err, data] = await readFile();
    if (err) {
        // todo
    };
})()

fs.readFile('<directory>', (err, data) => {
    if (err) {
        // todo
    }
});

结合了上面的一些分析,我自己做出一些浅显的总结:

    1. 如果我们通过完善一些测试,尽量确保不发生异常,则无需尝试使用 try catch 来捕获异常。
    1. 非异常路径不需要额外的 try catch,确保异常路径在需要考虑性能情况下优先考虑 if else,不考虑性能情况请君随意,而异步可以考虑回调函数返回 error 信息对其处理或者使用 Promse.reject()
    1. 应当适当减少 try catch 使用,也不要用它来保护我们的代码,其可读性和可维护性都不高,当你期望代码是异常时候,不满足上述1,2的情景时候可考虑使用。

最后,笔者希望这篇文章能给到你我一些方向和启发吧,如有疏漏不妥之处,还请不吝赐教!

面试题

写一段脚本,实现:当页面上任意一个链接被点击的时候,alert出这个链接在页面上的顺序号,如第一个链接则alert(1),依次类推。

var links = document.getElementsByTagName('a');
console.log(links);
for (var i = 0; i < links.length; i++) {
    links[i].onclick = (function (i) {
        return function () {
            alert(i)
        }
    })(i)
}
//or
var links = document.getElementsByTagName('a');
console.log(links);
for (let i = 0; i < links.length; i++) {
    links[i].onclick = function (i) {
        alert(i)
    }
}

创建”内置”方法:给String对象定义一个repeatify方法,该方法接收一个整数参数,作为字符串重复的次数,最后返回重复指定次数的字符串。

String.prototype.repeatify = function (num) {
    var str = "";
    for (var i = 0; i < num; i++) {
        str += this;
    }
    return str;
}
console.log("laoxie".repeatify(4))

完成一个函数,接受数组作为参数,数组元素为整数或者数组,数组元素包含整数或数组,函数返回扁平化后的数组,如:[1,[2,[[3,4],5],6]] = > [1,2,3,4,5,6]。

var arr = [1, [2, 3, [4, 5, [6]]], 7];
var newArr = [];

function getNewArr(arr) {
    for (var i = 0; i < arr.length; i++) {
        if (typeof arr[i] == "number") {
            newArr.push(arr[i])
        } else {
            getNewArr(arr[i])
        }
    }
}
getNewArr(arr)

假设现有一篇文章 var content = “…大量文字”,文章触及到一些敏感词[“wscat”,“yao”,“eno”,“6.1”]等,如何在文章中发现这些敏感词,并将背景置为红色或改变字体颜色标识出来。

var str = "6.1号,这是愉快的儿童节,我清晰地记得,愚蠢的yao牵着可爱的eno...";
var keyword = ["老谢", "老蓝", "千锋", "6.1"];
for (var i = 0; i < keyword.length; i++) {
    str = str.split(keyword[i]);
    str = str.join("<span style='color:red'>" + keyword[i] + "</span>")
    console.log(str)
}
document.getElementById('str').innerHTML = str;

不能使用定时器,实现5s刷新一次页面

定时自动刷新,content表示刷新间隔,单位为秒s,下面代码表示页面每隔三秒刷新一次

<meta http-equiv="refresh" content="3">

这种方法实现页面刷新有点投机取巧,这个标签作用是定时跳转,content第一个参数表示间隔时间,单位为秒,第二个参数表示目标网址,但是如果把第二个参数设为#,就会实现页面刷新

<meta http-equiv="refresh" content="3;url=#">

event.currentTarget和event.target的不同

可以发现,由于事件捕获和事件冒泡的存在,很多时候注册的事件监听者event.currentTarget并不是事件的触发者event.target,大部分时候事件可能是由子元素触发的,但是在捕获、冒泡的过程中被父级元素的事件监听器获取到了,注册监听事件的父级元素就是这种情况下event.currentTarget,而事件触发者也就是子元素就是event.target

<head>
	<meta charset="utf-8">
	<title>菜鸟教程(runoob.com)</title>
</head>
<body>
	<p>该实例使用 addEventListener() 方法在按钮中添加点击事件。 </p>
	<button id="myBtn">点我</button>
	<p id="demo"></p>
	<script>
		var btn = document.getElementById("myBtn");
		btn.addEventListener("click", function displayDate1(e) {
			console.log('按钮注册点击事件');
			console.log("e.currentTarget", e.currentTarget);
			console.log("e.target", e.target);
			console.log("this", this)
		});
		document.body.addEventListener("click", function displayDate1(e) {
			console.log('body注册点击事件');
			console.log("e.currentTarget", e.currentTarget);
			console.log("e.target", e.target);
			console.log("this", this)
		});
	</script>
</body>
</html>

css加载会阻塞DOM树渲染吗?

不会

  • css加载不会阻塞DOM树的解析
  • css加载会阻塞DOM树的渲染
  • css加载会阻塞后面js语句的执行

这可能也是浏览器的一种优化机制。因为你加载css的时候,可能会修改下面DOM节点的样式,如果css加载不阻塞DOM树渲染的话,那么当css加载完之后,DOM树可能又得重新重绘或者回流了,这就造成了一些没有必要的损耗。所以我干脆就先把DOM树的结构先解析完,把可以做的工作做完,然后等你css加载完之后,在根据最终的样式来渲染DOM树,这种做法性能方面确实会比较好一点,摘自css加载会造成阻塞吗?

defer和async的区别

<script src="script.js"></script>

没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。

<script async src="script.js"></script>

有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。

<script defer src="myscript.js"></script>

有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。defer和async的区别

image

遍历一个某一元素下的所有子元素(包括子元素的子元素)的方法,打印出所有子元素的ID

假设要遍历document.body

var allNodes = [];
function getChildNode(node) {
    //先找到子结点
    var nodeList = node.childNodes;
    for (var i = 0; i < nodeList.length; i++) {
        //childNode获取到到的节点包含了各种类型的节点
        //但是我们只需要元素节点  通过nodeType去判断当前的这个节点是不是元素节点
        var childNode = nodeList[i];
        //判断是否是元素结点
        if (childNode.nodeType == 1) {
            console.log(childNode.id);
            allNodes.push[childNode];
            //childNode.style.border = "1px solid red";
            getChildNode(childNode);
        }
    }
}
getChildNode(document.body);
//getChildNode("某元素");

f(1)=1, f(1)(2)=2, f(1)(2)(3)=6, 设置一个函数输出一下的值

难点:打印和相加计算,会分别调用toStringvalueOf函数,所以我们重写temp的toStringvalueOf方法,返回a的值,这里测试的时候发现只要改写toString就可以了

function add(a) {
    var temp = function (b) {
        return add(a + b);
    }
    temp.valueOf = temp.toString = function () {
        return a;
    };
    return temp;
}
console.log(add(1)(2));
alert(add(1)(2)(3));

简单讲,在对对象(广义的,包括函数对象)进行+ - * / == > < >= <=运算时,会依次尝试调用私有toString私有valueOf原型toString原型valueOf,直到遇到返回基本类型(如数字或字符串)停止,这里可以参考实现语法的功能:var a = add(1)(2)(3); //6没看懂别人的代码

var temp = function () {
    var a = 1 + 2;//这里执行了加法所以会执行temp.toString方法
    return temp;
};
temp.toString = function () {
    return "wscats";
};
console.log(temp());

从图片可以看出来,当改写了toString后,既返回了函数,还打印出了结果
2018-11-21 3 36 34

实现下面的函数new Test("test").firstSleep(3).sleep(5).eat("dinner")

具体可以参考链式调用

function Test(name) {
    this.task = [];
    let fn = () => {
        console.log(name);
        this.next();
    }
    this.task.push(fn);
    setTimeout(() => {
        this.next();
    }, 0)
    return this;
}

Test.prototype.firstSleep = function (timer) {
    console.time("firstSleep")
    let that = this;
    let fn = () => {
        setTimeout(() => {
            console.timeEnd("firstSleep");
            that.next();
        }, timer * 1000)
    }
    this.task.unshift(fn);
    return this;
}

Test.prototype.sleep = function (timer) {
    console.time("sleep")
    let that = this;
    let fn = () => {
        setTimeout(() => {
            console.timeEnd("sleep");
            that.next();
        }, timer * 1000)
    }
    this.task.push(fn);
    return this;
}

Test.prototype.eat = function (dinner) {
    let that = this;
    let fn = () => {
        console.log(dinner);
        that.next();
    }
    this.task.push(fn);
    return this;
}

Test.prototype.next = function (dinner) {
    let fn = this.task.shift();
    fn && fn()
}

new Test("test").firstSleep(3).sleep(5).eat("dinner")

如何优化if...else语句

决策树和卫语句

const a = 3;  
//bad:
if (a === 1) {
    console.log('a > 1');
}
if (a === 2) {
    console.log('a > 2');
}
if (a === 3) {
    console.log('a > 3');
}
if (a === 4) {
    console.log('a > 4');
}
// good:
switch(a) {
    case 1:
        console.log('a = 1');
    case 2:
        console.log('a = 2');
    case 3:
        console.log('a = 3');
    defaut:
        console.log('blablabla');
}
const judgeMap = {
    1: () => { console.log('a = 1') },
    2: () => { console.log('a = 2') },
    3: () => { console.log('a = 3') },
    4: () => { console.log('a = 4') }
}
judgeMap[a]();

三元表达式

const strength = (password.length > 7) ? 'Strong' : 'Weak';

position:sticky

不用JS情况写滑动到头部固定,参考杀了个回马枪,还是说说position:sticky吧

交换变量

let a;
let b;

[a, b] = [1, 2, 3];

https://dmitripavlutin.com/swap-variables-javascript/

以下表达式 (a== 1 && a ==2 && a==3)可以为真吗?

  • 如果a是一个对象Object,那在执行a==的时候首先会去先执行valueOf方法,如果没有valueOf方法,就会去执行toString方法。(toString方法与valueOf类似,这里不再重复)
const a = { value: 0 }
a.valueOf = function () {
    return  this.value += 1
}
console.log( a == 1 && a == 2 && a == 3 );
  • 如果a是一个数组Array,在数组转换成字符串的时候,数组toString会隐含调用join()方法
const a = [1, 2, 3];
a.join = a.shift;
console.log( a == 1 && a == 2 && a ==3 );
  • 扩展:字符编码实现相同效果
var aᅠ = 1;
var a = 2;
var ᅠa = 3;
console.log(aᅠ === 1 && a === 2 && ᅠa=== 3 );

变量提升

{
    a = 1;
    function a() { };
    a = 2;
    console.log(a); // 2
}
console.log(a); // 1

但是比上面会复杂一点,因为还涉及到块作用域,因为函数写在块作用域里面,由于函数提升,它即能访问局部作用域,也能访问全局作用域,所以此时会在全局作用域和块级作用域都产生一个变量 a

JavaScript 执行到函数所在的那一句把局部变量的内容扔给外面的那个变量

即相当如下的代码:

let outera;
{

    let innera;
    innera = function () { };
    innera = 1;
    // function a() { };
    outera = innera; // JavaScript 执行到函数所在的那一句把局部变量的内容扔给外面的那个变量
    innera = 2;
    console.log(innera); // 2
}
console.log(outera); // 1

HTML入门必备

一、前端开发做什么

  • 传统前端:html+css+js
  • HTML5大前端:各种端的兼容开发、Ajax+服务器端的开发、高级设计模式和框架、网站安全、SEO优化、测试、源代码管理、移动APP和移动站点的开发

二、HTML要点

HTML简介

1.什么是HTML?

超文本标记语言(HyperText Markup Language),在浏览器上运行的一种标记语言;
HTML 不是一种编程语言,而是一种标记语言;
标记语言是一套标记标签 ,HTML 使用标记标签来描述网页 。

2.html的文件命名

必须是小写字母开头,后接下划线、数字、字母,不可使用中文或特殊字符作为文件名。

HTML的结构

1.HTML标签的规范

  • HTML 标签是由尖括号包围的关键词,比如<html>
  • HTML 标签通常是成对出现的,比如 <b></b>
  • 标签对中的第一个标签是开始(开放)标签,第二个标签是结束(闭合)标签
  • HTML也存在单标签,例如<br/>、<img/>

2.Html标签结构初体验(标签、属性、元素、注释)

image

3.结构中的每个标签的含义

(1)<!DOCTYPE>标签

<!DOCTYPE>
  • 声明位于文档中的最前面的位置,处于 标签之前。此标签可告知浏览器文档使用哪种 HTML 或 XHTML 规范
  • 文档类型:
文档类型 分类 备注
HTML HTML Strict DTD 请求比较严格的html类型
HTML Transitional DTD 相对而言比较规范不太严禁
HTML Frameset DTD 框架级的类型
xHTML HTML Strict DTD 请求比较严格的html类型
xHTML HTML Transitional DTD 相对而言比较规范不太严禁
xHTML Frameset DTD 框架级的类型
HTML5

注意:每个页面都必须设置一个doctype,如果不设置,浏览器会默认开启quirks mode(怪异模式)解析(quirks mode是浏览器为了兼容很早之前针对旧版本浏览器设计、并未严格遵循 W3C 标准的网页而产生的一种页面渲染模式)。
(2)<html>告知浏览器这是一个html文件

属性值 作用
lang属性 设置这个页面的主要语言

(3)<head>标签管理所有头部元素的容器
可以存放:<base>, <link>, <meta>, <script>, <style>以及 <title>
(4)<title>标签定义文档的标题
title中的文本在SEO中占有很大的权重。(不可少)
(5)<meta>标签
作用:设置页面描述信息,设置页面关键字,设置页面的编码格式

  • Description:设置页面描述信息【关键信息的比重(前升排名的一种方式。)】
  • Keywords:设置页面关键词。【关健词的比重(前升排名的一种方式。)】
  • 设置页面的编码格式:

例如:

<meta charset=UTF-8">

Charset(字符集格式)常见:

  • UTF-8:国际通用字符集
  • gb2312:亚洲国家使用字符集

HTML标签详解

h系列的标签

image

h系列的标签(Header) 作用
h1,h2,h3,h4,h5,h6 把页面上的文字加上标题的语义,其中h1定义标题的语义化最重

注意:一个页面只能有一个h1标签。

p段落标签

image

p段落标签 作用
p 给页面的上一段文字加上段落的语义

hr标签

image

p段落标签 作用
hr 在页面显示一条横线,默认占整行。hr 元素可用于分隔内容

br标签

image

br段落标签 作用
br 换行

a标签

a:超链接,锚链接。

  • 作用:跳转页面;
  • a标签属性:
属性 属性值
href a标签的跳转目标地址
target 设置连接的跳转方式:target设置连接的跳转方式(1)_blank:保留原始页面,再进行跳转(2)_self:在当前页面进行跳转

补充<base target=“blank”>为页面上所有a标签设置跳转方式(一般放在titile标签下面)

a标签的其它用法

  • 可以不跳转(跳转到当前页面)href=”#”
  • 可以跳转到另外的页面
  • 可以在当前页面进行定位
    设置a标签的href属性为“#id名”
    在页面的指定位置加入一个目标标签(可以是任意标签)
    必须给这个标签设置一个id名:<p id=”mubiao”>这是目标</p>
  • 在跳转的页面进行定位。
  • 可以进行下载(强烈不推荐使用)<a href="1.zip">下载那个神奇的文件</a>

img

标签 作用
img 在页面显示一张图片
属性 属性值
src 图片显示的路径
alt 若图片加载不出来,显示出来文字
title 鼠标上移显示的文字(介绍图片)

涉及到的路径问题
第一种:绝对路径
带有盘符的路径:C:\上课内容\上课视频\firstday\源代码\wscat.jpg
缺点:可移植性不强,一般情况下不直接使用绝对路径。
第二种:相对路径
a.如果页面与图片在同一目录下面:
image
image

b.如果页面在图片一上级目录:
image
image

c.如果图片在页面的上一级目录:
image
image

总结:平时开发一般都是用相对路径:因为相对路径的可移植性要强。

文本格式化标签

(1)b , u , i , s:视觉要素

标签 作用
b(Bold) 加粗
u (Underlined) 下划线
i(Italic) 倾斜
s(Strikethrough) 删除线

没有语义的标签,<i></i>标签经常会用于不需要语义化的位置,例如放一个小图标

(2)strong,em,del,ins:表达要素

标签 作用
strong 加粗,加强语气
ins 下划线
em 倾斜
del 删除线

可以作为语义化标签使用
image

(3)sub、sup标签

标签 作用
sub 定义下标字
ins 定义上标字

预格式文本

HTML 输出空格

  • 当显示页面时,浏览器将所有连续的空格或换行都会被算作一个空格。
  • pre标签:预格式文本,保留原本的结构,保留空行和空格

表格

早期网站开发中,人们喜欢作用表格布局,使用非常的泛滥。到了2008年之后,人们意识到表格的局限性,所以改为用div+css模式。2002.Sina.com.cn、2004.sina.com.cn
作用:用来将数据以表格形式显示出来。
最简单的表格格式:
image

完整的表格格式:
image

常用属性 作用
border 给表格加上了边框
width 给表格设置宽
height 给表格设置高
cellspacing 规定单元格之间的空白
cellpadding 规定单元边沿与其内容之间的空白
align:center,left,right 设置table上,决定表格显示的位置
例如 <table border="1" width="800" height="400" cellspacing="0" cellpadding="0">

列表:显示数据

(1)无序列表(ul):(重点)

标签 作用
ul 显示一列没有排列顺序的数据

image

注意

  • ul标签是用来管理li标签,所以ul中只能出现li
  • li标签是一个容器,可以放其它标签

(2)有序列表(ol):(用的很少)

标签 作用
ol 显示一段有顺序的数据

注意:有序列表中的所有数据都是顺序的
image

(3)自定义列表(dl)

标签 作用
dl,dt,dd 显示一段数据,格式自己定义

image

注意:一个dl中可以有多个dt和多个dd。

表单:收集信息

1.表单标签(<form></form>

<form>
...
  input 元素
...
</form>

属性

  • action="url地址" ,用于指定接收并处理表单数据的服务器程序的url地址
  • method="提交方式" ,设置表单数据的提交方式,其取值为get或post
  • name="表单名称" ,用于指定表单的名称,以区分同一个页面中的多个表单

2. 表单元素(注册页面上能够填写的内容)
作用:用来收集用户的信息,将来提交到服务器。

image

标签 作用 属性
input type text:文本框
input type password:密码框
input type button:按钮
input type reset:重置
input type image:图片提交
input type submit:提交
input type hidden:隐藏域
input type radio:单选框
input type checkbox:多选框
input type hidden:隐藏域
input type submit是提交按钮 起到提交信息的作用
input value 给文本和按钮(text,password,button)用于设置默认值
select option 下拉框
textarea 文本域

注意

  • 如果想要多个radio或者checkbox组成一个选择集合,那么必须给每个radio以及checkbox都设置相同的name属性。
  • 如何给radio,checkbox设置默认值:checked=”checked”
  • 给下拉框设置默认值:selected="selected"

字符实体

往网页中输入特殊字符,需在HTML代码中,转变成以&开头的字母组合或以&#开头的数字,浏览器会用HTML命令对这些特殊代码进行翻译。常见的如:
image

行内元素+块级元素+行内块级元素

行内元素(行内不可置换元素):

(1)代表标签:a,span,b,u,i,s,strong,em,ins,del
(2)特点:

  • 一行里面可以显示多个
  • 无法设置宽高
  • 宽高由内容来决定(背景是显示在宽高上的,而不是行高)

(3)属性:display: inline(显示方式:行内元素)

块级元素:

(1)代表标签:p,h1-h6,div,ul,li,ol,dl,dt,dd
(2)特点:

  • 独占一行
  • 可以设置宽高
  • 默认宽度一整行

(3)属性: display:block(显示方式:块级元素)

行内块级元素(行内可置换元素):

(1)代表标签:img,input,textarea
(2)特点:

  • 可以设置宽高
  • 一行内可显示多个

(3)属性:display:inline-block
4.元素之间显示方式切换:修改display属性,使用浮动(后面会讲)

三、SEO

1.搜索引擎(网络爬虫)负责收集页面信息,对我们的页面进行一个归类排序。
2.搜索引擎(百度)是如何对页面进行搜索排序的?

  • 花钱买排名
  • 页面的优化(SEO)
    实现SEO,最主要是做到标签语义化,在合适的地方使用合适的标签
    发外链(网站就越受欢迎,将来被用户搜索出来的机会就要大)

前端面试必备——十大经典排序算法

冒泡排序

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

作为最简单的排序算法之一,冒泡排序给我的感觉就像 Abandon 在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。

1. 算法步骤

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

  3. 针对所有的元素重复以上的步骤,除了最后一个。

  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

2. 动图演示

bubblesort

3. 什么时候最快

当输入的数据已经是正序时(都已经是正序了,我还要你冒泡排序有何用啊)。

4. 什么时候最慢

当输入的数据是反序时(写一个 for 循环反序输出数据不就行了,干嘛要用你冒泡排序呢,我是闲的吗)。

5. JavaScript 代码实现

function bubbleSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len - 1; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {        // 相邻元素两两对比
                var temp = arr[j+1];        // 元素交换
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

6. Python 代码实现

def bubbleSort(arr):
    for i in range(1, len(arr)):
        for j in range(0, len(arr)-i):
            if arr[j] > arr[j+1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr

7. Go 代码实现

func bubbleSort(arr []int) []int {
	length := len(arr)
	for i := 0; i < length; i++ {
		for j := 0; j < length-1-i; j++ {
			if arr[j] > arr[j+1] {
				arr[j], arr[j+1] = arr[j+1], arr[j]
			}
		}
	}
	return arr
}

选择排序

选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。

1. 算法步骤

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置

  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

  3. 重复第二步,直到所有元素均排序完毕。

2. 动图演示

selectionsort

3. JavaScript 代码实现

function selectionSort(arr) {
    var len = arr.length;
    var minIndex, temp;
    for (var i = 0; i < len - 1; i++) {
        minIndex = i;
        for (var j = i + 1; j < len; j++) {
            if (arr[j] < arr[minIndex]) {     // 寻找最小的数
                minIndex = j;                 // 将最小数的索引保存
            }
        }
        temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
    return arr;
}

4. Python 代码实现

def selectionSort(arr):
    for i in range(len(arr)-1):
        for j in range(i+1, len(arr)):
            if arr[j] < arr[i]:
                arr[i], arr[j] = arr[j], arr[i]
    return arr

5. Go 代码实现

func selectionSort(arr []int) []int {
	length := len(arr)
	for i := 0; i < length-1; i++ {
		min := i
		for j := i + 1; j < length; j++ {
			if arr[min] > arr[j] {
				min = j
			}
		}
		arr[i], arr[min] = arr[min], arr[i]
	}
	return arr
}

插入排序

插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。

1. 算法步骤

  1. 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

2. 动图演示

insertionsort

3. JavaScript 代码实现

function insertionSort(arr) {
    var len = arr.length;
    var preIndex, current;
    for (var i = 1; i < len; i++) {
        preIndex = i - 1;
        current = arr[i];
        while(preIndex >= 0 && arr[preIndex] > current) {
            arr[preIndex+1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex+1] = current;
    }
    return arr;
}

4. Python 代码实现

def insertionSort(arr):
    for i in range(len(arr)):
        preIndex = i-1
        current = arr[i]
        while preIndex >= 0 and arr[preIndex] > current:
            arr[preIndex+1] = arr[preIndex]
            preIndex-=1
        arr[preIndex+1] = current
    return arr

5. Go 代码实现

func insertionSort(arr []int) []int {
	for i := range arr {
		preIndex := i - 1
		current := arr[i]
		for preIndex >= 0 && arr[preIndex] > current {
			arr[preIndex+1] = arr[preIndex]
			preIndex -= 1
		}
		arr[preIndex+1] = current
	}
	return arr
}

希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

希尔排序的基本**是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

1. 算法步骤

  1. 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;

  2. 按增量序列个数 k,对序列进行 k 趟排序;

  3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

2. JavaScript 代码实现

function shellSort(arr) {
    var len = arr.length,
        temp,
        gap = 1;
    while(gap < len/3) {          //动态定义间隔序列
        gap =gap*3+1;
    }
    for (gap; gap > 0; gap = Math.floor(gap/3)) {
        for (var i = gap; i < len; i++) {
            temp = arr[i];
            for (var j = i-gap; j >= 0 && arr[j] > temp; j-=gap) {
                arr[j+gap] = arr[j];
            }
            arr[j+gap] = temp;
        }
    }
    return arr;
}

3. Python 代码实现

def shellSort(arr):
    import math
    gap=1
    while(gap < len(arr)/3):
        gap = gap*3+1
    while gap > 0:
        for i in range(gap,len(arr)):
            temp = arr[i]
            j = i-gap
            while j >=0 and arr[j] > temp:
                arr[j+gap]=arr[j]
                j-=gap
            arr[j+gap] = temp
        gap = math.floor(gap/3)
    return arr
}

4. Go 代码实现

func shellSort(arr []int) []int {
	length := len(arr)
	gap := 1
	for gap < gap/3 {
		gap = gap*3 + 1
	}
	for gap > 0 {
		for i := gap; i < length; i++ {
			temp := arr[i]
			j := i - gap
			for j >= 0 && arr[j] > temp {
				arr[j+gap] = arr[j]
				j -= gap
			}
			arr[j+gap] = temp
		}
		gap = gap / 3
	}
	return arr
}

归并排序

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

作为一种典型的分而治之**的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
  • 自下而上的迭代;

和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。

2. 算法步骤

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;

  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

  4. 重复步骤 3 直到某一指针达到序列尾;

  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

3. 动图演示

mergesort

4. JavaScript 代码实现

function mergeSort(arr) {  // 采用自上而下的递归方法
    var len = arr.length;
    if(len < 2) {
        return arr;
    }
    var middle = Math.floor(len / 2),
        left = arr.slice(0, middle),
        right = arr.slice(middle);
    return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right)
{
    var result = [];

    while (left.length && right.length) {
        if (left[0] <= right[0]) {
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }

    while (left.length)
        result.push(left.shift());

    while (right.length)
        result.push(right.shift());

    return result;
}

5. Python 代码实现

def mergeSort(arr):
    import math
    if(len(arr)<2):
        return arr
    middle = math.floor(len(arr)/2)
    left, right = arr[0:middle], arr[middle:]
    return merge(mergeSort(left), mergeSort(right))

def merge(left,right):
    result = []
    while left and right:
        if left[0] <= right[0]:
            result.append(left.pop(0));
        else:
            result.append(right.pop(0));
    while left:
        result.append(left.pop(0));
    while right:
        result.append(right.pop(0));
    return result

6. Go 代码实现

func mergeSort(arr []int) []int {
	length := len(arr)
	if length < 2 {
		return arr
	}
	middle := length / 2
	left := arr[0:middle]
	right := arr[middle:]
	return merge(mergeSort(left), mergeSort(right))
}

func merge(left []int, right []int) []int {
	var result []int
	for len(left) != 0 && len(right) != 0 {
		if left[0] <= right[0] {
			result = append(result, left[0])
			left = left[1:]
		} else {
			result = append(result, right[0])
			right = right[1:]
		}
	}

	for len(left) != 0 {
		result = append(result, left[0])
		left = left[1:]
	}

	for len(right) != 0 {
		result = append(result, right[0])
		right = right[1:]
	}

	return result
}

快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。

快速排序又是一种分而治之**在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好。

1. 算法步骤

  1. 从数列中挑出一个元素,称为 “基准”(pivot);

  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

2. 动图演示

quicksort

3. JavaScript 代码实现

function quickSort(arr, left, right) {
    var len = arr.length,
        partitionIndex,
        left = typeof left != 'number' ? 0 : left,
        right = typeof right != 'number' ? len - 1 : right;

    if (left < right) {
        partitionIndex = partition(arr, left, right);
        quickSort(arr, left, partitionIndex-1);
        quickSort(arr, partitionIndex+1, right);
    }
    return arr;
}

function partition(arr, left ,right) {     // 分区操作
    var pivot = left,                      // 设定基准值(pivot)
        index = pivot + 1;
    for (var i = index; i <= right; i++) {
        if (arr[i] < arr[pivot]) {
            swap(arr, i, index);
            index++;
        }        
    }
    swap(arr, pivot, index - 1);
    return index-1;
}

function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
functiion paritition2(arr, low, high) {
  let pivot = arr[low];
  while (low < high) {
    while (low < high && arr[high] > pivot) {
      --high;
    }
    arr[low] = arr[high];
    while (low < high && arr[low] <= pivot) {
      ++low;
    }
    arr[high] = arr[low];
  }
  arr[low] = pivot;
  return low;
}

function quickSort2(arr, low, high) {
  if (low < high) {
    let pivot = paritition2(arr, low, high);
    quickSort2(arr, low, pivot - 1);
    quickSort2(arr, pivot + 1, high);
  }
  return arr;
}

4. Python 代码实现

def quickSort(arr, left=None, right=None):
    left = 0 if not isinstance(left,(int, float)) else left
    right = len(arr)-1 if not isinstance(right,(int, float)) else right
    if left < right:
        partitionIndex = partition(arr, left, right)
        quickSort(arr, left, partitionIndex-1)
        quickSort(arr, partitionIndex+1, right)
    return arr

def partition(arr, left, right):
    pivot = left
    index = pivot+1
    i = index
    while  i <= right:
        if arr[i] < arr[pivot]:
            swap(arr, i, index)
            index+=1
        i+=1
    swap(arr,pivot,index-1)
    return index-1

def swap(arr, i, j):
    arr[i], arr[j] = arr[j], arr[i]

5. Go 代码实现

func quickSort(arr []int) []int {
	return _quickSort(arr, 0, len(arr)-1)
}

func _quickSort(arr []int, left, right int) []int {
	if left < right {
		partitionIndex := partition(arr, left, right)
		_quickSort(arr, left, partitionIndex-1)
		_quickSort(arr, partitionIndex+1, right)
	}
	return arr
}

func partition(arr []int, left, right int) int {
	pivot := left
	index := pivot + 1

	for i := index; i <= right; i++ {
		if arr[i] < arr[pivot] {
			swap(arr, i, index)
			index += 1
		}
	}
	swap(arr, pivot, index-1)
	return index - 1
}

func swap(arr []int, i, j int) {
	arr[i], arr[j] = arr[j], arr[i]
}

6. C++版

 //标准分割函数
 Paritition1(int A[], int low, int high) {
   int pivot = A[low];
   while (low < high) {
     while (low < high && A[high] >= pivot) {
       --high;
     }
     A[low] = A[high];
     while (low < high && A[low] <= pivot) {
       ++low;
     }
     A[high] = A[low];
   }
   A[low] = pivot;
   return low;
 }

 void QuickSort(int A[], int low, int high) //快排母函数
 {
   if (low < high) {
     int pivot = Paritition1(A, low, high); 
     QuickSort(A, low, pivot - 1);
     QuickSort(A, pivot + 1, high);
   }
 }

堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  1. 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  2. 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

堆排序的平均时间复杂度为 Ο(nlogn)。

1. 算法步骤

  1. 创建一个堆 H[0……n-1];

  2. 把堆首(最大值)和堆尾互换;

  3. 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;

  4. 重复步骤 2,直到堆的尺寸为 1。

2. 动图演示

heapsort

3. JavaScript 代码实现

var len;    // 因为声明的多个函数都需要数据长度,所以把len设置成为全局变量

function buildMaxHeap(arr) {   // 建立大顶堆
    len = arr.length;
    for (var i = Math.floor(len/2); i >= 0; i--) {
        heapify(arr, i);
    }
}

function heapify(arr, i) {     // 堆调整
    var left = 2 * i + 1,
        right = 2 * i + 2,
        largest = i;

    if (left < len && arr[left] > arr[largest]) {
        largest = left;
    }

    if (right < len && arr[right] > arr[largest]) {
        largest = right;
    }

    if (largest != i) {
        swap(arr, i, largest);
        heapify(arr, largest);
    }
}

function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

function heapSort(arr) {
    buildMaxHeap(arr);

    for (var i = arr.length-1; i > 0; i--) {
        swap(arr, 0, i);
        len--;
        heapify(arr, 0);
    }
    return arr;
}

4. Python 代码实现

def buildMaxHeap(arr):
    import math
    for i in range(math.floor(len(arr)/2),-1,-1):
        heapify(arr,i)

def heapify(arr, i):
    left = 2*i+1
    right = 2*i+2
    largest = i
    if left < arrLen and arr[left] > arr[largest]:
        largest = left
    if right < arrLen and arr[right] > arr[largest]:
        largest = right

    if largest != i:
        swap(arr, i, largest)
        heapify(arr, largest)

def swap(arr, i, j):
    arr[i], arr[j] = arr[j], arr[i]

def heapSort(arr):
    global arrLen
    arrLen = len(arr)
    buildMaxHeap(arr)
    for i in range(len(arr)-1,0,-1):
        swap(arr,0,i)
        arrLen -=1
        heapify(arr, 0)
    return arr

5. Go 代码实现

func heapSort(arr []int) []int {
	arrLen := len(arr)
	buildMaxHeap(arr, arrLen)
	for i := arrLen - 1; i >= 0; i-- {
		swap(arr, 0, i)
		arrLen -= 1
		heapify(arr, 0, arrLen)
	}
	return arr
}

func buildMaxHeap(arr []int, arrLen int) {
	for i := arrLen / 2; i >= 0; i-- {
		heapify(arr, i, arrLen)
	}
}

func heapify(arr []int, i, arrLen int) {
	left := 2*i + 1
	right := 2*i + 2
	largest := i
	if left < arrLen && arr[left] > arr[largest] {
		largest = left
	}
	if right < arrLen && arr[right] > arr[largest] {
		largest = right
	}
	if largest != i {
		swap(arr, i, largest)
		heapify(arr, largest, arrLen)
	}
}

func swap(arr []int, i, j int) {
	arr[i], arr[j] = arr[j], arr[i]
}

计数排序

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

1. 动图演示

countingsort

2. JavaScript 代码实现

function countingSort(arr, maxValue) {
    var bucket = new Array(maxValue+1),
        sortedIndex = 0;
        arrLen = arr.length,
        bucketLen = maxValue + 1;

    for (var i = 0; i < arrLen; i++) {
        if (!bucket[arr[i]]) {
            bucket[arr[i]] = 0;
        }
        bucket[arr[i]]++;
    }

    for (var j = 0; j < bucketLen; j++) {
        while(bucket[j] > 0) {
            arr[sortedIndex++] = j;
            bucket[j]--;
        }
    }

    return arr;
}

3. Python 代码实现

def countingSort(arr, maxValue):
    bucketLen = maxValue+1
    bucket = [0]*bucketLen
    sortedIndex =0
    arrLen = len(arr)
    for i in range(arrLen):
        if not bucket[arr[i]]:
            bucket[arr[i]]=0
        bucket[arr[i]]+=1
    for j in range(bucketLen):
        while bucket[j]>0:
            arr[sortedIndex] = j
            sortedIndex+=1
            bucket[j]-=1
    return arr

4. Go 代码实现

func countingSort(arr []int, maxValue int) []int {
	bucketLen := maxValue + 1
	bucket := make([]int, bucketLen) // 初始为0的数组

	sortedIndex := 0
	length := len(arr)

	for i := 0; i < length; i++ {
		bucket[arr[i]] += 1
	}

	for j := 0; j < bucketLen; j++ {
		for bucket[j] > 0 {
			arr[sortedIndex] = j
			sortedIndex += 1
			bucket[j] -= 1
		}
	}

	return arr
}

桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:

  1. 在额外空间充足的情况下,尽量增大桶的数量
  2. 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中

同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。

1. 什么时候最快

当输入的数据可以均匀的分配到每一个桶中。

2. 什么时候最慢

当输入的数据被分配到了同一个桶中。

3. JavaScript 代码实现

function bucketSort(arr, bucketSize) {
    if (arr.length === 0) {
      return arr;
    }

    var i;
    var minValue = arr[0];
    var maxValue = arr[0];
    for (i = 1; i < arr.length; i++) {
      if (arr[i] < minValue) {
          minValue = arr[i];                // 输入数据的最小值
      } else if (arr[i] > maxValue) {
          maxValue = arr[i];                // 输入数据的最大值
      }
    }

    //桶的初始化
    var DEFAULT_BUCKET_SIZE = 5;            // 设置桶的默认数量为5
    bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
    var bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;   
    var buckets = new Array(bucketCount);
    for (i = 0; i < buckets.length; i++) {
        buckets[i] = [];
    }

    //利用映射函数将数据分配到各个桶中
    for (i = 0; i < arr.length; i++) {
        buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
    }

    arr.length = 0;
    for (i = 0; i < buckets.length; i++) {
        insertionSort(buckets[i]);                      // 对每个桶进行排序,这里使用了插入排序
        for (var j = 0; j < buckets[i].length; j++) {
            arr.push(buckets[i][j]);                      
        }
    }

    return arr;
}

基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

1. 基数排序 vs 计数排序 vs 桶排序

基数排序有两种方法:

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

  • 基数排序:根据键值的每位数字来分配桶;
  • 计数排序:每个桶只存储单一键值;
  • 桶排序:每个桶存储一定范围的数值;

2. LSD 基数排序动图演示

radixsort

3. JavaScript 代码实现

//LSD Radix Sort
var counter = [];
function radixSort(arr, maxDigit) {
    var mod = 10;
    var dev = 1;
    for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
        for(var j = 0; j < arr.length; j++) {
            var bucket = parseInt((arr[j] % mod) / dev);
            if(counter[bucket]==null) {
                counter[bucket] = [];
            }
            counter[bucket].push(arr[j]);
        }
        var pos = 0;
        for(var j = 0; j < counter.length; j++) {
            var value = null;
            if(counter[j]!=null) {
                while ((value = counter[j].shift()) != null) {
                      arr[pos++] = value;
                }
          }
        }
    }
    return arr;
}

参考文档

ES6 (...)展开运算符

函数传参

以前我们都是使用Function.prototype.apply方法来将一个数组展开成多个参数,apply 方法的第二个接受一个数组能帮我们把数组展开,然后一一按顺序对应函数的每一个形参,当然如果传进去的数组大于形参个数也是可行的

function test(x, y, z) {
  console.log(arguments); //[0, 1, 2]
  console.log(x); //0
  console.log(y); //1
  console.log(z); //2
}
var args = [0, 1, 2];
test.apply(null, args);
test.apply(this, args);

如果我们改用 ES6 的展开运算符就可以这么写了,跟上面的效果一样,但是下面这一种更简洁

function test(x, y, z) {
  console.log(arguments);
  console.log(x);
  console.log(y);
  console.log(z);
}
var args = [0, 1, 2];
test(...args);

当然除了展开,还可以支持下面这种,如果我们传递数据,函数形参...items这种形式,那就是相当于把数据转化为数组,那此时items形参就是聚合后的数组

'autumn', true, false, 18 => [true, false, 18]
'wscats', 1, 2, 'Hello', ['a', 'b', 'c'] => [1, 2, "Hello", Array[3]]
function test2(type, ...items) {
  console.log(items);
}
test2("autumn", true, false, 18);
test2("wscats", 1, 2, "Hello", ["a", "b", "c"]);

当然我们还可以再复杂点,传递多个...arg

function test3(x, y) {
  console.log(x); //autumn
  console.log(y); //wscats
  //...
}
test3(...["autumn"], ...["wscats", 1, 2, "Hello", ["a", "b", "c"]]);

数据解构

其实就是把数组的每个数据拆开然后放进去

let arr = ["autumn", "wscats"];
// 析构数组
let y;
[autumn, ...y] = arr;
console.log(y); // ["wscats"]

数据构造

两个对象连接返回新的对象

let x = {
  name: "autumn",
};
let y = {
  age: 18,
};
let z = { ...x, ...y };
console.log(z);

两个数组连接返回新的数组

let x = ["autumn"];
let y = ["wscats"];
let z = [...x, ...y];
console.log(z); // ["autumn", "wscats"]

数组加上对象返回新的数组

let x = [
  {
    name: "autumn",
  },
];
let y = {
  name: "wscats",
};
let z = [...x, y];
console.log(z);

image

数组+字符串

let x = ["autumn"];
let y = "wscats";
let z = [...x, y];
console.log(z);

数组+对象

let x = {
  name: ["autumn", "wscats"],
  age: 18,
};
let y = {
  ...x, //name: ['autumn','wscats'],age:18
  arr: [...x.name], //['autumn','wscats']
};
console.log(y);

day2

01判断奇数偶数.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>01判断奇数偶数</title>
	<style>
		.even{color:#f00;}
		.odd{color:#0f0;}
	</style>
	<script>
		// 判断一个数是偶数还是奇数,并输出判断结果;

		function oddEven(){
			// 先获取输入的数字
			var num = document.getElementById('num');
			var _num = num.value;

			// 获取输出信息的元素
			var output = document.getElementById('output');

			// 判断奇偶
			// 通过判断是否能被2整除,如果能被2整除,则为偶数,否则为奇数
			if(_num%2 === 0){
				// alert('偶数');
				// 如何把信息写入html元素:innerHTML
				// 的
				output.innerHTML = '<span class="even">偶数</span>';
			}else{
				// alert('奇数');
				output.innerHTML = '<span class="odd">奇数</span>';
			}
		}
	</script>
</head>
<body>
	<input type="number" id="num"><button onclick="oddEven()">判断奇偶</button>
	<div id="output"></div>
</body>
</html>

day6

数组forEach()

// ES新增的数组方法
var arr = ['html5', 'css3', 'ecmascript5', 'jquery', 'angular'];

/*for(var i=0;i<arr.length;i++){
	var new = ''
	document.write('<p>' + arr[i] + '</p>');
}*/

// 用于遍历数组的forEach()
// forEach的缺点:不能使用break/continue
arr.forEach(function(item, idx, arr) {
	console.log(item, idx, arr);
	// item就是for循环中的arr[i]
	document.write('<p>' + item + '</p>');
});

数组map()

// map()
var arr = [10, 20, 30, 5, 100];

var newArr = arr.map(function(item) {
	console.log(item);

	// 一定要有一个返回值
	// 返回一个增加20%的数
	// 得到数组元素取决于下面的return值

	// return item + item*0.2
	return item * item;
});
console.log(newArr, arr)

数组every()

// 判断arr中的数是否都大于10
var res = arr.every(function(item) {
	return item > 10;
});
console.log(res);

数组some()

// 只要数组中有一项符合return后的条件(返回true)
var res = arr.some(function(item) {
	return item > 20;
});
console.log(res);

前端搜索

<input id="input" onkeyup="search()" />
<ul id="ul"></ul>
<script>
	var arr = ['html5', 'css3', 'ecmascript5', 'jquery', 'css3', 'angular'];

	function search() {
		var input = document.getElementById("input").value;
		var newArr = [];
		arr.forEach(function(item) {
			if(item.indexOf(input)) {} else {
				newArr.push(item)
			}
		});
		console.log(newArr, arr)
		var str = newArr.map(function(item) {
			return "<p>" + item + "</p>";
		}).join("");
		console.log(str)
		document.getElementById("ul").innerHTML = str
	}
</script>

严格模式

作用域

'use strict';
var arr = [10, 20, 30];
var num = 100;
function sum() {
	// 'use strict';
	// 不适用var声明的变量在正常模式下会称为全局变量
	// 在严格模式下会报错
	x = 100;
	console.log(x);
}
sum();

删除全局变量

'use strict';
// delete
var obj = {
	name: '1612',
	add: '广州'
}
obj.add = '广州天河';
// 删除add属性
delete obj.add;
//不能删除obj全局变量
delete obj;
console.log(obj)
// 全局变量是不可删除的属性
window.num = 100;
delete num; //删除失败,因为num为全局变量
console.log(num);

arguments

function test() {
	'use strict';
	console.log(arguments);
	// 交换arguments中的参数位置
	var temp = arguments[0];
	arguments[0] = arguments[1];
	arguments[1] = temp;
	console.log(arguments);
}
test(1, 2)

arguments.callee

var num = 100;
function factorial(num) {
	// 'use strict'
	if(num === 1) {
		return 1;
	}
	return num * arguments.callee(num - 1);
}
console.log(factorial(5));

不允许把函数声明写在条件判断/循环语句中

'use strict'
for(var i = 0; i < 100; i++) {
	function test(idx) {
		console.log(idx);
	}
	test(i);
}

字符串创建

//方式一:字面量(推荐)
var str = '城市套路深,我想回农村';
//方式二:构造函数:
var str2 = new String('城市套路深,我想回农村');
console.log(typeof str);
console.log(typeof str2);
//字符长度
console.log(str.length);

正则表达式

var str = 'Good good study, day day up';
// 字面量
var reg = /good/gi; //good=>hao
// 构造函数
var reg = new RegExp('good', 'gi');
// 参数
// i,g
console.log(str.replace(reg, 'hao'));

提取url的参数

/*
			取出name, age和gender的值
			1)先获取?后的字符
			2)用&分隔字符串为数组
			3)遍历数组,提取name,age,gender
		 */
var url = 'https://www.baidu.com/s?name=wangmouqiang&age=20&gender=male'
console.log(url.substring(2, 5));
console.log(url.substr(-10, 5));
// var name = url.substr(url.indexOf('name=')+5,6);
// console.log(name)
// 1)先获取?后的字符
var idx = url.indexOf('?');
var attr = url.substring(idx + 1);
attr = attr.split('&');
console.log(attr)
attr.forEach(function(item) {
	var arr = item.split('=');
	console.log(arr[0], arr[1]);
});

加密解密

window.onload = function() {
	var msg = document.getElementById('msg');
	var btn = document.getElementById('btn');
	var output = document.getElementById('output');
	var btn2 = document.getElementById('btn2');
	// 绑定点击事件
	btn.onclick = function() {
		var _msg = msg.value;
		var arr = [];
		for(var i = 0; i < _msg.length; i++) {
			arr.push(_msg.charCodeAt(i));
		}
		output.innerHTML = arr.join();
	}
	// 加密
	btn2.onclick = function() {
		var _msg = output.innerHTML;
		// 转回数组
		var arr = _msg.split(',');
		var str = '';
		arr.forEach(function(num) {
			str += String.fromCharCode(num);
		});
		output.innerHTML = str;
	}
}

继 NPM 被收购后,微软宣布推出 Github 口袋版!

昨天除了 NPM 被微软收购的消息外,微软旗下的 Github 也正式发布了 GitHub移动版 ,它是 iOS 和 Android上对 GitHub 网页桌面版的完全体验版。现在,我们可以随时随地在移动设备上与我们的团队保持联系,分类问题,甚至合并代码。

之前开发者对 Beta 的反应令人难以置信,Beta 测试人员仅在过去的几周内就累计评论,和审查合并了超过近十万个拉取请求。自从 Github 首次发布测试版以供下载以来,已经有成千上万的团队互动。如今,适用于移动设备的 GitHub 的 iOS 和 Android 版本除了Beta版之外,正式版已全面上市。

我们可以从以下商店获取并安装该应用:

使用 GitHub 移动版,我们可以随时随地分类:

  • 轻扫即可整理任务:立即将收件箱设为零,轻扫即可完成任务或保存通知以供日后返回。

IMG_0197

  • 提供反馈并回答问题:让你在旅途中回复评论。

IMG_0194

  • 复查和合并拉取请求:合并和标记拉取请求,无论您身在何处都可以轻松进行工作流。

IMG_0196

借助移动版 GitHub,无论我们是在办公桌上还是在旅途中,我们都可以享受到更加无缝的通知体验,现在我们还可以享受最新通知的更新功能

GoogleApp Store 现已针对个人计划,团队和企业云提供了适用于移动设备的 GitHub。他们正在努力添加功能和 API,以便在今年晚些时候可以支持 Enterprise Server。

如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的肯定是我前进的最大动力😁

  • 附笔记链接:前端笔记本和公众号「前端遨游」,阅读往期更多优质文章可移步查看,喜欢的可以给我点赞鼓励哦:

截屏2020-03-15上午10 53 06

36个工作中常用的JavaScript函数片段

如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的肯定是我前进的最大动力😁

数组 Array

数组去重

function noRepeat(arr) {
  return [...new Set(arr)];
}

查找数组最大

function arrayMax(arr) {
  return Math.max(...arr);
}

查找数组最小

function arrayMin(arr) {
  return Math.min(...arr);
}

返回已 size 为长度的数组分割的原数组

function chunk(arr, size = 1) {
  return Array.from(
    {
      length: Math.ceil(arr.length / size),
    },
    (v, i) => arr.slice(i * size, i * size + size)
  );
}

检查数组中某元素出现的次数

function countOccurrences(arr, value) {
  return arr.reduce((a, v) => (v === value ? a + 1 : a + 0), 0);
}

扁平化数组

  • 默认 depth 全部展开
function flatten(arr, depth = -1) {
  if (depth === -1) {
    return [].concat(
      ...arr.map((v) => (Array.isArray(v) ? this.flatten(v) : v))
    );
  }
  if (depth === 1) {
    return arr.reduce((a, v) => a.concat(v), []);
  }
  return arr.reduce(
    (a, v) => a.concat(Array.isArray(v) ? this.flatten(v, depth - 1) : v),
    []
  );
}

对比两个数组并且返回其中不同的元素

function diffrence(arrA, arrB) {
  return arrA.filter((v) => !arrB.includes(v));
}

返回两个数组中相同的元素

function intersection(arr1, arr2) {
  return arr2.filter((v) => arr1.includes(v));
}

从右删除 n 个元素

function dropRight(arr, n = 0) {
  return n < arr.length ? arr.slice(0, arr.length - n) : [];
}

截取第一个符合条件的元素及其以后的元素

function dropElements(arr, fn) {
  while (arr.length && !fn(arr[0])) arr = arr.slice(1);
  return arr;
}

返回数组中下标间隔 nth 的元素

function everyNth(arr, nth) {
  return arr.filter((v, i) => i % nth === nth - 1);
}

返回数组中第 n 个元素

  • 支持负数
function nthElement(arr, n = 0) {
  return (n >= 0 ? arr.slice(n, n + 1) : arr.slice(n))[0];
}

返回数组头元素

function head(arr) {
  return arr[0];
}

返回数组末尾元素

function last(arr) {
  return arr[arr.length - 1];
}

数组乱排

function shuffle(arr) {
  let array = arr;
  let index = array.length;

  while (index) {
    index -= 1;
    let randomInedx = Math.floor(Math.random() * index);
    let middleware = array[index];
    array[index] = array[randomInedx];
    array[randomInedx] = middleware;
  }

  return array;
}

浏览器对象 BOM

判读浏览器是否支持 CSS 属性

/**
 * 告知浏览器支持的指定css属性情况
 * @param {String} key - css属性,是属性的名字,不需要加前缀
 * @returns {String} - 支持的属性情况
 */
function validateCssKey(key) {
  const jsKey = toCamelCase(key); // 有些css属性是连字符号形成
  if (jsKey in document.documentElement.style) {
    return key;
  }
  let validKey = "";
  // 属性名为前缀在js中的形式,属性值是前缀在css中的形式
  // 经尝试,Webkit 也可是首字母小写 webkit
  const prefixMap = {
    Webkit: "-webkit-",
    Moz: "-moz-",
    ms: "-ms-",
    O: "-o-",
  };
  for (const jsPrefix in prefixMap) {
    const styleKey = toCamelCase(`${jsPrefix}-${jsKey}`);
    if (styleKey in document.documentElement.style) {
      validKey = prefixMap[jsPrefix] + key;
      break;
    }
  }
  return validKey;
}

/**
 * 把有连字符号的字符串转化为驼峰命名法的字符串
 */
function toCamelCase(value) {
  return value.replace(/-(\w)/g, (matched, letter) => {
    return letter.toUpperCase();
  });
}

/**
 * 检查浏览器是否支持某个css属性值(es6版)
 * @param {String} key - 检查的属性值所属的css属性名
 * @param {String} value - 要检查的css属性值(不要带前缀)
 * @returns {String} - 返回浏览器支持的属性值
 */
function valiateCssValue(key, value) {
  const prefix = ["-o-", "-ms-", "-moz-", "-webkit-", ""];
  const prefixValue = prefix.map((item) => {
    return item + value;
  });
  const element = document.createElement("div");
  const eleStyle = element.style;
  // 应用每个前缀的情况,且最后也要应用上没有前缀的情况,看最后浏览器起效的何种情况
  // 这就是最好在prefix里的最后一个元素是''
  prefixValue.forEach((item) => {
    eleStyle[key] = item;
  });
  return eleStyle[key];
}

/**
 * 检查浏览器是否支持某个css属性值
 * @param {String} key - 检查的属性值所属的css属性名
 * @param {String} value - 要检查的css属性值(不要带前缀)
 * @returns {String} - 返回浏览器支持的属性值
 */
function valiateCssValue(key, value) {
  var prefix = ["-o-", "-ms-", "-moz-", "-webkit-", ""];
  var prefixValue = [];
  for (var i = 0; i < prefix.length; i++) {
    prefixValue.push(prefix[i] + value);
  }
  var element = document.createElement("div");
  var eleStyle = element.style;
  for (var j = 0; j < prefixValue.length; j++) {
    eleStyle[key] = prefixValue[j];
  }
  return eleStyle[key];
}

function validCss(key, value) {
  const validCss = validateCssKey(key);
  if (validCss) {
    return validCss;
  }
  return valiateCssValue(key, value);
}

返回当前网页地址

function currentURL() {
  return window.location.href;
}

获取滚动条位置

function getScrollPosition(el = window) {
  return {
    x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
    y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop,
  };
}

获取 url 中的参数

function getURLParameters(url) {
  return url
    .match(/([^?=&]+)(=([^&]*))/g)
    .reduce(
      (a, v) => (
        (a[v.slice(0, v.indexOf("="))] = v.slice(v.indexOf("=") + 1)), a
      ),
      {}
    );
}

页面跳转,是否记录在 history 中

function redirect(url, asLink = true) {
  asLink ? (window.location.href = url) : window.location.replace(url);
}

滚动条回到顶部动画

function scrollToTop() {
  const scrollTop =
    document.documentElement.scrollTop || document.body.scrollTop;
  if (scrollTop > 0) {
    window.requestAnimationFrame(scrollToTop);
    window.scrollTo(0, c - c / 8);
  } else {
    window.cancelAnimationFrame(scrollToTop);
  }
}

复制文本

function copy(str) {
  const el = document.createElement("textarea");
  el.value = str;
  el.setAttribute("readonly", "");
  el.style.position = "absolute";
  el.style.left = "-9999px";
  el.style.top = "-9999px";
  document.body.appendChild(el);
  const selected =
    document.getSelection().rangeCount > 0
      ? document.getSelection().getRangeAt(0)
      : false;
  el.select();
  document.execCommand("copy");
  document.body.removeChild(el);
  if (selected) {
    document.getSelection().removeAllRanges();
    document.getSelection().addRange(selected);
  }
}

检测设备类型

function detectDeviceType() {
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
    navigator.userAgent
  )
    ? "Mobile"
    : "Desktop";
}

Cookie

function setCookie(key, value, expiredays) {
  var exdate = new Date();
  exdate.setDate(exdate.getDate() + expiredays);
  document.cookie =
    key +
    "=" +
    escape(value) +
    (expiredays == null ? "" : ";expires=" + exdate.toGMTString());
}

function delCookie(name) {
  var exp = new Date();
  exp.setTime(exp.getTime() - 1);
  var cval = getCookie(name);
  if (cval != null) {
    document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString();
  }
}

function getCookie(name) {
  var arr,
    reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
  if ((arr = document.cookie.match(reg))) {
    return arr[2];
  } else {
    return null;
  }
}

日期 Date

时间戳转换为时间

  • 默认为当前时间转换结果

  • isMs 为时间戳是否为毫秒

function timestampToTime(timestamp = Date.parse(new Date()), isMs = true) {
  const date = new Date(timestamp * (isMs ? 1 : 1000));
  return `${date.getFullYear()}-${
    date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1
  }-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
}

  文档对象 DOM

固定滚动条

/**
 * 功能描述:一些业务场景,如弹框出现时,需要禁止页面滚动,这是兼容安卓和 iOS 禁止页面滚动的解决方案
 */

let scrollTop = 0;

function preventScroll() {
  // 存储当前滚动位置
  scrollTop = window.scrollY;

  // 将可滚动区域固定定位,可滚动区域高度为 0 后就不能滚动了
  document.body.style["overflow-y"] = "hidden";
  document.body.style.position = "fixed";
  document.body.style.width = "100%";
  document.body.style.top = -scrollTop + "px";
  // document.body.style['overscroll-behavior'] = 'none'
}

function recoverScroll() {
  document.body.style["overflow-y"] = "auto";
  document.body.style.position = "static";
  // document.querySelector('body').style['overscroll-behavior'] = 'none'

  window.scrollTo(0, scrollTop);
}

判断当前位置是否为页面底部

  • 返回值为 true/false
function bottomVisible() {
  return (
    document.documentElement.clientHeight + window.scrollY >=
    (document.documentElement.scrollHeight ||
      document.documentElement.clientHeight)
  );
}

判断元素是否在可视范围内

  • partiallyVisible 为是否为完全可见
function elementIsVisibleInViewport(el, partiallyVisible = false) {
  const { top, left, bottom, right } = el.getBoundingClientRect();

  return partiallyVisible
    ? ((top > 0 && top < innerHeight) ||
        (bottom > 0 && bottom < innerHeight)) &&
        ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
    : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
}

获取元素 css 样式

function getStyle(el, ruleName) {
  return getComputedStyle(el, null).getPropertyValue(ruleName);
}

进入全屏

function launchFullscreen(element) {
  if (element.requestFullscreen) {
    element.requestFullscreen();
  } else if (element.mozRequestFullScreen) {
    element.mozRequestFullScreen();
  } else if (element.msRequestFullscreen) {
    element.msRequestFullscreen();
  } else if (element.webkitRequestFullscreen) {
    element.webkitRequestFullScreen();
  }
}

launchFullscreen(document.documentElement);
launchFullscreen(document.getElementById("id")); //某个元素进入全屏

退出全屏

function exitFullscreen() {
  if (document.exitFullscreen) {
    document.exitFullscreen();
  } else if (document.msExitFullscreen) {
    document.msExitFullscreen();
  } else if (document.mozCancelFullScreen) {
    document.mozCancelFullScreen();
  } else if (document.webkitExitFullscreen) {
    document.webkitExitFullscreen();
  }
}

exitFullscreen();

全屏事件

document.addEventListener("fullscreenchange", function (e) {
  if (document.fullscreenElement) {
    console.log("进入全屏");
  } else {
    console.log("退出全屏");
  }
});

数字 Number

数字千分位分割

function commafy(num) {
  return num.toString().indexOf(".") !== -1
    ? num.toLocaleString()
    : num.toString().replace(/(\d)(?=(?:\d{3})+$)/g, "$1,");
}

生成随机数

function randomNum(min, max) {
  switch (arguments.length) {
    case 1:
      return parseInt(Math.random() * min + 1, 10);
    case 2:
      return parseInt(Math.random() * (max - min + 1) + min, 10);
    default:
      return 0;
  }
}

交流

文章同步持续更新,可以微信搜索「 前端遨游 」关注公众号方便你往后阅读,往期文章收录在https://github.com/Wscats/art...
欢迎您的关注和交流😁

截屏2020-03-15上午11.16.34.png

JavaScript工具函数大全(持续更新)

为元素添加on方法

Element.prototype.on = Element.prototype.addEventListener;

NodeList.prototype.on = function (event, fn) {
    []['forEach'].call(this, function (el) {
        el.on(event, fn);
    });
    return this;
};

为元素添加trigger方法

Element.prototype.trigger = function(type, data) {
  var event = document.createEvent("HTMLEvents");
  event.initEvent(type, true, true);
  event.data = data || {};
  event.eventName = type;
  event.target = this;
  this.dispatchEvent(event);
  return this;
};

NodeList.prototype.trigger = function(event) {
  []["forEach"].call(this, function(el) {
    el["trigger"](event);
  });
  return this;
};

转义html标签

function HtmlEncode(text) {
  return text
    .replace(/&/g, "&")
    .replace(/\"/g, '"')
    .replace(/</g, "<")
    .replace(/>/g, ">");
}

HTML标签转义

// HTML 标签转义
// @param {Array.<DOMString>} templateData 字符串类型的tokens
// @param {...} ..vals 表达式占位符的运算结果tokens
//
function SaferHTML(templateData) {
  var s = templateData[0];
  for (var i = 1; i < arguments.length; i++) {
    var arg = String(arguments[i]);
    // Escape special characters in the substitution.
    s += arg
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;");
    // Don't escape special characters in the template.
    s += templateData[i];
  }
  return s;
}
// 调用
var html = SaferHTML`<p>这是关于字符串模板的介绍</p>`;

跨浏览器绑定事件

function addEventSamp(obj, evt, fn) {
  if (!oTarget) {
    return;
  }
  if (obj.addEventListener) {
    obj.addEventListener(evt, fn, false);
  } else if (obj.attachEvent) {
    obj.attachEvent("on" + evt, fn);
  } else {
    oTarget["on" + sEvtType] = fn;
  }
}

加入收藏夹

function addFavorite(sURL, sTitle) {
  try {
    window.external.addFavorite(sURL, sTitle);
  } catch (e) {
    try {
      window.sidebar.addPanel(sTitle, sURL, "");
    } catch (e) {
      alert("加入收藏失败,请使用Ctrl+D进行添加");
    }
  }
}

提取页面代码中所有网址

var aa = document.documentElement.outerHTML
  .match(
    /(url\(|src=|href=)[\"\']*([^\"\'\(\)\<\>\[\] ]+)[\"\'\)]*|(http:\/\/[\w\-\.]+[^\"\'\(\)\<\>\[\] ]+)/gi
  )
  .join("\r\n")
  .replace(/^(src=|href=|url\()[\"\']*|[\"\'\>\) ]*$/gim, "");
alert(aa);

动态加载脚本文件

function appendscript(src, text, reload, charset) {
  var id = hash(src + text);
  if (!reload && in_array(id, evalscripts)) return;
  if (reload && $(id)) {
    $(id).parentNode.removeChild($(id));
  }

  evalscripts.push(id);
  var scriptNode = document.createElement("script");
  scriptNode.type = "text/javascript";
  scriptNode.id = id;
  scriptNode.charset = charset
    ? charset
    : BROWSER.firefox
    ? document.characterSet
    : document.charset;
  try {
    if (src) {
      scriptNode.src = src;
      scriptNode.onloadDone = false;
      scriptNode.onload = function() {
        scriptNode.onloadDone = true;
        JSLOADED[src] = 1;
      };
      scriptNode.onreadystatechange = function() {
        if (
          (scriptNode.readyState == "loaded" ||
            scriptNode.readyState == "complete") &&
          !scriptNode.onloadDone
        ) {
          scriptNode.onloadDone = true;
          JSLOADED[src] = 1;
        }
      };
    } else if (text) {
      scriptNode.text = text;
    }
    document.getElementsByTagName("head")[0].appendChild(scriptNode);
  } catch (e) {}
}

返回顶部的通用方法

function backTop(btnId) {
  var btn = document.getElementById(btnId);
  var d = document.documentElement;
  var b = document.body;
  window.onscroll = set;
  btn.style.display = "none";
  btn.onclick = function() {
    btn.style.display = "none";
    window.onscroll = null;
    this.timer = setInterval(function() {
      d.scrollTop -= Math.ceil((d.scrollTop + b.scrollTop) * 0.1);
      b.scrollTop -= Math.ceil((d.scrollTop + b.scrollTop) * 0.1);
      if (d.scrollTop + b.scrollTop == 0)
        clearInterval(btn.timer, (window.onscroll = set));
    }, 10);
  };
  function set() {
    btn.style.display = d.scrollTop + b.scrollTop > 100 ? "block" : "none";
  }
}
backTop("goTop");

实现base64解码

function base64_decode(data) {
  var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  var o1,
    o2,
    o3,
    h1,
    h2,
    h3,
    h4,
    bits,
    i = 0,
    ac = 0,
    dec = "",
    tmp_arr = [];
  if (!data) {
    return data;
  }
  data += "";
  do {
    h1 = b64.indexOf(data.charAt(i++));
    h2 = b64.indexOf(data.charAt(i++));
    h3 = b64.indexOf(data.charAt(i++));
    h4 = b64.indexOf(data.charAt(i++));
    bits = (h1 << 18) | (h2 << 12) | (h3 << 6) | h4;
    o1 = (bits >> 16) & 0xff;
    o2 = (bits >> 8) & 0xff;
    o3 = bits & 0xff;
    if (h3 == 64) {
      tmp_arr[ac++] = String.fromCharCode(o1);
    } else if (h4 == 64) {
      tmp_arr[ac++] = String.fromCharCode(o1, o2);
    } else {
      tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
    }
  } while (i < data.length);
  dec = tmp_arr.join("");
  dec = utf8_decode(dec);
  return dec;
}

确认是否是键盘有效输入值

function checkKey(iKey) {
  if (iKey == 32 || iKey == 229) {
    return true;
  } /*空格和异常*/
  if (iKey > 47 && iKey < 58) {
    return true;
  } /*数字*/
  if (iKey > 64 && iKey < 91) {
    return true;
  } /*字母*/
  if (iKey > 95 && iKey < 108) {
    return true;
  } /*数字键盘1*/
  if (iKey > 108 && iKey < 112) {
    return true;
  } /*数字键盘2*/
  if (iKey > 185 && iKey < 193) {
    return true;
  } /*符号1*/
  if (iKey > 218 && iKey < 223) {
    return true;
  } /*符号2*/
  return false;
}

全角半角转换

//iCase: 0全到半,1半到全,其他不转化
function chgCase(sStr, iCase) {
  if (
    typeof sStr != "string" ||
    sStr.length <= 0 ||
    !(iCase === 0 || iCase == 1)
  ) {
    return sStr;
  }
  var i,
    oRs = [],
    iCode;
  if (iCase) {
    /*半->全*/
    for (i = 0; i < sStr.length; i += 1) {
      iCode = sStr.charCodeAt(i);
      if (iCode == 32) {
        iCode = 12288;
      } else if (iCode < 127) {
        iCode += 65248;
      }
      oRs.push(String.fromCharCode(iCode));
    }
  } else {
    /*全->半*/
    for (i = 0; i < sStr.length; i += 1) {
      iCode = sStr.charCodeAt(i);
      if (iCode == 12288) {
        iCode = 32;
      } else if (iCode > 65280 && iCode < 65375) {
        iCode -= 65248;
      }
      oRs.push(String.fromCharCode(iCode));
    }
  }
  return oRs.join("");
}

版本对比

function compareVersion(v1, v2) {
  v1 = v1.split(".");
  v2 = v2.split(".");

  var len = Math.max(v1.length, v2.length);

  while (v1.length < len) {
    v1.push("0");
  }

  while (v2.length < len) {
    v2.push("0");
  }

  for (var i = 0; i < len; i++) {
    var num1 = parseInt(v1[i]);
    var num2 = parseInt(v2[i]);

    if (num1 > num2) {
      return 1;
    } else if (num1 < num2) {
      return -1;
    }
  }
  return 0;
}

压缩CSS样式代码

function compressCss(s) {
  //压缩代码
  s = s.replace(/\/\*(.|\n)*?\*\//g, ""); //删除注释
  s = s.replace(/\s*([\{\}\:\;\,])\s*/g, "$1");
  s = s.replace(/\,[\s\.\#\d]*\{/g, "{"); //容错处理
  s = s.replace(/;\s*;/g, ";"); //清除连续分号
  s = s.match(/^\s*(\S+(\s+\S+)*)\s*$/); //去掉首尾空白
  return s == null ? "" : s[1];
}

获取当前路径

var currentPageUrl = "";
if (typeof this.href === "undefined") {
  currentPageUrl = document.location.toString().toLowerCase();
} else {
  currentPageUrl = this.href.toString().toLowerCase();
}

字符串长度截取

function cutstr(str, len) {
    var temp,
        icount = 0,
        patrn = /[^\x00-\xff]/
        strre = "";
    for (var i = 0; i < str.length; i++) {
        if (icount < len - 1) {
            temp = str.substr(i, 1);
                if (patrn.exec(temp) == null) {
                   icount = icount + 1
            } else {
                icount = icount + 2
            }
            strre += temp
            } else {
            break;
        }
    }
    return strre + "..."
}

时间日期格式转换

Date.prototype.format = function(formatStr) {
  var str = formatStr;
  var Week = ["日", "一", "二", "三", "四", "五", "六"];
  str = str.replace(/yyyy|YYYY/, this.getFullYear());
  str = str.replace(
    /yy|YY/,
    this.getYear() % 100 > 9
      ? (this.getYear() % 100).toString()
      : "0" + (this.getYear() % 100)
  );
  str = str.replace(
    /MM/,
    this.getMonth() + 1 > 9
      ? (this.getMonth() + 1).toString()
      : "0" + (this.getMonth() + 1)
  );
  str = str.replace(/M/g, this.getMonth() + 1);
  str = str.replace(/w|W/g, Week[this.getDay()]);
  str = str.replace(
    /dd|DD/,
    this.getDate() > 9 ? this.getDate().toString() : "0" + this.getDate()
  );
  str = str.replace(/d|D/g, this.getDate());
  str = str.replace(
    /hh|HH/,
    this.getHours() > 9 ? this.getHours().toString() : "0" + this.getHours()
  );
  str = str.replace(/h|H/g, this.getHours());
  str = str.replace(
    /mm/,
    this.getMinutes() > 9
      ? this.getMinutes().toString()
      : "0" + this.getMinutes()
  );
  str = str.replace(/m/g, this.getMinutes());
  str = str.replace(
    /ss|SS/,
    this.getSeconds() > 9
      ? this.getSeconds().toString()
      : "0" + this.getSeconds()
  );
  str = str.replace(/s|S/g, this.getSeconds());
  return str;
};

// 或
Date.prototype.format = function(format) {
  var o = {
    "M+": this.getMonth() + 1, //month
    "d+": this.getDate(), //day
    "h+": this.getHours(), //hour
    "m+": this.getMinutes(), //minute
    "s+": this.getSeconds(), //second
    "q+": Math.floor((this.getMonth() + 3) / 3), //quarter
    S: this.getMilliseconds() //millisecond
  };
  if (/(y+)/.test(format))
    format = format.replace(
      RegExp.$1,
      (this.getFullYear() + "").substr(4 - RegExp.$1.length)
    );
  for (var k in o) {
    if (new RegExp("(" + k + ")").test(format))
      format = format.replace(
        RegExp.$1,
        RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)
      );
  }
  return format;
};
alert(new Date().format("yyyy-MM-dd hh:mm:ss"));

跨浏览器删除事件

function delEvt(obj, evt, fn) {
  if (!obj) {
    return;
  }
  if (obj.addEventListener) {
    obj.addEventListener(evt, fn, false);
  } else if (oTarget.attachEvent) {
    obj.attachEvent("on" + evt, fn);
  } else {
    obj["on" + evt] = fn;
  }
}

判断是否以某个字符串结束

String.prototype.endWith = function(s) {
  var d = this.length - s.length;
  return d >= 0 && this.lastIndexOf(s) == d;
};

返回脚本内容

function evalscript(s) {
  if (s.indexOf("<script") == -1) return s;
  var p = /<script[^\>]*?>([^\x00]*?)<\/script>/gi;
  var arr = [];
  while ((arr = p.exec(s))) {
    var p1 = /<script[^\>]*?src=\"([^\>]*?)\"[^\>]*?(reload=\"1\")?(?:charset=\"([\w\-]+?)\")?><\/script>/i;
    var arr1 = [];
    arr1 = p1.exec(arr[0]);
    if (arr1) {
      appendscript(arr1[1], "", arr1[2], arr1[3]);
    } else {
      p1 = /<script(.*?)>([^\x00]+?)<\/script>/i;
      arr1 = p1.exec(arr[0]);
      appendscript("", arr1[2], arr1[1].indexOf("reload=") != -1);
    }
  }
  return s;
}

格式化CSS样式代码

function formatCss(s) {
  //格式化代码
  s = s.replace(/\s*([\{\}\:\;\,])\s*/g, "$1");
  s = s.replace(/;\s*;/g, ";"); //清除连续分号
  s = s.replace(/\,[\s\.\#\d]*{/g, "{");
  s = s.replace(/([^\s])\{([^\s])/g, "$1 {\n\t$2");
  s = s.replace(/([^\s])\}([^\n]*)/g, "$1\n}\n$2");
  s = s.replace(/([^\s]);([^\s\}])/g, "$1;\n\t$2");
  return s;
}

获取cookie值

function getCookie(name) {
  var arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)"));
  if (arr != null) return unescape(arr[2]);
  return null;
}

获得URL中GET参数值

// 用法:如果地址是 test.htm?t1=1&t2=2&t3=3, 那么能取得:GET["t1"], GET["t2"], GET["t3"]
function getGet() {
  querystr = window.location.href.split("?");
  if (querystr[1]) {
    GETs = querystr[1].split("&");
    GET = [];
    for (i = 0; i < GETs.length; i++) {
      tmp_arr = GETs.split("=");
      key = tmp_arr[0];
      GET[key] = tmp_arr[1];
    }
  }
  return querystr[1];
}

获取移动设备初始化大小

function getInitZoom() {
  if (!this._initZoom) {
    var screenWidth = Math.min(screen.height, screen.width);
    if (this.isAndroidMobileDevice() && !this.isNewChromeOnAndroid()) {
      screenWidth = screenWidth / window.devicePixelRatio;
    }
    this._initZoom = screenWidth / document.body.offsetWidth;
  }
  return this._initZoom;
}

获取页面高度

function getPageHeight() {
  var g = document,
    a = g.body,
    f = g.documentElement,
    d = g.compatMode == "BackCompat" ? a : g.documentElement;
  return Math.max(f.scrollHeight, a.scrollHeight, d.clientHeight);
}

获取页面scrollLeft

function getPageScrollLeft() {
  var a = document;
  return a.documentElement.scrollLeft || a.body.scrollLeft;
}

获取页面scrollTop

function getPageScrollTop() {
  var a = document;
  return a.documentElement.scrollTop || a.body.scrollTop;
}

获取页面可视高度

function getPageViewHeight() {
  var d = document,
    a = d.compatMode == "BackCompat" ? d.body : d.documentElement;
  return a.clientHeight;
}

获取页面可视宽度

function getPageViewWidth() {
  var d = document,
    a = d.compatMode == "BackCompat" ? d.body : d.documentElement;
  return a.clientWidth;
}

获取页面宽度

function getPageWidth() {
  var g = document,
    a = g.body,
    f = g.documentElement,
    d = g.compatMode == "BackCompat" ? a : g.documentElement;
  return Math.max(f.scrollWidth, a.scrollWidth, d.clientWidth);
}

获取移动设备屏幕宽度

function getScreenWidth() {
  var smallerSide = Math.min(screen.width, screen.height);
  var fixViewPortsExperiment =
    rendererModel.runningExperiments.FixViewport ||
    rendererModel.runningExperiments.fixviewport;
  var fixViewPortsExperimentRunning =
    fixViewPortsExperiment && fixViewPortsExperiment.toLowerCase() === "new";
  if (fixViewPortsExperiment) {
    if (this.isAndroidMobileDevice() && !this.isNewChromeOnAndroid()) {
      smallerSide = smallerSide / window.devicePixelRatio;
    }
  }
  return smallerSide;
}

获取网页被卷去的位置

function getScrollXY() {
  return document.body.scrollTop
    ? {
        x: document.body.scrollLeft,
        y: document.body.scrollTop
      }
    : {
        x: document.documentElement.scrollLeft,
        y: document.documentElement.scrollTop
      };
}

获取URL上的参数

// 获取URL中的某参数值,不区分大小写
// 获取URL中的某参数值,不区分大小写,
// 默认是取'hash'里的参数,
// 如果传其他参数支持取‘search’中的参数
// @param {String} name 参数名称
export function getUrlParam(name, type = "hash") {
  let newName = name,
    reg = new RegExp("(^|&)" + newName + "=([^&]*)(&|$)", "i"),
    paramHash = window.location.hash.split("?")[1] || "",
    paramSearch = window.location.search.split("?")[1] || "",
    param;

  type === "hash" ? (param = paramHash) : (param = paramSearch);

  let result = param.match(reg);

  if (result != null) {
    return result[2].split("/")[0];
  }
  return null;
}

检验URL链接是否有效

function getUrlState(URL) {
  var xmlhttp = new ActiveXObject("microsoft.xmlhttp");
  xmlhttp.Open("GET", URL, false);
  try {
    xmlhttp.Send();
  } catch (e) {
  } finally {
    var result = xmlhttp.responseText;
    if (result) {
      if (xmlhttp.Status == 200) {
        return true;
      } else {
        return false;
      }
    } else {
      return false;
    }
  }
}

获取窗体可见范围的宽与高

function getViewSize() {
  var de = document.documentElement;
  var db = document.body;
  var viewW = de.clientWidth == 0 ? db.clientWidth : de.clientWidth;
  var viewH = de.clientHeight == 0 ? db.clientHeight : de.clientHeight;
  return Array(viewW, viewH);
}

获取移动设备最大化大小

function getZoom() {
  var screenWidth =
    Math.abs(window.orientation) === 90
      ? Math.max(screen.height, screen.width)
      : Math.min(screen.height, screen.width);
  if (this.isAndroidMobileDevice() && !this.isNewChromeOnAndroid()) {
    screenWidth = screenWidth / window.devicePixelRatio;
  }
  var FixViewPortsExperiment =
    rendererModel.runningExperiments.FixViewport ||
    rendererModel.runningExperiments.fixviewport;
  var FixViewPortsExperimentRunning =
    FixViewPortsExperiment &&
    (FixViewPortsExperiment === "New" || FixViewPortsExperiment === "new");
  if (FixViewPortsExperimentRunning) {
    return screenWidth / window.innerWidth;
  } else {
    return screenWidth / document.body.offsetWidth;
  }
}

判断是否安卓移动设备访问

function isAndroidMobileDevice() {
  return /android/i.test(navigator.userAgent.toLowerCase());
}

判断是否苹果移动设备访问

function isAppleMobileDevice() {
  return /iphone|ipod|ipad|Macintosh/i.test(navigator.userAgent.toLowerCase());
}

判断是否为数字类型

function isDigit(value) {
  var patrn = /^[0-9]*$/;
  if (patrn.exec(value) == null || value == "") {
    return false;
  } else {
    return true;
  }
}

是否是某类手机型号

// 用devicePixelRatio和分辨率判断
const isIphonex = () => {
  // X XS, XS Max, XR
  const xSeriesConfig = [
    {
      devicePixelRatio: 3,
      width: 375,
      height: 812
    },
    {
      devicePixelRatio: 3,
      width: 414,
      height: 896
    },
    {
      devicePixelRatio: 2,
      width: 414,
      height: 896
    }
  ];
  // h5
  if (typeof window !== "undefined" && window) {
    const isIOS = /iphone/gi.test(window.navigator.userAgent);
    if (!isIOS) return false;
    const { devicePixelRatio, screen } = window;
    const { width, height } = screen;
    return xSeriesConfig.some(
      item =>
        item.devicePixelRatio === devicePixelRatio &&
        item.width === width &&
        item.height === height
    );
  }
  return false;
};

判断是否移动设备

function isMobile() {
  if (typeof this._isMobile === "boolean") {
    return this._isMobile;
  }
  var screenWidth = this.getScreenWidth();
  var fixViewPortsExperiment =
    rendererModel.runningExperiments.FixViewport ||
    rendererModel.runningExperiments.fixviewport;
  var fixViewPortsExperimentRunning =
    fixViewPortsExperiment && fixViewPortsExperiment.toLowerCase() === "new";
  if (!fixViewPortsExperiment) {
    if (!this.isAppleMobileDevice()) {
      screenWidth = screenWidth / window.devicePixelRatio;
    }
  }
  var isMobileScreenSize = screenWidth < 600;
  var isMobileUserAgent = false;
  this._isMobile = isMobileScreenSize && this.isTouchScreen();
  return this._isMobile;
}

判断吗是否手机号码

function isMobileNumber(e) {
  var i =
      "134,135,136,137,138,139,150,151,152,157,158,159,187,188,147,182,183,184,178",
    n = "130,131,132,155,156,185,186,145,176",
    a = "133,153,180,181,189,177,173,170",
    o = e || "",
    r = o.substring(0, 3),
    d = o.substring(0, 4),
    s =
      !!/^1\d{10}$/.test(o) &&
      (n.indexOf(r) >= 0
        ? "联通"
        : a.indexOf(r) >= 0
        ? "电信"
        : "1349" == d
        ? "电信"
        : i.indexOf(r) >= 0
        ? "移动"
        : "未知");
  return s;
}

判断是否是移动设备访问

function isMobileUserAgent() {
  return /iphone|ipod|android.*mobile|windows.*phone|blackberry.*mobile/i.test(
    window.navigator.userAgent.toLowerCase()
  );
}

判断鼠标是否移出事件

function isMouseOut(e, handler) {
  if (e.type !== "mouseout") {
    return false;
  }
  var reltg = e.relatedTarget
    ? e.relatedTarget
    : e.type === "mouseout"
    ? e.toElement
    : e.fromElement;
  while (reltg && reltg !== handler) {
    reltg = reltg.parentNode;
  }
  return reltg !== handler;
}

判断是否Touch屏幕

function isTouchScreen() {
  return (
    "ontouchstart" in window ||
    (window.DocumentTouch && document instanceof DocumentTouch)
  );
}

判断是否为网址

function isURL(strUrl) {
  var regular = /^\b(((https?|ftp):\/\/)?[-a-z0-9]+(\.[-a-z0-9]+)*\.(?:com|edu|gov|int|mil|net|org|biz|info|name|museum|asia|coop|aero|[a-z][a-z]|((25[0-5])|(2[0-4]\d)|(1\d\d)|([1-9]\d)|\d))\b(\/[-a-z0-9_:\@&?=+,.!\/~%\$]*)?)$/i;
  if (regular.test(strUrl)) {
    return true;
  } else {
    return false;
  }
}

判断是否打开视窗

function isViewportOpen() {
  return !!document.getElementById("wixMobileViewport");
}

加载样式文件

function loadStyle(url) {
  try {
    document.createStyleSheet(url);
  } catch (e) {
    var cssLink = document.createElement("link");
    cssLink.rel = "stylesheet";
    cssLink.type = "text/css";
    cssLink.href = url;
    var head = document.getElementsByTagName("head")[0];
    head.appendChild(cssLink);
  }
}

替换地址栏

function locationReplace(url) {
  if (history.replaceState) {
    history.replaceState(null, document.title, url);
    history.go(0);
  } else {
    location.replace(url);
  }
}

解决offsetX兼容性问题

// 针对火狐不支持offsetX/Y
function getOffset(e) {
  var target = e.target, // 当前触发的目标对象
    eventCoord,
    pageCoord,
    offsetCoord;

  // 计算当前触发元素到文档的距离
  pageCoord = getPageCoord(target);

  // 计算光标到文档的距离
  eventCoord = {
    X: window.pageXOffset + e.clientX,
    Y: window.pageYOffset + e.clientY
  };

  // 相减获取光标到第一个定位的父元素的坐标
  offsetCoord = {
    X: eventCoord.X - pageCoord.X,
    Y: eventCoord.Y - pageCoord.Y
  };
  return offsetCoord;
}

function getPageCoord(element) {
  var coord = { X: 0, Y: 0 };
  // 计算从当前触发元素到根节点为止,
  // 各级 offsetParent 元素的 offsetLeft 或 offsetTop 值之和
  while (element) {
    coord.X += element.offsetLeft;
    coord.Y += element.offsetTop;
    element = element.offsetParent;
  }
  return coord;
}

打开一个窗体通用方法

function openWindow(url, windowName, width, height) {
  var x = parseInt(screen.width / 2.0) - width / 2.0;
  var y = parseInt(screen.height / 2.0) - height / 2.0;
  var isMSIE = navigator.appName == "Microsoft Internet Explorer";
  if (isMSIE) {
    var p = "resizable=1,location=no,scrollbars=no,width=";
    p = p + width;
    p = p + ",height=";
    p = p + height;
    p = p + ",left=";
    p = p + x;
    p = p + ",top=";
    p = p + y;
    retval = window.open(url, windowName, p);
  } else {
    var win = window.open(
      url,
      "ZyiisPopup",
      "top=" +
        y +
        ",left=" +
        x +
        ",scrollbars=" +
        scrollbars +
        ",dialog=yes,modal=yes,width=" +
        width +
        ",height=" +
        height +
        ",resizable=no"
    );
    eval("try { win.resizeTo(width, height); } catch(e) { }");
    win.focus();
  }
}

将键值对拼接成URL带参数

export default const fnParams2Url = obj=> {
      let aUrl = []
      let fnAdd = function(key, value) {
        return key + '=' + value
      }
      for (var k in obj) {
        aUrl.push(fnAdd(k, obj[k]))
      }
      return encodeURIComponent(aUrl.join('&'))
 }

去掉url前缀

function removeUrlPrefix(a) {
  a = a
    .replace(//g, ":")
    .replace(//g, ".")
    .replace(//g, "/");
  while (
    trim(a)
      .toLowerCase()
      .indexOf("http://") == 0
  ) {
    a = trim(a.replace(/http:\/\//i, ""));
  }
  return a;
}

替换全部

String.prototype.replaceAll = function(s1, s2) {
  return this.replace(new RegExp(s1, "gm"), s2);
};

resize的操作

(function() {
  var fn = function() {
    var w = document.documentElement
        ? document.documentElement.clientWidth
        : document.body.clientWidth,
      r = 1255,
      b = Element.extend(document.body),
      classname = b.className;
    if (w < r) {
      //当窗体的宽度小于1255的时候执行相应的操作
    } else {
      //当窗体的宽度大于1255的时候执行相应的操作
    }
  };
  if (window.addEventListener) {
    window.addEventListener("resize", function() {
      fn();
    });
  } else if (window.attachEvent) {
    window.attachEvent("onresize", function() {
      fn();
    });
  }
  fn();
})();

滚动到顶部

// 使用document.documentElement.scrollTop 或 document.body.scrollTop 获取到顶部的距离,从顶部
// 滚动一小部分距离。使用window.requestAnimationFrame()来滚动。
// @example
// scrollToTop();
function scrollToTop() {
  var c = document.documentElement.scrollTop || document.body.scrollTop;

  if (c > 0) {
    window.requestAnimationFrame(scrollToTop);
    window.scrollTo(0, c - c / 8);
  }
}

设置cookie值

function setCookie(name, value, Hours) {
  var d = new Date();
  var offset = 8;
  var utc = d.getTime() + d.getTimezoneOffset() * 60000;
  var nd = utc + 3600000 * offset;
  var exp = new Date(nd);
  exp.setTime(exp.getTime() + Hours * 60 * 60 * 1000);
  document.cookie =
    name +
    "=" +
    escape(value) +
    ";path=/;expires=" +
    exp.toGMTString() +
    ";domain=360doc.com;";
}

设为首页

function setHomepage() {
  if (document.all) {
    document.body.style.behavior = "url(#default#homepage)";
    document.body.setHomePage("http://w3cboy.com");
  } else if (window.sidebar) {
    if (window.netscape) {
      try {
        netscape.security.PrivilegeManager.enablePrivilege(
          "UniversalXPConnect"
        );
      } catch (e) {
        alert(
          "该操作被浏览器拒绝,如果想启用该功能,请在地址栏内输入 about:config,然后将项 signed.applets.codebase_principal_support 值该为true"
        );
      }
    }
    var prefs = Components.classes[
      "@mozilla.org/preferences-service;1"
    ].getService(Components.interfaces.nsIPrefBranch);
    prefs.setCharPref("browser.startup.homepage", "http://w3cboy.com");
  }
}

按字母排序,对每行进行数组排序

function setSort() {
  var text = K1.value
    .split(/[\r\n]/)
    .sort()
    .join("\r\n"); //顺序
  var test = K1.value
    .split(/[\r\n]/)
    .sort()
    .reverse()
    .join("\r\n"); //反序
  K1.value = K1.value != text ? text : test;
}

延时执行

// 比如 sleep(1000) 意味着等待1000毫秒,还可从 Promise、Generator、Async/Await 等角度实现。
// Promise
const sleep = time => {
  return new Promise(resolve => setTimeout(resolve, time));
};

sleep(1000).then(() => {
  console.log(1);
});


// Generator
function* sleepGenerator(time) {
  yield new Promise(function(resolve, reject) {
    setTimeout(resolve, time);
  });
}

sleepGenerator(1000)
  .next()
  .value.then(() => {
    console.log(1);
  });

//async
function sleep(time) {
  return new Promise(resolve => setTimeout(resolve, time));
}

async function output() {
  let out = await sleep(1000);
  console.log(1);
  return out;
}

output();

function sleep(callback, time) {
  if (typeof callback === "function") {
    setTimeout(callback, time);
  }
}

function output() {
  console.log(1);
}

sleep(output, 1000);

判断是否以某个字符串开头

String.prototype.startWith = function(s) {
  return this.indexOf(s) == 0;
};

清除脚本内容

function stripscript(s) {
  return s.replace(/<script.*?>.*?<\/script>/gi, "");
}

时间个性化输出功能

/*
1、< 60s, 显示为“刚刚”
2、>= 1min && < 60 min, 显示与当前时间差“XX分钟前”
3、>= 60min && < 1day, 显示与当前时间差“今天 XX:XX”
4、>= 1day && < 1year, 显示日期“XX月XX日 XX:XX”
5、>= 1year, 显示具体日期“XXXX年XX月XX日 XX:XX”
*/
function timeFormat(time) {
  var date = new Date(time),
    curDate = new Date(),
    year = date.getFullYear(),
    month = date.getMonth() + 10,
    day = date.getDate(),
    hour = date.getHours(),
    minute = date.getMinutes(),
    curYear = curDate.getFullYear(),
    curHour = curDate.getHours(),
    timeStr;

  if (year < curYear) {
    timeStr = year + "年" + month + "月" + day + "日 " + hour + ":" + minute;
  } else {
    var pastTime = curDate - date,
      pastH = pastTime / 3600000;

    if (pastH > curHour) {
      timeStr = month + "月" + day + "日 " + hour + ":" + minute;
    } else if (pastH >= 1) {
      timeStr = "今天 " + hour + ":" + minute + "分";
    } else {
      var pastM = curDate.getMinutes() - minute;
      if (pastM > 1) {
        timeStr = pastM + "分钟前";
      } else {
        timeStr = "刚刚";
      }
    }
  }
  return timeStr;
}

全角转换为半角函数

function toCDB(str) {
  var result = "";
  for (var i = 0; i < str.length; i++) {
    code = str.charCodeAt(i);
    if (code >= 65281 && code <= 65374) {
      result += String.fromCharCode(str.charCodeAt(i) - 65248);
    } else if (code == 12288) {
      result += String.fromCharCode(str.charCodeAt(i) - 12288 + 32);
    } else {
      result += str.charAt(i);
    }
  }
  return result;
}

半角转换为全角函数

function toDBC(str) {
  var result = "";
  for (var i = 0; i < str.length; i++) {
    code = str.charCodeAt(i);
    if (code >= 33 && code <= 126) {
      result += String.fromCharCode(str.charCodeAt(i) + 65248);
    } else if (code == 32) {
      result += String.fromCharCode(str.charCodeAt(i) + 12288 - 32);
    } else {
      result += str.charAt(i);
    }
  }
  return result;
}

金额大写转换函数

function transform(tranvalue) {
  try {
    var i = 1;
    var dw2 = new Array("", "万", "亿"); //大单位
    var dw1 = new Array("拾", "佰", "仟"); //小单位
    var dw = new Array(
      "零",
      "壹",
      "贰",
      "叁",
      "肆",
      "伍",
      "陆",
      "柒",
      "捌",
      "玖"
    ); 
    //整数部分用
    //以下是小写转换成大写显示在合计大写的文本框中
    //分离整数与小数
    var source = splits(tranvalue);
    var num = source[0];
    var dig = source[1];
    //转换整数部分
    var k1 = 0; //计小单位
    var k2 = 0; //计大单位
    var sum = 0;
    var str = "";
    var len = source[0].length; //整数的长度
    for (i = 1; i <= len; i++) {
      var n = source[0].charAt(len - i); //取得某个位数上的数字
      var bn = 0;
      if (len - i - 1 >= 0) {
        bn = source[0].charAt(len - i - 1); //取得某个位数前一位上的数字
      }
      sum = sum + Number(n);
      if (sum != 0) {
        str = dw[Number(n)].concat(str); //取得该数字对应的大写数字,并插入到str字符串的前面
        if (n == "0") sum = 0;
      }
      if (len - i - 1 >= 0) {
        //在数字范围内
        if (k1 != 3) {
          //加小单位
          if (bn != 0) {
            str = dw1[k1].concat(str);
          }
          k1++;
        } else {
          //不加小单位,加大单位
          k1 = 0;
          var temp = str.charAt(0);
          if (temp == "万" || temp == "亿")
            //若大单位前没有数字则舍去大单位
            str = str.substr(1, str.length - 1);
          str = dw2[k2].concat(str);
          sum = 0;
        }
      }
      if (k1 == 3) {
        //小单位到千则大单位进一
        k2++;
      }
    }
    //转换小数部分
    var strdig = "";
    if (dig != "") {
      var n = dig.charAt(0);
      if (n != 0) {
        strdig += dw[Number(n)] + "角"; //加数字
      }
      var n = dig.charAt(1);
      if (n != 0) {
        strdig += dw[Number(n)] + "分"; //加数字
      }
    }
    str += "元" + strdig;
  } catch (e) {
    return "0元";
  }
  return str;
}
//拆分整数与小数
function splits(tranvalue) {
  var value = new Array("", "");
  temp = tranvalue.split(".");
  for (var i = 0; i < temp.length; i++) {
    value = temp;
  }
  return value;
}

清除空格

String.prototype.trim = function() {
  var reExtraSpace = /^\s*(.*?)\s+$/;
  return this.replace(reExtraSpace, "$1");
};

// 清除左空格
function ltrim(s) {
  return s.replace(/^(\s*| *)/, "");
}

// 清除右空格
function rtrim(s) {
  return s.replace(/(\s*| *)$/, "");
}

随机数时间戳

function uniqueId() {
  var a = Math.random,
    b = parseInt;
  return (
    Number(new Date()).toString() + b(10 * a()) + b(10 * a()) + b(10 * a())
  );
}

实现utf8解码

function utf8_decode(str_data) {
  var tmp_arr = [],
    i = 0,
    ac = 0,
    c1 = 0,
    c2 = 0,
    c3 = 0;
  str_data += "";
  while (i < str_data.length) {
    c1 = str_data.charCodeAt(i);
    if (c1 < 128) {
      tmp_arr[ac++] = String.fromCharCode(c1);
      i++;
    } else if (c1 > 191 && c1 < 224) {
      c2 = str_data.charCodeAt(i + 1);
      tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
      i += 2;
    } else {
      c2 = str_data.charCodeAt(i + 1);
      c3 = str_data.charCodeAt(i + 2);
      tmp_arr[ac++] = String.fromCharCode(
        ((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)
      );
      i += 3;
    }
  }
  return tmp_arr.join("");
}

如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的肯定是我前进的最大动力😁

  • 阅读往期更多优质文章可移步我的 GitHub 查看

day12

商品列表

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>

	<body>
		<ul class="goods">
			<li data-guid="g01">
				<p>短袖衬衣</p>
				<p class="price">98.88</p>
				<div class="add2cart">
					<button>添加到购物车</button>
				</div>
			</li>
			<li data-guid="g02">
				<p>短袖衬衣2</p>
				<p class="price">88.88</p>
				<div class="add2cart">
					<button>添加到购物车</button>
				</div>
			</li>
			<li data-guid="g03">
				<p>短袖衬衣3</p>
				<p class="price">9.98</p>
				<div class="add2cart">
					<button>添加到购物车</button>
				</div>
			</li>
			<li data-guid="g04">
				<p>短袖衬衣4</p>
				<p class="price">8.88</p>
				<div class="add2cart">
					<button>添加到购物车</button>
				</div>
			</li>
		</ul>
		<a href="结算.html">去结算</a>
		<script>
			var goods = document.querySelector(".goods");
			console.log(goods)
			var carlist = [];
			var cookie = document.cookie.split('; ');
			for(var i = 0; i < cookie.length; i++) {
				var arr = cookie[i].split('=');
				if(arr[0] === 'carlist') {
					// 在cookie中得到的数据都是字符串
					// 所以需要转换成数组/对象
					carlist = JSON.parse(arr[1]);
				}
				console.log(carlist)
			}
			goods.addEventListener("click", function(e) {
				console.log(e.target.nodeName.toLowerCase() === "button")
				if(e.target.nodeName.toLowerCase() === "button") {
					var li = e.target.parentNode.parentNode;
					var liId = li.getAttribute('data-guid');
					console.log(liId)
				}
				for(var i = 0; i < carlist.length; i++) {
					if(carlist[i].gid === liId) {
						carlist[i].num++;
						break;
					}
				}
				console.log(i)
				console.log(li.children[0].innerHTML)
				if(i === carlist.length) {
					// 创建一个对象,用于保存商品信息
					var obj = {};
					obj.gid = liId;
					obj.name = li.firstElementChild.innerHTML;
					obj.price = li.children[1].innerHTML;
					obj.num = 1;
					carlist.push(obj);
				}
				console.log(carlist);

				var now = new Date();
				now.setDate(now.getDate() + 3);
				document.cookie = ('carlist=' + JSON.stringify(carlist) + ';expires=' + now);
			})
		</script>
	</body>

</html>

购物车

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<ul>
		</ul>
		<button id="btnClear">清除购物车</button>
		<script>
			var carlist = [];
			var cookie = document.cookie.split('; ');
			for(var i = 0; i < cookie.length; i++) {
				var arr = cookie[i].split('=');
				if(arr[0] === 'carlist') {
					// 在cookie中得到的数据都是字符串
					// 所以需要转换成数组/对象
					carlist = JSON.parse(arr[1]);
				}
				console.log(carlist)
			}
			var ul = document.querySelector("ul")

			function render() {
				var str = carlist.map(function(item) {
					return "<li data-guid='" + item.gid + "'>" + item.name + "</li>"
				}).join("")
				console.log(str);
				ul.innerHTML = "";
				ul.innerHTML = str;
			}
			render();
			ul.addEventListener("click", function(e) {
				console.log(e.target.getAttribute("data-guid"))
				var liId = e.target.getAttribute("data-guid");
				//删除
				/*for(var i=0;i<carlist.length;i++){
					if(carlist[i].gid === liId){
						carlist.splice(i,1);
						render();
						var now = new Date();
						now.setDate(now.getDate() + 3);
						document.cookie = 'carlist=' + JSON.stringify(carlist) + ';expires='+now;
						break;
					}
				}*/
				//增加
				for(var i = 0; i < carlist.length; i++) {
					if(carlist[i].gid === liId) {
						carlist[i].num++;
						render();
						var now = new Date();
						now.setDate(now.getDate() + 3);
						document.cookie = 'carlist=' + JSON.stringify(carlist) + ';expires=' + now;
						break;
					}
				}
			})
			// 清空购物车
			// 删除carlist这个cookie
			// 利用设置过期时间达到删除的效果。
			var btnClear = document.querySelector("#btnClear");
			btnClear.onclick = function() {
				var now = new Date();
				now.setDate(now.getDate() - 1);
				document.cookie = 'carlist=null;expires=' + now;
				// 清空数组
				carlist = [];
				render();
			}
		</script>
	</body>

</html>

在腾讯这一年,坚守初心持续单纯 | 2021年终总结

image

「时光不负,创作不停,本文正在参加2021 年终总结征文大赛

前言

每年的 12 月总像是一场告别,2021 年从起点到终点也该时候跟你说一声再见,忙碌的一年难得周五请假搬完了家,在夕阳下写下这篇文章回忆总结这一年,这一年回望来时的路,至少有一些同事给我鼓励和安慰,至少有些事不曾莫名伤心和流泪,至少还有家人相互依偎。

在掘金

回顾了今年在掘金的表现,说实话有点惭愧,只发表了四篇文章,虽然没有多少阅读量,但我每篇都写得很用心,其中写了一篇关于前端黑产技术的实现原理,好不容易才通过了审核发表,然后又花了两个月把 Fackboook 的单元测试框架从头到尾看了一篇,写了篇如何从零开始实现一个类 Jest 的单元测试框架,写完 Fackboook 就改名为 Meta 了,后面华为孟晚舟被释放成功回国,激动之下写了一篇关于鸿蒙 OpenHarmony 的开发分享。先定了个小目标,希望明年能写完关于单元测试专题的掘金小册并发表,目前只写了两三万字,距离发表还很遥远,加油吧!

image.png

在开源社区

除了在掘金产出比较低之外,今年在开源社区的贡献也不是很多,因为大部分都需要在周末或者业余时间完成,观察了下提交记录,在三四季度基本断开连接,一句话总结就是越到后面越有心无力了,或许也是因为平时工作也越来越忙了,不像上几年沉迷于研究各种技术框架,给 VSCodeOmi 贡献一些代码。只有在年初给国产的 Hbuilder 写了些插件,年底参加了国内码云的鸿蒙 OpenHarmony 比赛,移植了一个表格渲染引擎,收获了 7k 奖金和开发板能勉强说一下了。

image.png

在工作上

这一年其实没积累到太多的知识,也忘记了很多的东西,用得比较多的是 ReactlessTypescript 等技术栈,说实话我甚至都忘了 VueAngular 等框架是怎么用的了,如果让我写一个 Vue 相关的项目,我可能需要去官网花好长时间去重温,Vue3 也没好好学习,忘了怎么使用路由和状态管理,有哪些生命周期,有哪些钩子,怎么实现一个 loaderplugin,怎么写一个 VSCodeChrome 插件,怎么手写 Promise 和防抖节流,怎么实现设计模式,怎么算空间和时间复杂度,如果有人问我这些问题,我可能真的不能再详细答出来了。

肌肉记忆都保留在公司的产品调试链路,如何环境切换,如何定位 Bug,如何解决用户的反馈问题,记忆也感觉差了很多,不知道是不是熬夜变多了,休息不充分,回头看看自己的工作提交记录,除了假期大部分时间都是在线的,提交密度和代码量还是远远大于开源社区的。

image.png

今年在开发功能的时候,要经常思考怎么写一个易维护,既方便别人和自己的组件,在维护模块修复 Bug 的时候,要经常思考前面的人为什么这么写,他可能遇到什么困难,那个时期可能有那个时期的局限性,可能那个时候有上线压力,也可能那时候并没有那么思考长远,也可能那时候并没有规划好。

当下的我能做好的是对未来的自己或者别人负责,若干年之后自己或者别人再看回你写的这段代码能不能减少他一点疑惑,写的再好的代码远不如一条注释让人阔然开朗,至少让他明白为什么我要这样写,甚至有时候我觉得写注释是一种自信的表现,当别人 Code Review 你的代码的时候会更清晰也更能看出你的态度,如果它是相对稳定的代码我会在维护的时候尽可能多留下点注释,如果它是比较不稳定代码,我会思考重构部分或者整体,并留下单元测试。

image.png

所以在今年我写下了比以往任何时候都多的注释和单测,勿以善小而不为,勿以恶小而为之,有些代码经历了各种时期的变动,但注释可能还是当初的样子,因为我发现删代码是很常见,但是删注释真的比较少,那它存在的意义相对多点,比翻修改的历史记录来的更高效,如果最后代码和单测都被删掉了,那这才是它最终的归宿和使命,希望自己自己明年写的单测和注释再好一点,不求尽如人意但求问心无愧。

给力的同事

翻了下相册,看到了这张代表着运气爆棚的照片,不得不说下,人生首次人品爆发,在年初参加部门年会抽到了手机,从座位到领奖台只有十几米,章子怡花了十七年才能登台拿到了影后,而我花光了一年所有的运气才拿到了一部手机哈哈,但我依然很喜欢站在奖台上,因为那需要很大的力气和运气,当然希望今年也能抽到奖品。

image.png

身旁几位大佬带我一个坑,也让我在台上领到了梦寐以求的部门奖金,教会了我如何实现大型前端项目函数调用链跟踪和分析方案,教会落地商业化实现的方案,教会我如何各种姿势薅羊毛,教会我如何花式凡尔赛,教会我攻克难题优化性能,教会了我 try catch 用得好,性能翻一番...

image.png

紧接着部门架构的升级调整,搬到了新的办公地,虽然,又认识到一批非常优秀的同事,让我每周节目都很丰富,好好给这些可爱的同事点下名:

  • 每周一抱着 xunxunjsonziming 和杜兰特等大腿去打篮球
  • 每周三跟着火箭哥去游泳,时不时还可以拉上小海豹和刘总
  • 浩哥安排的 KTV 和打羽毛球让我可以有机会跟漂亮妹子切磋下
  • 大圣JC,凡凡,国春的豪华代练团队,让我明白打王者,LOL 和怪猎原来那么轻松
  • solin 的篮球比赛让我热血沸腾,好想舔下朱芳雨给他发的冠军戒指
  • 莲神的 Typescript 让我顶礼膜拜,CR 人肉扫描让我受益匪浅
  • 老宝带去跑步和跳操,让我一直保持住健身的好习惯

正因为有一个优秀的部门和一群优秀的同事,埋在春天的那些种子,都在之后冬夏逐渐焕发。明年我们都要加油,继续迎接着下一个阶段全新的挑战!

image.png

旅途和生活

今年我也去了很多的地方,去了些离工作地不会很远的地方去旅游,因为疫情和工作的原因,要随时待命赶回来处理工作,怕 14 天隔离,回来工位都没了哈哈,在生活也解锁了很多新的技能,部门组织去了花都融创滑雪,第一次滑雪,从 66 米高,坡度 21 度一直龟速滑下来,当然免不了摔了无数次,但很刺激,以后有时间还要来玩。

image.png

去打了第一次的棒球,初体验很好就是有点累,但规则对于我这种头脑简单的人来说实在是太复杂了,就记得要跑得快要跑准垒位,说好的没有身体接触的运动,居然还有触杀这种反人类的设计,有点费脑费脚,场下试了下用棒子击飞一个固定的球的难度也很大,属于随缘碰运气,更别用说要打高打远了,所以我给自己定了个需求,人生一定要实现一次全垒打。

image.png

坐船去了一趟珠海,在台风天看到了传说中的港珠澳大桥,沿途晕船吐了一路,在长隆看到了鲸鱼,海牛和海豚,坐了可怕的鹦鹉过山车,在拱北情侣路逛街吃烧烤,用 iPad 画了几幅油画。

image.png

坐高铁去了长沙参加旧同事的婚礼,喝了茶颜悦色,吃了臭豆腐,串串,小龙虾和辣椒炒肉,走过湘江,橘子洲,岳麓山和黄兴广场。

image.png

参加公司的团建坐飞机去了海南,住进了网红酒店亚特兰蒂斯,让我体验了一波高大上的五星级酒店,去了水世界爽了一天,近距离看看海洋世界,玩到了海神之跃各种旋转大喇叭。

image.png

租车自驾游去了新会和韶关,玩了动力伞,走过玻璃桥,泡了温泉,但最难忘的是经历第一次交通事故,由于是新手上路,又不熟悉路况,在一个双十字路口转弯的时候没注意,刮到了别人的车,学会了打电话等交警然后报保险冷静处理,当然幸好是没有人员受伤,也算是给自己一个教训。

最后

很多时候当你经历一些成长的事情,就会慢慢发现学会让自己开心,不为难自己,懂得在生活中,事业中和感情中与自己和解真的很重要,我依然觉得自己几年前在剃光头进手术室前是最帅最勇敢的,但我更觉得当下的我是开心和幸运的,学会和自己和解,真的会少走很多弯路,人生还很长,我还有很多 Switch 游戏,电脑和手机游戏想玩,还有很多美丽的地方想去,还有很多有趣的人等着我去认识。

image.png

最后给明年的自己一些目标,多读几本读书,多刷几部剧,少吃点垃圾食品,多睡点觉,多陪陪家人,保持健康坚持学习直到我生命停止呼吸。坚持自己的初心,用心工作开心生活,纵使饱经世故,亦持续单纯,也希望明年有更多志同道合的人加入我们腾讯 AlloyTeam 团队,一起去探索和遨游!分享总结不易,如果文章能给您一点启发,请不要稀罕你的赞~

前端工程师必备实用网站

一、配色类网站

这个网站给我们提供了很多的配色方案,我们直接使用就OK了。使用方法也很简单,鼠标移动到对应的颜色上,我们就可以看到颜色的十六进制码,复制这个颜色到工具里就可以使用了。

这个是Adobe公司出的,他提供了多种配色方案。我们点击圆盘中间的点,就可以调整出我们想要的配色方案。

这是一个提取现有图片配色方案的工具。我们上传一张图片,它就会帮我们把图片的配色提取出来供我们使用。

这个网站是为WEB设计,开发中经常用到的安全色。网站内列出了颜色的十六进制码和RGB码,复制粘贴就可以了。

这是一个在线RGB和十六进制颜色码转换工具。在对应的位置填入十六进制代码,点击转换,我们就可以获取到RGB颜色的代码了。

二、图标类网站

这是阿里巴巴旗下的图标库网站,直接搜索关键词就可以找到大批的图标。下载图标的时候我们还可以选择颜色、大小、格式,根据自己的需要下载就好了。

这也是一个非常有名的图标库,与上面那个不同的是,这里的图标不是单一颜色的,而是设计好的颜色。下载图标也很简单,直接点击对应图标上面的格式就可以下载。

奥森图标(Font Awesome)提供丰富的矢量字体图标—通过CSS可以任意控制所有图标的大小 ,颜色,阴影。

三、插件类网站

这个网站分享jQuery插件和提供各种jQuery特效的详细使用方法,在线预览,jQuery插件下载及教程

这个网站与上一个网站类似,也提供了大量的jQuery插件

这是一个强大的模块化前端框架

H-ui前端框架,一个轻量级前端框架,简单免费,兼容性好,服务**网站。

四、素材类网站

千库网,一个免费下载图片素材的网站

Unsplash是一个分享免费高质量照片的网站,照片分辨率都挺大,而且都是真实的摄影师作品,图片多是风景和静物。

五、字体类网站

有字库,一个免下载字体,直接在线引用字体的网站。

PS字体库,包含了几乎所有类型的字体,下载好安装,PS中就可以使用了。

六、工具类

一个在线工具网站,包含大量在线工具

一个在线图片压缩工具,可以批量使用

各种前端类工具和技术文章

腾讯文档给 VSCode 贡献的 400 多行核心代码都干了什么

在前几天腾讯文档给 VSCode 合入了大概 400 行核心代码,主要涉及到 VSCode 配置化的部分,增强其插件化能力,提供更多的匹配接口,整理部分代码结构和补充功能单测。

image

由于腾讯文档最近在完善我们的配置化系统,在完善的过程也探索了多种实现方案,也分析了很多产品的实现方式,如大名鼎鼎的 VSCode,我们也希望把我们积累经验贡献给开源社区,一起共同进步。

image

其中合入腾讯文档提供的代码是微软 VSCode 团队现主要负责人之一 Alexdima,也是 Monaco Editor 负责人(VSCode前身),也是同 Erich Gamma (VSCode之父) 来自苏黎世同一个团队,特别感谢他和他团队的支持,还帮我们挖坑的代码写了不少单测...

image

由于我们是给 VSCode 贡献了这部分代码,那我们就从 VSCode 本身开始聊起,我们给 VSCode 的配置化贡献了什么?我相信大部分的开发者都使用过 VSCode,所以配置化应该不陌生,由于使用者众多,任何编辑器其实都不能做到面面俱到去满足所有的使用者,如果什么用户的需求都要满足,就需要把所有的功能都塞进去,这不但臃肿,还不好维护。

所以我们需要配置化去丰富和拓展,减轻编辑器本身的包袱,把部分内容移交给用户/合作方去定制,就如我们可以在 VSCode 的设置面板选择编辑器的颜色,更换它的主题背景。

image

也可以在快捷键面板里面绑定或者解绑我们的快捷键,更换我们的字体大小和改变我们的悬浮信息等,这些其实都离不开背后实现的一套配置化系统。

image

上面举例的都是有默认的配置,可以通过面板去更改的,当然还有些隐藏的配置我们无需在面板改变也能实现配置,比如缩小 VSCode 的界面大小,某些功能就会自动隐藏,这种也是属于配置化。

image

我们除了通过面板可视化操作,还可以通过插件来配置界面,VSCode 中插件的核心就是一个配置文件 package.json,里面拥有提供了配置点,只需按要求编写正确的配置点就可以改变 VSCode 的视图状态,里面最主要的字段就是 contributes 字段:

  • contributes.configuration:插件有哪些可供用户配置的选项,提供的界面跟上面切背景颜色棉棒相似
  • contributes.configurationDefaults:覆盖 vscode 默认的一些编辑器配置
  • contributes.commands:向 vscode 的命令系统注册一些可供用户调用的命令
  • contributes.menus:扩展菜单
  • ...

这是更换编辑器各个位置颜色的配置参数,很简单明了。

{
  "colors": {
    "activityBar.background": "#333842",
    "activityBar.foreground": "#D7DAE0",
    "activityBarBadge.background": "#528BFF"
  }
}

里面的代码思路其实就是挖了一个给第三方,然后支持参数的填入,下面代码就是一个示例,把配置文件的颜色读取出来,然后生成一个新的颜色规则,达到控制面板背景颜色的功能。

const color = theme.getColor(registerColor("activityBar.background"));
if (color) {
  collector.addRule(
    `.monaco-workbench .activitybar > .content > .home-bar > .home-bar-icon-badge { background-color: ${color}}`
  );
}

上面这个最基本的能力在代码实现里面应该是毫无难度的,只需要挖空一个配置点即可,但是实际肯定会再复杂点,此时如果用户想在此功能基础上继续做配置,比如编辑器在 Win 系统的时候颜色变深,在 Mac 系统的时候颜色变浅.

if (color) {
  if (isMacintosh) {
    color = darken(color);
  }
  if (isWindows) {
    color = lighter(color);
  }
  collector.addRule(
    `.monaco-workbench .activitybar > .content > .home-bar > .home-bar-icon-badge { background-color: ${color}}`
  );
}

这里的就需要知会开发者,讲道理对开发者来说难度也不是很大,无非就是往代码里面在插入几段条件判断的代码而已嘛,但是某一天用户说我又得改了,我想在分辨率大于 855 的时候颜色变深,在分辨率小于等于 855 的时候颜色变浅,并且遇到 Linux 系统也得颜色变深。那此时再变更代码来满足客户的需求,不得继续加代码如下了:

if (color) {
  if (isMacintosh || window.innerWidth > 855) {
    color = darken(color);
  }
  if (isLinux) {
    color = darken(color);
  }
  if (isWindows || window.innerWidth <= 855) {
    color = lighter(color);
  }
  collector.addRule(
    `.monaco-workbench .activitybar > .content > .home-bar > .home-bar-icon-badge { background-color: ${color}}`
  );
}

那开发宝宝那能遭得住,谁知道那天的又得改呢,要知道编辑器用户可是上千万啊,用户的需求可是花里胡哨,怎么接得住。

那开发的自己去定制规范,不能让你随意来,我提供变暗和变深的接口,但是规则我不再负责写了,请用户自己提供,所以开发可能会继续调整下代码:

class Color {
  color = theme.getColor(registerColor("activityBar.background"));

  @If(isLinux)
  @If(isMacintosh || window.innerWidth > 855)
  darken() {
    return darken(this.color);
  }

  @If(userRule1)
  @If(userRule2)
  @If(userRule3)
  @If([isWindows, window.innerWidth <= 855].includes(true))
  lighter() {
    return lighter(this.color);
  }
}

上面的只是列了下伪代码,开发者想得很简单,只提供纯粹的 darken 和 lighter,不希望与频繁的条件表达式耦合,所以可能会做判断条件的前置化,那么后续开发者只需叠加装饰器即可给用户增加配置,并且可以动态保留一个装饰器 @If(userRule) 作为配置文件的洞口。

然后提供官方配置文档让他们写类似的 package.json 文件填写对应的参数,那么压力就会转嫁到使用者或者接入者身上。

这种写法看似美好,但会出现很多致命情况,darkenlighter 在执行前已经被带条件表达式给装饰了,后面如果二次执行 darkenlighter 方法则不会再执行装饰器中条件表达式的判断,本质上这两个函数的 descriptor.value 已经被覆写,逻辑从根本上发生了改变。

export const If = (condition: boolean) => {
  console.log(condition);
  return (target: any, name?: any, descriptor?: any) => {
    const func = descriptor?.value;
    if (typeof func === "function") {
      descriptor.value = function (...args: any) {
        return condition && func.apply(this, args);
      };
    }
  };
};

而正常情况下客户端侧 isLinuxisMacintoshisWindows 是不会发生改变的,但是 window.innerWidth 在客户端却是有可能持续发生变化,所以一般情况下对待客户端环境经常变化的值或者需要通过作用域判断值,我不建议写成上面装饰器暴露接口的方案,但是如果这是一个比较固定的配置值,那么这种方案配合 webpackDefinePlugin 会有意外的收获。

new webpack.DefinePlugin({
  isLinux: JSON.stringify(true),
  VERSION: JSON.stringify("5fa3b9"),
  BROWSER_SUPPORTS_HTML5: true,
  TWO: "1+1",
  "typeof window": JSON.stringify("object"),
  "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
});

但是我们很多时候是需要在运行时候进行配置化,上述的其实大部分都能算是静态的配置(俗话说是写死的),比如 if(window.innerWidth > 855) 这个配置参数:

左边 window.innerWidth 在运行时候是变化的,右边 855 在代码是写死的,所以我们一般得把这整段扣一个洞出来进行外部的配置化,一般我们会选用 json 去描述这份配置。

在 VSCode 等应用中,很多地方你没看到 json 文件去配置,那是因为大部分情况给你提供可视化界面去修改配置,但你要知道本质是改动了 json 的配置文件去达到目的的,比如上面的 if(isMacintosh || window.innerWidth > 855) 就被扣到外面的 json 文件中。

// if(isMacintosh || window.innerWidth > 855) ...
// if(isWindows || window.innerWidth <= 855) ...
// ↓

{
  "darken": { "when": "isMacintosh || window.innerWidth > 855" },
  "lighter": { "when": "isWindows || window.innerWidth <= 855" }
}

你一般会需要接入方或者使用者写成上面类似的文件,然后通过服务器配置系统,比如:七彩石,下发到客户端,然后把贡献点放入装饰器中洞口,再执行对应的配置逻辑,大概如下:

@If(JSON.darken)
darken() {
return darken(this.color);
}

@If(JSON.lighter)
lighter() {
return lighter(this.color);
}

JSON.darkenJSON.lighter 分别是对应 JSON 文件中的配置项,所以实际在代码运行时的时候接受的字符串参数:

@If("isMacintosh || window.innerWidth > 855")
darken() {
return darken(this.color);
}

@If("isWindows || window.innerWidth <= 855")
lighter() {
return lighter(this.color);
}

这是大部分配置化绕不开的问题,简单的配置只需要传承好字符串语义即可,但是复杂的配置化可能是带有条件表达式,代码语法等东西,截一张 VSCode 官方插件的配置代码,满满都是配置表达式。

image

本质上这些字符串最终是要解析为布尔值,作为开关去启动 darken 或者 lighter 接口的,所以这里需要花费一些代价去实现表达式解析器,和字符串转义解释引擎。

  • "window.innerWidth" => window.innerWidth
  • "isWindows" => isWindows
  • "isMacintosh || window.innerWidth > 855" => true/false

这个过程中还需要实现校验函数,如果识别到是非法的字符串则不允许解析,免得非法启动配置接口。

  • "isMacintosh || window.innerWidth > 855" => 合法配置参数
  • "isMacintosh |&&| window.innerWidth > 855" => 非法配置参数
  • "isMacintosh \\// window.innerWidth > 855" => 非法配置参数

这种引擎的实现设计其实还有一种更暴力的解决方案,就是把读取的配置字符串完全交给 eval 去处理,这当然可以很快去实现,但是还是刚才上面说到的问题,这个东西如果接受了一段非法的字符串,就会很容易执行一些非法的脚本,绝对不是一个最优的方案。

eval("window.innerWidth > 855"); // true 或者 false

image

{
  "darken": { "when": "isMacintosh || window.innerWidth > 855" },
  "lighter": { "when": "isWindows || window.innerWidth <= 855" }
}

那介绍下我们的解决方案,这里先读取 json 文件,定位到关键 when: xxx 这些地方(VSCode 目前只能暴露 when 对外匹配,腾讯文档实际还没开源的代码是可以实现暴露更多的键值规则给使用方去匹配),不管后端配置系统读取和前端配置系统读取,解题思路都是一样的。

然后读取条件表达式字符串 "isMacintosh || window.innerWidth > 855",并按照表达式的优先级拆解成几个部分,放入下面的 contextMatchesRules 去匹配预埋的作用域返回布尔值(VSCode 只做到按对应的键值去解析,腾讯文档可以做到对整个 JSON 配置表的键值扫描解析)。

context.set("isMacNative", isMacintosh && !isWeb);
context.set("isEdge", _userAgent.indexOf("Edg/") >= 0);
context.set("isFirefox", _userAgent.indexOf("Firefox") >= 0);
context.set("isChrome", _userAgent.indexOf("Chrome") >= 0);
context.set("isSafari", _userAgent.indexOf("Safari") >= 0);
context.set("isIPad", _userAgent.indexOf("iPad") >= 0);
context.set(window.innerWidth, () => window.innerWidth);

contextMatchesRules(context, ["isMacintosh"]); // true
contextMatchesRules(context, ["isEdge"]); // false
contextMatchesRules(context, ["window.innerWidth", ">=", "800"]); // true

其实 VSCode 只是实现了很简单的表达式解析就支撑起了上万个插件的配置。

因为 VSCode 有完善的文档,足够大的体量去定制规范,对开发者能做到了强约束。

那说明上面这些解析器其实在有约束的情况下,不乱增加规则,正常情况都是够用的,但是能用或者够用不代表好用开源项目和商业化项目对用户侧的约束和规范不可能一样的,腾讯文档基本把整个解析器实现完整了,并完善了 VSCode 的解析器,赋予其更多的配置功能,后续还会继续推动并完善整个解析器,其实目前 VSCode 这方面还不是最完整的。

  • 支持变量
  • 支持常量:布尔值、数字、字符串
  • 支持正则
  • 支持全等intypeof
  • 支持全等=、不等!
  • 支持与&&、或||
  • 支持大于>、小于<、大于等于>=、小于等于<=的比较运算
  • 支持非!等逻辑运算

我们的配置解析器支持上述所有的方法,在文章中 大型前端项目如何实现 UI 可配置化 有详细讲解这部分的原理,这里再具体讲述下思路:

我们使用下面这个再复杂的例子来概括不同的情况:

"when": "canEdit == true || platform == pc && window.innerWidth >= 1080"

我们可以封装一个 deserialize 方法去解析 "when": "canEdit == true || platform == pc && window.innerWidth >= 1080" 这段字符串,里面涉及了 ==,&&,>= 三个表达式的解析,使用 indexOfsplit 进行分词,一般切割成三部分,key、type 和 value,特殊情况 canEdit == true,只要有 key 和 value 即可。

根据优先级,先拆解 || 再拆解 && 这两个表达式

const _deserializeOrExpression: ContextKeyExpression | undefined = (
  serialized: string,
  strict: boolean
) => {
  // 先解 ||
  let pieces = serialized.split("||");
  // 再解 &&
  return ContextKeyOrExpr.create(pieces.map((p) => _deserializeAndExpression(p, strict)));
};

const _deserializeAndExpression: ContextKeyExpression | undefinedn = (
  serialized: string,
  strict: boolean
) => {
  let pieces = serialized.split("&&");
  return ContextKeyAndExpr.create(pieces.map((p) => _deserializeOne(p, strict)));
};

然后再拆解其他表达式,注意代码解析的顺序非常重要,比如有些时候你需要增加 !== 这种表达式的解析,那么一定注意先解析 == 再解析 !== 不然会拆解有误,代码的解析顺序也决定表达式的执行优先级,由于大部分都是字符串比对,所以一般也无需比对类型,特殊情况在使用大于和小于号的时候,如果出现 5 < '6' 也是判断执行成功的。

const _deserializeOne: ContextKeyExpression = (
  serializedOne: string,
  strict: boolean
) => {
  serializedOne = serializedOne.trim();

  if (serializedOne.indexOf("!=") >= 0) {
    let pieces = serializedOne.split("!=");
    return ContextKeyNotEqualsExpr.create(
      pieces[0].trim(),
      this._deserializeValue(pieces[1], strict)
    );
  }

  if (serializedOne.indexOf("==") >= 0) {
    let pieces = serializedOne.split("==");
    return ContextKeyEqualsExpr.create(
      pieces[0].trim(),
      this._deserializeValue(pieces[1], strict)
    );
  }

  if (serializedOne.indexOf("=~") >= 0) {
    let pieces = serializedOne.split("=~");
    return ContextKeyRegexExpr.create(
      pieces[0].trim(),
      this._deserializeRegexValue(pieces[1], strict)
    );
  }

  if (serializedOne.indexOf(" in ") >= 0) {
    let pieces = serializedOne.split(" in ");
    return ContextKeyInExpr.create(pieces[0].trim(), pieces[1].trim());
  }

  if (serializedOne.indexOf(">=") >= 0) {
    const pieces = serializedOne.split(">=");
    return ContextKeyGreaterEqualsExpr.create(pieces[0].trim(), pieces[1].trim());
  }

  if (serializedOne.indexOf(">") >= 0) {
    const pieces = serializedOne.split(">");
    return ContextKeyGreaterExpr.create(pieces[0].trim(), pieces[1].trim());
  }

  if (serializedOne.indexOf("<=") >= 0) {
    const pieces = serializedOne.split("<=");
    return ContextKeySmallerEqualsExpr.create(pieces[0].trim(), pieces[1].trim());
  }

  if (serializedOne.indexOf("<") >= 0) {
    const pieces = serializedOne.split("<");
    return ContextKeySmallerExpr.create(pieces[0].trim(), pieces[1].trim());
  }

  if (/^\!\s*/.test(serializedOne)) {
    return ContextKeyNotExpr.create(serializedOne.substr(1).trim());
  }

  return ContextKeyDefinedExpr.create(serializedOne);
};

最终 when 会被解析为这种树结构,type 是预先定义对表达式的转义,如下表所示:

ContextKey Type ContextKey Type
False 0 Regex 7
True 1 NotRegex 8
Defined 2 Or 9
Not 3 Greater 10
Equals 4 Less 11
NotEquals 5 GreaterOrEquals 12
And 6 LessOrEquals 13

这里留了一个很有意思的 Defined 接口,它不属于任何的表达式语法,但可以后续这样使用:

export class RawContextKey<T> extends ContextKeyDefinedExpr {

	private readonly _defaultValue: T | undefined;

	constructor(key: string, defaultValue: T | undefined) {
		super(key);
		this._defaultValue = defaultValue;
	}

	public toNegated(): ContextKeyExpression {
		return ContextKeyExpr.not(this.key);
	}

	public isEqualTo(value: string): ContextKeyExpression {
		return ContextKeyExpr.equals(this.key, value);
	}

	public notEqualsTo(value: string): ContextKeyExpression {
		return ContextKeyExpr.notEquals(this.key, value);
	}
}

const Extension = new RawContextKey<string>('resourceExtname', undefined);
Extension.isEqualTo("abc");
const ExtensionContext = new Maps();
ExtensionContext.setValue("resourceExtname", "abc");
console.log(contextMatchesRules(ExtensionContext, Extension.isEqualTo("abc")));

在任何地方创建一个 ExtensionContext 作用域,然后建立键值对来使用 isEqualTo 进行等值比对。

条件表达式分词规则再用一张图来表示,以下面这颗树生成的思路为例子,遵循我们常用表达式的一些语法规范和优先级规则,优先切割 || 两边所有的表达式,然后遍历两边的表达式往下去切割 && 表达式,切完所有的 ||&& 再处理子节点的 !===>= 等这些符号。

image

当我们把切割完整个 when 配置项,会把这个树结构结合上面的 ContextKey-Type 映射表,转换出下面的 JS 对象,上面的存储着 ContextKeyOrExprContextKeyAndExprContextKeyEqualsExprContextKeyGreaterOrEqualsExpr 这些重要的规则类,将该 JS 对象存储到 MenuRegistry 里面,后面只需遍历 MenuRegistry 就可以把里面存着的 key 和 value 根据 type 运算规则取出来进行比对并返回布尔值。

when: {
    ContextKeyOrExpr: {
        expr: [{
            ContextKeyDefinedExpr: {
                key: "canEdit",
                type: 2
            }
        }, {
            ContextKeyAndExpr: {
                expr: [{
                    ContextKeyEqualsExpr: {
                        key: "platform",
                        type: 4,
                        value: "pc",
                    },
                    ContextKeyGreaterOrEqualsExpr: {
                        key: "window.innerWidth",
                        type: 12,
                        value: "1080",
                    }
                }],
                type: 6
            }
        }],
        type: 9
    }
}

我们要上面也说了,"window.innerWidth"canEdit"platform" 这些是字符串,不是真正可用于判断的值,这些 key 有些是运行时才会得到值,有些是在某个作用域下才会得到值,我们也需要将这些 key 进行转化,我们借鉴了 Vscode 的做法,在 Vscode 中,它会将这部分逻辑交给一个叫 context 的对象进行处理,它提供两个关键的接口 setValuegetValue 方法,简单的实现如下。

export class Maps {
  protected readonly _values = new Map<string, any>();
  public get values() {
    return this._values;
  }

  public getValue(key: string): any {
    if (this._values.has(key)) {
      let value = this._values.get(key);
      // 执行获取最新的值,并返回
      if (typeof value == "function") {
        value = value();
      }
      return value;
    }
  }

  public removeValue(key: string): boolean {
    if (key in this._values) {
      this._values.delete(key);
      return true;
    }
    return false;
  }

  public setValue(key: string, value: any) {
    this._values.set(key, value);
  }
}

它本质是维护着一份 Map 对象,我们需要把 "window.innerWidth"canEdit"platform" 这些值绑定进去,从而让 key 可以转化对应的变量或者常量,这里注意的是我们 getValue 里面有一段判断是否是函数,如果是函数则执行获取最新的值,这个地方非常关键,因为我们去收集 window.innerWidth 这些的值很可能是实时变化的,我们需要在判断的时候触发这个回调获取真正最新的值,保证条件表达式解析最终结果的正确性,当然如果是 platform 或者 isMacintosh 这些在运行的时候通常不会变,就直接写入即可,不需要每次都触发回调来获取最新的值。

const context = new Context();

context.setValue("platform", "pc");
context.setValue("window.innerWidth", () => window.innerWidth);
context.setValue(
  "canEdit",
  window.SpreadsheetApp.sheetStatus.rangesStatus.status.canEdit
);

当然有些常量或者全局的固定变量,事先预埋就好,比如字符串 "true" 肯定对应就是 true,字符串 "false" 肯定对应就是 false

context.setValue(JSON.stringify(true), true);
context.setValue(JSON.stringify(false), false);

以后如果要交给第三方配置,我们就需要提前在这里规定好 key 值绑定的变量和常量,输出一份配置文档就可以让第三方使用这些关键 key 来进行个性化配置。

image

那么最后只要封装上面例子用到 contextMatchesRules 方法,先读取 json 配置文件为对象,遍历出每一个 when,并关联 context 最终得出一个布尔值,这个布尔值其实来之不易,生成的最终其实是一个带布尔值的策略树,这棵树的前后最终节点的目的都是为了求出布尔值,如果是服务端下发的动态配置本质可以是 0 和 1 的策略树即可。

image

image

要知道实现一个强大的配置系统还能保证整体的质量和性能确实是很不容易的,上图是在我们实际项目其中的一个改造例子,左边的表达式收集会转化成右边表达式配置,左边所有的 if 会收到配置表里面转嫁给接入方或者可视化配置界面,以后每当变动配置表的信息,就可以配合作用域收集得到全信的策略树来渲染视图或者更新视图。

腾讯文档团队

希望有更多志同道合的人加入我们腾讯文档团队,一起跟腾讯文档和开源社区成长,介绍下我们团队部分开源项目成员。

  • AlloyTeam创始人,HTML5梦工场深圳负责人 TAT.Kinvix
  • 《深入理解Vue.js》美女作者 被删
  • Hippy核心开发者 XuQingKuang
  • 公众号@前端控作者 Cooper
  • Hexo Even主题作者 YueXun
  • 工具人一楼 EnoYao

总结

关于这方面的相关文章不多,一路走来跳了不少的坑,感谢团队成员的支持,并让这个方案落地,后续希望能贡献更多代码回馈开源社区,也希望有更多志同道合的人加入我们腾讯文档团队,一起去探索和遨游,最后也希望这篇文章能给到你们一些启发吧 😁

大型前端项目如何实现 UI 可配置化

场景

在我们腾讯文档项目中,我们常用的顶部工具栏会根据编辑权限,屏幕宽度,设备等场景配置对应的显示内容。

image

image

我们的菜单或者底部栏,某些时间段内需要出现引导用户的红点,子菜单栏在某些交互行为下显示不同内容项,切换英文的时候菜单栏内容变更为中文等。

image

上述的这些场景会导致我们的业务代码会高几率出现下面类似的写法,大量的条件判断等,如果后续需要更改条件或者增加条件,都需要在大量的条件判断里面去补充逻辑,并且工具栏的显示效果可能会根据不同业务场景改变外观,比如图标,排序和样式,那么每次第三方接入或者运营想额外配置都需要开发人员在代码里面做修改,这显然是不利于维护和低效的。

const PcToolbarButtonConfig = ['undo', 'redo', 'format', 'clear-format'];
const PcFullToolbarButtonConfig = ['undo', 'redo'];
const PcShortToolbarButtonConfig = ['undo'];
const PcShortToolbarButtonReadonlyConfig = ['undo', 'redo', 'format'];

switch (true) {
    case window.innerWidth < 1440 && window.innerWidth >= 855:
        if (isMore) null;
        return canEdit ? PcToolbarButtonConfig : PcToolbarButtonReadonlyConfig;
    case window.innerWidth < 855:
        if (isMore) null;
        return canEdit ? PcShortToolbarButtonConfig : PcShortToolbarButtonReadonlyConfig;
}
return canEdit ? PcFullToolbarButtonConfig : PcFullToolbarButtonReadonlyConfig;

思考

那么我们应该如何解决呢,从 Vscode 中找到了灵感,Vscode 在开发自定义插件的时候,可以对 Vscode 做自定义配置,从官网中我们看到它使用的是一份 package.json 里面定义了一个 contributes 属性,传入了一个对象,它就会在编辑器的右上角出现一个新的可点击功能图标。

"contributes": {
    "menus": {
        "editor/title": [
            {
                "when": "resourceLangId == markdown",
                "command": "markdown.showPreview",
                "alt": "markdown.showPreviewToSide",
                "group": "navigation"
            }
        ]
    }
}

如下图所示,而这个图标出现的位置也是根据配置项决定的,注意里面有个 when 的条件语句,其实就是当打开的这个文件是 markdown 的时候,条件判断为真,图标就出现了。

image

既然 Vscode 支持利用插件加载配置文件来配置编辑器的显示功能,那么我们其实同样也可以使用这个思路来对我们的工具栏进行配置。

下面我们就详细讲解 Vscode 的插件机制,并利用它的思路实现一个属于我们自己腾讯文档UI可配置化的机制,Vscode 提供了一个 ExtensionsRegistry 的实例,该实例下有两个关键的方法:

  • ExtensionsRegistry.registerExtensionPoint
  • ExtensionsRegistry.getExtensionPoints

首先使用 ExtensionsRegistry.registerExtensionPoint 去注册一个配置项 menus,当注册成功后回用 setHandler 设置一个回调去处理后面将要读到的配置参数,紧接着使用 ExtensionsRegistry.getExtensionPoints 就可以把插件下所有的 package.json 扫描一遍,并配合刚才的 setHandler 回调把所有 contributes 里面的 menu 参数解析出来存到 MenuRegistry 里面。

ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyMenuItem[] }>({
	extensionPoint: 'menus',
	jsonSchema: schema.menusContribution
}).setHandler(extensions => {
    for (const extension of extensions) {
        const { value } = extension;
        for (const command of value) {
            MenuRegistry.addCommands(command);
        }
    }
})

如果你想自定义一个配置项,让 Vscode 成功扫描并解析成功,那么你就可以根据上面的思路,结合下图来配置,先使用 ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'toolbars' }).setHandler(callback) 注册配置项 toolbars 并设置一个回调函数解析参数,然后使用 ExtensionsRegistry.getExtensionPoints 扫描就可以把contributes 里面的 toolbars 参数解析出来存到 ToolbarRegistry 里面,然后使用 ToolbarRegistry 解析好的参数来渲染或者更新 Vscode 的视图。

image

这里会提供 jsonSchema 去校验扫描的参数是否符合规范,如果不规范会有警告提醒并忽略非法的配置参数,所以当开放配置文件给第三方开发者我们也可以很放心的让其自由定制,我们就无需担心配置参数对代码构成严重影响。

export namespace jsonSchema {
    export function isValidCommand(command: IUserFriendlyCommand, collector: ExtensionMessageCollector): boolean {
        if (!command) {
            collector.error(localize('nonempty', "expected non-empty value."));
            return false;
        }
        if (typeof command.command !== 'string') {
            collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command'));
            return false;
        }
}

表达式解析器

当然上面的介绍所有的操作本质其实就是解析一份 JSON 配置文件并提供校验,那么实际还有更多的“隐藏”功能,我们上面说到,我们腾讯文档的顶部工具栏会根据编辑权限,屏幕宽度,设备等场景配置对应的显示内容,在业务代码中会让我们出现大量的条件判断逻辑:

switch (true) {
    case window.innerWidth < 1440 && window.innerWidth >= 855:
        if (isMore) null;
        return canEdit ? PcToolbarButtonConfig : PcToolbarButtonReadonlyConfig;
    case window.innerWidth < 855:
        if (isMore) null;
        return canEdit ? PcShortToolbarButtonConfig : PcShortToolbarButtonReadonlyConfig;
}

我们可以使用上面的插件机制去规避这种问题,比如改写成这样的形式可以更直观:

"contributes": {
    "toolbars": [
        {
            "command": "undo",
            "component": "Redobutton",
            "icon": "undo",
            "when": "canEdit == true && window.innerWidth < 1080 && window.innerWidth >= 855"
        },
        {
            "command": "redo",
            "icon": "redo",
            "when": "platform == pc && window.innerWidth < 855 && isMore == true"
        }
    ]
}

当我们工具栏的 UI 视图需要定制化的时候,我们只需要少量变动我们的配置文件就可以打到目的,第三方开发者也可以根据自己的需求来定制化属于他们自己的工具栏,菜单和底部栏等。

这种方案本质其实是使用 "when": "canEdit == true && window.innerWidth < 1080 && window.innerWidth >= 855" 来代替各种复杂的条件语句,Vscode 的插件机制就使用了这种方案来实现配置文件对 UI 视图的绑定

image

要实现这种方案本质其实就是把 when: xxx 这种配置参数解析为一个布尔值,Vscode 为了实现这个目的,在内部自己实现了一个简单的表达式解析器,目前支持以下表达式:

  • 支持变量
  • 支持常量:布尔值、数字、字符串
  • 支持正则
  • 支持全等(===)、不等(!==)
  • 支持与(&&)、或(||)

Vscode 只实现了上面这些简单的表达式解析就很好的支持了上万个插件的配置,那说明上面这些解析器正常情况是够用的,也是 Vscode 鼓励我们去使用的规范。

image

我们如果自己实现一个复杂点的解析器,可以考虑支持以下表达式。

  • 不支持加法(+)、减法(-)、乘法(*)、除法(/)、取余(%)运算
  • 不支持大于(>)、小于(<)、大于等于(>=)、小于等于(<=)等比较运算
  • 不支持非(!)等逻辑运算
  • 不支持括号()

注意大于和小于均不支持,所有我们刚才 "when": "canEdit == true && window.innerWidth < 1080 && window.innerWidth >= 855" 这类写法我们是不支持,需要自己拓展,在腾讯文档的插件机制里面是支持这部分的。

这里简单说下思路,我们可以封装一个 deserialize 方法去解析 "when": "canEdit == true || platform == pc && window.innerWidth >= 1080" 这段字符串,里面涉及了 ==,&&,>= 三个表达式的解析,使用 indexOfsplit 进行分词,一般切割成三部分,key、type 和 value,特殊情况 canEdit == true,只要有 key 和 value 即可。

private static deserialize(serializedOne: string, strict: boolean): ContextKeyExpression {
	if (serializedOne.indexOf('>=') >= 0) {
		let pieces = serializedOne.split('>=');
		return ContextKeyGreaterOrEqualsExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1], strict));
	}
	if (serializedOne.indexOf('<') >= 0) {
		let pieces = serializedOne.split('<');
		return ContextKeyLessExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1], strict));
	}
	return ContextKeyDefinedExpr.create(serializedOne);
}

最终 when 会被解析为这种树结构,type 是预先定义对表达式的转义,如下表所示:

ContextKey Type ContextKey Type
False 0 Regex 7
True 1 NotRegex 8
Defined 2 Or 9
Not 3 Greater 10
Equals 4 Less 11
NotEquals 5 GreaterOrEquals 12
And 6 LessOrEquals 13

具体的分词规则也很简单,以下面这颗树生成的思路为例子,遵循我们常用表达式的一些语法规范和优先级规则,优先切割 || 两边所有的表达式,然后遍历两边的表达式往下去切割 && 表达式,切完所有的 ||&& 再处理子节点的 !===>= 等这些符号。

image

当我们把切割完整个 when 配置项,会把这个树结构结合上面的 ContextKey-Type 映射表,转换出下面的 JS 对象,上面的存储着 ContextKeyOrExpr,ContextKeyAndExpr,ContextKeyEqualsExpr 和 ContextKeyGreaterOrEqualsExpr 这些重要的规则类,将该 JS 对象存储到 MenuRegistry 里面,后面只需遍历 MenuRegistry 就可以把里面存着的 key 和 value 根据 type 运算规则取出来进行比对并返回布尔值。

when: {
    ContextKeyOrExpr: {
        expr: [{
            ContextKeyDefinedExpr: {
                key: "canEdit",
                type: 2
            }
        }, {
            ContextKeyAndExpr: {
                expr: [{
                    ContextKeyEqualsExpr: {
                        key: "platform",
                        type: 4,
                        value: "pc",
                    },
                    ContextKeyGreaterOrEqualsExpr: {
                        key: "window.innerWidth",
                        type: 12,
                        value: "1080",
                    }
                }],
                type: 6
            }
        }],
        type: 9
    }
}

策略模式

但是我们要注意的是 key 是 "window.innerWidth"canEdit"platform" 这些是字符串,不是真正可用于判断的值,这些 key 有些是运行时才会得到值,有些是在某个作用域下才会得到值,我们也需要将这些 key 进行转化,我们借鉴了 Vscode 的做法,在 Vscode 中,它会将这部分逻辑交给一个叫 context 的对象进行处理,它提供两个关键的接口 setValuegetValue 方法,简单的实现如下。

class Context {
    private readonly _values = new Map<string, any>();
    getValue(key: string): any {
        if (this._values.has(key)) {
            return this._values.get(key);
        }
    }
    setValue(key: string, value: any) {
        this._values.set(key, value);
    }
}

它本质是维护着一份 Map 对象,我们需要把 "window.innerWidth"canEdit"platform" 这些值绑定进去,从而让 key 可以转化对应的变量或者常量,在 Vscode 中的实现会比这里更复杂,它会给每个作用域分配一个 id,当我们去使用 key 去换值的时候,还需要匹配对应的作用域,我们暂时不需要设计那么复杂。

const context = new Context();

context.setValue('platform', 'pc');
context.setValue('window.innerWidth', window.innerWidth);
context.setValue('canEdit', window.SpreadsheetApp.sheetStatus.rangesStatus.status.canEdit);

以后如果要交给第三方配置,我们就需要提前在这里规定好 key 值绑定的变量和常量,输出一份配置文档就可以让第三方使用这些关键 key 来进行个性化配置。

那么最后只要封装一个 contextMatchesRules 方法,先读取已处理成条件表达式树对象的 MenuRegistry,遍历出每一个 when,并关联 context 最终得出一个布尔值,这个布尔值其实来之不易,做了上述那么多的处理估计已经能帮你去掉很多使用 if else,switch,三元表达式,枚举和表驱动等实现的判断逻辑。

const bool:boolean = contextMatchesRules(context, item.when);

for (const commandId of MenuRegistry.getCommands().keys()) {
    let item = MenuRegistry.getCommand(commandId);
    if (contextMatchesRules(item?.when)) {
        const button = document.createElement('button');
        if (item?.command) {
            button.innerHTML = item?.command;
        }
        document.body.appendChild(button);
    }
}

function contextMatchesRules(rules: ContextKeyExpression | undefined): boolean {
    const result = KeybindingResolver.contextMatchesRules(context, rules);
    return result;
}

image

总结

关于这方面的相关文章不多,一路走来跳了不少的坑,感谢团队成员的支持,并让这个方案最终成功落地,也希望有更多志同道合的人加入我们腾讯文档团队,一起去探索和遨游,最后也希望这篇文章能给到你们一些启发吧😁

30 个极大提高开发效率的超级实用 VSCode 插件

Visual Studio Code 的插件对于在提升编程效率和加快工作速度非常重要。这里有 30 个最受欢迎的 VSCode 插件,它们将使你成为更高效的搬砖摸鱼大师。这些插件主要适用于前端开发人员,但也有一些通用插件也可以适用于任何开发环境。以下是我将介绍的 VSCode 插件:

  1. Settings Sync
  2. Live Server
  3. Remote SSH
  4. Prettier
  5. Bracket Pair Colorizer
  6. Auto Rename Tag
  7. GitLens
  8. Git History
  9. CSS Peek
  10. Javascript Code Snippets
  11. Peacock
  12. Sass/Less/Stylus/Pug/Jade/Typescript/Javascript Compile Hero Pro
  13. Code Spell Checker
  14. Chrome 调试器
  15. Material Icon Theme
  16. Turbo Console Log
  17. TODO highlight
  18. VSCode Icons
  19. Chrmarti Regex
  20. Bookmarks
  21. Tabnine
  22. Import Cost
  23. Quokka
  24. ESLint
  25. Visual Studio IntelliCode
  26. SQLTools — Database tools
  27. Better Comments
  28. Vue 3 Support - All In One
  29. HTML/CSS/JavaScript Snippets
  30. Search/Translate Hero - Google/Translate/Bing/Baidu/Wiki/Yahoo/Github/Npm Engine

Settings Sync

Settings Sync 可为你节省大量跨设备安装插件的时间

在开始左右安装插件之前,最好知道Settings Sync的存在。它允许你将在 VSCode 上自定义的几乎所有内容同步到 Github,从设置到键盘快捷键到其他 VSCode 插件。

这样,你就可以从任何你想要的设备访问你喜欢的 IDE,而不必在新设备上从普通
VSCode 环境中进行编程,也不必再次手动设置所有内容。

Live Server

立即查看浏览器中反映的代码更改

这是我最喜欢的插件之一。Live Server启动本地开发服务器,并为静态和动态页面提供实时重新加载功能。

每次保存代码时,你都会立即看到浏览器中反映的更改。你会更快地发现错误,并且可以更轻松地对你的代码进行一些快速实验。

Tabnine

Tabnine 是一款广受欢迎的 VSCode 人工智能助手,适用于所有主要编程语言,因此毫无疑问,无论你的技能如何,你都会发现它很有用。

通过研究公开共享的代码库,Tabnine 使用深度学习算法来预测你的需求,并提供一键代码完成功能,让你可以快速高效地完成项目。 对于新手编码员来说,这让学习变得轻而易举,因为它为你提供了发挥想法的空间,而无需记住编码语法或搜索 StackOverflow。

而且,如果你是一位经验丰富的开发人员,那么你会发现 Tabnine 为你提供了运行所需的构建块,从而为你的工作节省了大量时间。

Remote SSH

使用任何带有 SSH 服务器的远程机器,该SSH插件可以让你使用任何远程计算机与 SSH 服务器作为开发环境。这使得在各种场景中开发和或故障排除变得更加容易。

你也不需要本地机器上的任何源代码,因为插件直接在远程机器上运行命令和其他插件。

Prettier

花更少的时间格式化代码

Prettier是一个自以为是的代码格式化程序,如果你有多人在一个项目上工作,它会特别有效,因为该插件强制执行一致的样式。

你可以对其进行设置,以便在每次保存代码时格式化你的代码,从而显着减少你花在格式化代码上的时间。

Bracket Pair Colorizer

每个人都喜欢对代码着色,Bracket Pair Colorizer提供了匹配颜色的左括号和右括号,从而更容易知道哪些括号属于谁。

还可以配置自定义括号字符,你也可以为活动范围添加背景颜色。

Auto Rename Tag

自动重命名标签,虽然 VSCode 固有地突出显示匹配的标签并在你键入开始标签时立即添加结束标签,但自动重命名标签插件会自动重命名你更改的标签。

该插件适用于 HTML、XML、PHP 和 JavaScript,无需更改标签名称两次。

GitLens

增强你的 Git 能力GitLens 增强了Visual Studio Code 的 Git 功能。这是一个强大的插件,可让你查看代码行随时间变化的人、原因和方式以及许多其他功能。

GitLens 是一个高度可定制的插件。如果你不喜欢某个特定设置,你可以在设置中轻松将其关闭。

Git History

获得 git 日志,并显示漂亮的视觉效果

与 GitLens 类似,Git History是一个 VSCode 插件,它提供了 git 日志的可视化。你不需要再终端中查看 git log。

插件也非常全面。它允许你跨提交比较分支、提交和文件。也可以查 Github 头像,挺整洁的。

Vue 3 Support - All In One

这是一款在 Vue 2 或者 Vue 3 开发中提供代码片段,语法高亮和格式化的 VSCode 插件,能极大提高你的开发效率。你可以在 VSCode 编辑器底部栏右下角打开 Auto Format Vue 开关,它可能帮你在代码保存的时候自动格式化 vue 文件的格式,默认是关闭状态。

如果你不想自动格式化 vue 文件,你也可以在 vue 文件中点击鼠标右键,在出现的菜单栏中选择 Format Document 菜单项,则文件会执行一次格式化。

CSS Peek

插件你的 HTML 文件以查看你的 CSS 代码

这个插件对于前端开发人员来说是无价的。受 IDE Brackets 中类似功能的启发,CSS Peek允许你插件 HTML 和 ejs 文件以在源代码中显示 CSS/SCSS/LESS 代码。

如果你知道类或 ID 名称,它还允许你快速跳转到对应 CSS 代码的位置。

Javascript Code Snippets

提供很多 JS 代码块提示,虽然 VSCode 包括内置的 JS IntelliSense,但JS 代码片段插件通过添加大量导入、导出触发器、类助手和方法触发器来增强这种体验。

该插件支持 JS、TypeScript、JS React、TS React、HTML 和 Vue。在 VSCode Marketplace 中,也可以轻松获得其他风格(例如 Angular)的代码片段。

HTML/CSS/JavaScript Snippets

只需输入前缀名称,就会自动完成完整的代码片段。

Search/Translate Hero - Google/Translate/Bing/Baidu/Wiki/Yahoo/Github/Npm Engine

一款让您在代码中进行搜索或者翻译的 VSCode 插件。你可以在编辑器中,选中代码中对应的关键词,然后点击鼠标右键,在出现的菜单面板中选择 Search Online 菜单项,插件会自动帮你打开默认浏览器,并搜索对应的关键词和显示搜索结果。

你还可以选中对应的关键词后,使用快捷键去打开浏览器进行搜索。

Peacock

更改 VSCode 实例的颜色,非常实用。Peacock允许你更改 Visual Studio Code 环境的颜色,因此你可以快速识别刚刚切换到的实例。

Colorize

查看你在风格指南中使用的颜色,使用Colorize立即将 CSS/SASS/Less/... 文件中的 CSS 颜色可视化。这使得一目了然地看到你在何处使用了哪些颜色变得非常容易。

Code Spell Checker

让你代码不再有拼写错误,虽然拼写错误不是致命问题,但我更喜欢我的代码没有拼写错误。代码拼写检查器插件在其字典文件中无法识别的单词下划线。

该插件有许多不同的语言版本,并支持医学术语等行话。

Debugger For Chrome

在 VSCode 中调试你的 JS 代码,由 Microsoft 开发的Debugger for Chrome允许你在 VSCode 中调试你的 JS 代码。与其他 IDE 中的调试器相反,它非常流畅。

你可以设置断点、逐步执行代码、调试动态添加的脚本等等。

Icon Fonts

提供各种图标供你使用!Icon Fonts提供了各种图标字体的片段,包括流行的 Font Awesome v5 图标包。

对于那些不使用 VSCode 的人,这个包也可用于 Atom 和 Sublime Text。

Turbo Console Log

自动创建有意义的日志消息,该控制台显示日志\插件自动创建一个有意义的日志信息的过程。它使调试更容易,因为你将有一些有用的控制台输出来找出问题所在。

Todo Highlight

立即发现代码中的 TODO,很多程序员习惯在代码中写 TODO,然后完全忘记它们。Todo Highlight使它们更加突出。

你可以切换突出显示,也可以列出所有突出显示的注释并从相应的文件中显示它们。

VSCode Icons

等等,不是每个人都喜欢图标吗?你不会认为图标有很大的不同,但它们确实有至少对我来说。VSCode Icons为你的 IDE 增添了一抹色彩和可爱的小图标,我已经爱上了它。

Regex Previewer

创建正则表达式的预览,正则表达式可能是一个很困难的难题。Regex Previewer为你提供与你的正则表达式匹配的辅助文档。

该插件提供了多个示例进行匹配,因此为各种用例快速准确地编写正则表达式变得更加容易。

Bookmarks

为你的代码添加书签,尽管 VSCode 有行号,但Bookmarks允许你在代码中添加书签,帮助你快速导航并轻松来回跳转。

Import Cost


当你开发 Web 应用程序或网站时,很容易制作一些臃肿的东西——尤其是作为一个新手开发者。 这其中的一个重要因素是,许多开发人员在通过代码导入时并不总是知道包有多大。

Import Cost 是一个 VSCode 扩展,可以内联显示导入包的大小,因此你可以确切地知道在开发过程中导入该包的成本是多少。 因此,它将帮助你更好地优化你的应用程序和网站,特别是对于通常因膨胀而遭受更多痛苦的移动用户。

Quokka


如果你选择的语言是 JavaScript 或 TypeScript,那么你一定会喜欢 Quokka.js。 此扩展旨在通过在编写代码时在 IDE 中显示运行时值来加快开发速度,因此你可以专注于编写代码,而不是仅仅为了尝试新事物而构建自定义配置。

这是一个简单、轻量级的扩展,非常适合经验丰富的开发人员和新手。 它也可以免费供社区使用,但如果你是 JavaScript/TypeScript 专家,你还可以购买 Pro 许可证,让你无需更改代码即可修改运行时值。

ESLint

如果你需要格式化程序和规范代码,那么这个插件适合你。
它可以自动格式化你的代码并查找代码中的错误。

此外,它允许你在书签代码之间选择代码区域,这对于日志文件分析等非常有用。

Visual Studio IntelliCode

它旨在帮助开发人员和程序员提供智能代码完成建议。
它默认支持 Python、TypeScript/JavaScript、React 和 Java。

SQLTools — Database tools

通过 VSCode 管理数据库的工具。
它支持许多驱动程序,你可以使用它来做很多事情,例如连接资源管理器、查询运行程序、智能感知、书签、查询历史记录。

Better Comments

Better Comments 扩展将帮助你在代码中创建更人性化的注释。
每种颜色都可以作为表示评论类型(注意、待办事项等)的一种方式。

最后

这是我个人介绍的 30 个 VSCode 插件,可在不影响质量的情况下提高你的编程效率。,如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,文章同步持续更新,往期文章也收录在 https://github.com/Wscats/CV
欢迎您的关注和交流,你的肯定是我前进的最大动力 😁

day4

冒泡排序

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>12冒泡排序</title>
	<script>
		var arr = [20,133,65,68,24,15,66,58,100,10];

		for(var i=0;i<arr.length-1;i++){
			console.log(i,'-------------------');
			for(var j=0;j<arr.length-i-1;j++){

				if(arr[j] > arr[j+1]){
					var temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
				console.log(arr.join());
			}
		}

		console.log(arr);


		// 用数组sort方法实现排序
		var arr = [20,133,65,68,24,15,66,58,100,10];
		arr.sort(function(a,b){
			// console.log(a,b);
			
			/*if(a>b){
				return 1;
			}else if(a < b){
				return -1;
			}else{
				return 0;
			}*/

			return a-b;
		});

		console.log(arr);
		console.log(arr.reverse());
	</script>
</head>
<body>
	
</body>
</html>

Vue,React,Angular

React Vue Angular

- Angular Vue React
维护团队 Google 国人 Even You Facebook
类型 MVP MVC MVVM MVVM MVVM
初始化 双向数据绑定 双向数据绑定 单项数据绑定
实现方案 利用脏值检测实现双向数据绑定 数据劫持 利用ES5的Object.defineProperty的get和set属性值来实现双向数据绑定 2.0版本增加虚拟DOM 虚拟DOM
性能 最差 功能最多 最轻 内置功能较少 比较轻 性能最少
ajax $http vue-resource/jQuery jQuery
自定义服务 app.service()构造器/app.factory工厂函数/app.constant()/app.value()
服务 有服务 需要依赖注入 没有服务 没有服务
自定义过滤器 app.directive()来实现 Vue.directive()来实现
内置过滤器 currency,uppercase,lowercase,json,number,filter,orderBy,limitTo... filterBy...
自定义指令 有指令 ng-xxx 使用app.directive()去定义 有指令 v-xxx 使用Vue.directive()去定义
内置指令 ng-app,ng-controller,ng-model,ng-bind,ng-style,ng-class,ng-bind-html,ng-if,ng-show... v-model,@click,:src 没有
jQ工具包 jQlite 引入jQ 引入jQ
绑定数据 {{xxx}}/ng-bind {{xxx}}/v-text {xxx}
绑定函数 通过$scope和ng-click等指令完成 通过methods和@click等指令完成 通过定义函数和onClick={}等方法完成
语法 JS/ES6 JS/ES6 JSX

初始化

angular

var app = angular.module("app",[]);
		app.controller("indexCtrl",function($scope){
			$scope.text = "Hello World"
		})

vue

new Vue({
			el:"#demo",
			data:{
				//code
				text:"Hello World"
			},
			template:"<p>{{text}}</p>"
		})

react

var text = "Hello WorldD";
		ReactDOM.render(
			<p>{text}</p>
		,document.getElementById("demo"))

MVVM MVC MVP

  1. mvp的全称为Model-View-Presenter,Model提供数据,View负责显示,Controller/Presenter负责逻辑的处理。MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller
  2. MVVM是Model-View-ViewModel的简写。微软的WPF带来了新的技术体验,如Silverlight、音频、视频、3D、动画……,这导致了软件UI层更加细节化、可定制化。同时,在技术层面,WPF也带来了 诸如Binding、Dependency Property、Routed Events、Command、DataTemplate、ControlTemplate等新特性。MVVM(Model-View-ViewModel)框架的由来便是MVP(Model-View-Presenter)模式与WPF结合的应用方式时发展演变过来的一种新型架构框架。它立足于原有MVP框架并且把WPF的新特性糅合进去,以应对客户日益复杂的需求变化
  3. MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中

虚拟DOM

其实就是类似jQ的字符串拼接,然后再把拼接好的完整的html结构插入到对应的节点,我们就不会再对html结构实现增删查改,以后要重新操作DOM,只要重新修改新的html结构再插入即可

Ajax

angular
注意使用之前一定要注入$http服务,只有angular才有服务的概念,服务其实就是封装好一大堆可服用的方法,jQ来扩展react和vue的功能

$http({
				url:"test.json",
				methods:"GET",
				params:{
					name:"test"
				}
			}).then(function(data){
				console.log(data)
			})

vue
实现ajax,要不写原生ajax,要不引入jQ,vue-resource,this.$http().then

getData:function(){
					$.ajax({
						type:"get",
						url:"test.json",
						async:true,
						data:{
							skill:"PS"
						},
						success:function(data){
							console.log(data)
						}.bind(this)
					});
				}

react
需要引入jQ,也可以用原生,也可以用第三方库

var getData = function(){
			console.log("H")
			$.ajax({
				url:"test.json",
				type:"GET",
				success:function(data){
					console.log(data)
				}
			})
		}

绑定数据

angular
双向数据绑定,$scope的属性值绑定对应的值,然后通过ng-bind或者{{}}实现渲染

//HTML VIEW
<p>{{text}}</p>
//JS MODEL
$scope.text = "xxxx"

vue
双向数据绑定,通过在data里面定义属性值,然后通过v-text或者{{}}实现渲染

//HTML VIEW
<p>{{text}}</p>
//MODEL
data:{
	text:"Hello World"
},

react
单向数据绑定,通过定义一个变量,然后使用{}进行绑定

//JS MODEL
var text = "Hello WorldD";
//HTML VIEW
<p>{text}</p>

绑定函数

angular
通过在$scope里面定义一个函数,然后通过指令(ng-click)绑定到对应的标签上

//HTML
<button ng-click="getData">GET</button>
//MODEL
$scope.getData = function() {}

vue
通过在methods里面定义方法,然后通过指令(@click)绑定到对应的标签上
@click === v-on:click

<button @Click="getData">Get</button>
methods:{
	getData:function(){}
}

react
定义函数,配合{}来绑定,jsx=>{}左右是没有双引号

//定义一个函数
var getData = function(){}
//
<button onClick={getData}>Ok</button>
<button onClick={function(){}}>Ok</button>

自定义指令

其实就是把相同类型的DOM操作封装在一起,然后实现复用
angular

<ng-color>组件</ng-color>
<div ng-color="">指令</div>
//使用
<p ng-color="blue">{{text}}</p>
//定义指令
app.directive("ngColor",function(){
	return {
			link:function(scope,ele,attr){
			ele.css("color",attr.ngColor)
		}
	}
})

vue
全局和局部的directive有单数复数的区分

Vue.directive()//全局定义
				directives:{//局部定义
					color:{
						bind:function(){
							this.vm//scope
							this.el.style.color = "red"//ele
						}
					}
				}

react
没有指令,要自己去实现DOM的复用就要封装类指令来实现

内置指令(遍历数组)

angular
ng-show ng-if

<ul>
	<li ng-repeat="a in arr">{{a}}</li>
</ul>

vue

<ul>
	<li v-for="a in arr">{{a}}</li>
</ul>

react

{arr.map(function(item){
	return <li>{item}</li>
})}

过滤器

angular
过滤器其实是一种特殊的服务,封装一个处理相同类型数据的方法,使用的时候用管道字符+过滤器名字

//HTML
<div ng-bind-html="html|html">
//JS
app.filter("html",function($sce){
			return function(input){
				console.log(input)
				return $sce.trustAsHtml(input)
			}
		})

vue

<p>{{text|ed}}</p>
Vue.filter("过滤器的名字",function(){})
			filters:{//局部定义
				ed:function(input){
					return input+"ed";
				}
			}

react
实现一个类过滤器的方法

var ed = function(input){
	return input+"ed"
}
<p>{ed(text)}</p>

55 个提高你 CSS 开发效率的必备片段

这篇文章会记录我们平时常用到的 CSS 片段,使用这些 CSS 可以帮助我们解决许多实际项目问题中遇到的,墙裂建议点赞收藏再看,方便日后查找😁

清除浮动

浮动给我们的代码带来的麻烦,想必不需要多说,我们会用很多方式来避免这种麻烦,其中我觉得最方便也是兼容性最好的一种是,在同级目录下再创建一个<div style="clear:both;"></div>;不过这样会增加很多无用的代码。此时我们用:after这个伪元素来解决浮动的问题,如果当前层级有浮动元素,那么在其父级添加上 clearfix 类即可。

// 清除浮动
.clearfix:after {
  content: "\00A0";
  display: block;
  visibility: hidden;
  width: 0;
  height: 0;
  clear: both;
  font-size: 0;
  line-height: 0;
  overflow: hidden;
}
.clearfix {
  zoom: 1;
}

垂直水平居中

在 css 的世界里水平居中比垂直居中来的简单一些,经过了多年的演化,依然没有好的方式来让元素垂直居中(各种方式各有优缺点,但都不能达到兼容性好,破坏力小的目标),以下是几种常见的实现方式

绝对定位方式且已知宽高

position: absolute;
top: 50%;
left: 50%;
margin-top: -3em;
margin-left: -7em;
width: 14em;
height: 6em;

绝对定位 + 未知宽高 + translate

position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
//需要补充浏览器前缀

flex 轻松搞定水平垂直居中(未知宽高)

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

文本末尾添加省略号

当文本的内容超出容器的宽度的时候,我们希望在其默认添加省略号以达到提示用户内容省略显示的效果。

宽度固定,适合单行显示...

overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;

宽度不固定,适合多行以及移动端显示

overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;

制造文本的模糊效果

当我们希望给文本制造一种模糊效果感觉的时候,可以这样做

color: transparent;
text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);

动画实现简洁 loading 效果

我们来实现一个非常简洁的 loading 效果

.loading:after {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  content: "\2026";
  -webkit-animation: ellipsis 2s infinite;
}

// 动画部分
@-webkit-keyframes ellipsis {
  from {
    width: 2px;
  }
  to {
    width: 15px;
  }
}

自定义文本选中样式

默认情况下,我们在网页上选中文字的时候,会给选中的部分一个深蓝色背景颜色(可以自己拿起鼠标试试),如果我们想自己定制被选中的部分的样式呢?

// 注意只能修改这两个属性 字体颜色 选中背景颜色

element::selection {
  color: green;
  background-color: pink;
}
element::-moz-selection {
  color: green;
  background-color: pink;
}

顶角贴纸效果

有时候我们会有这样的需求,在一个列表展示页面,有一些列表项是新添加的、或者热度比较高的,就会要求在其上面添加一个贴纸效果的小条就像 hexo 默认博客的 fork me on github 那个效果一样。

接下来我们开始一步步完成最左边的这个效果

html

<div class="wrap">
  <div class="ribbon">
    <a href="#">Fork me on GitHub</a>
  </div>
</div>

css

/* 外层容器几本设置  */
.wrap {
  width: 160px;
  height: 160px;
  overflow: hidden;
  position: relative;
  background-color: #f3f3f3;
}

.ribbon {
  background-color: #a00;
  overflow: hidden;
  white-space: nowrap;
  position: absolute;
  /* shadom */
  -webkit-box-shadow: 0 0 10px #888;
  -moz-box-shadow: 0 0 10px #888;
  box-shadow: 0 0 10px #888;
  /* rotate */
  -webkit-transform: rotate(-45deg);
  -moz-transform: rotate(-45deg);
  -ms-transform: rotate(-45deg);
  -o-transform: rotate(-45deg);
  transform: rotate(-45deg);
  /* position */
  left: -50px;
  top: 40px;
}

.ribbon a {
  border: 1px solid #faa;
  color: #fff;
  display: block;
  font: bold 81.25% "Helvetica Neue", Helvetica, Arial, sans-serif;
  margin: 1px 0;
  padding: 10px 50px;
  text-align: center;
  text-decoration: none;
  /* shadow */
  text-shadow: 0 0 5px #444;
}

input 占位符

当我们给部分 input 类型的设置 placeholder 属性的时候,有的时候需要修改其默认的样式。

input::-webkit-input-placeholder {
  color: green;
  background-color: #f9f7f7;
  font-size: 14px;
}
input::-moz-input-placeholder {
  color: green;
  background-color: #f9f7f7;
  font-size: 14px;
}
input::-ms-input-placeholder {
  color: green;
  background-color: #f9f7f7;
  font-size: 14px;
}

移动端可点击元素去处默认边框

在移动端浏览器上,当你点击一个链接或者通过 Javascript 定义的可点击元素的时候,会出现蓝色边框,说实话,这是很恶心的,怎么去掉呢?

-webkit-tap-highlight-color: rgba(255, 255, 255, 0);

首字下沉

要实现类似 word 中首字下沉的效果可以使用以下代码

element:first-letter {
  float: left;
  color: green;
  font-size: 30px;
}

小三角

在很多地方我们可以用得上小三角,接下来我们画一下四个方向的三角形

.triangle {
  /* 基础样式 */
  border: solid 10px transparent;
}
/*下*/
.triangle.bottom {
  border-top-color: green;
}
/*上*/
.triangle.top {
  border-bottom-color: green;
}
/*左*/
.triangle.top {
  border-right-color: green;
}
/*右*/
.triangle.top {
  border-left-color: green;
}

可以看出画一个小三角非常简单,只要两行样式就可以搞定,对于方向只要想着画哪个方向则设置反方向的样式属性就可以

鼠标手型

一般情况下,我们希望在以下元素身上添加鼠标手型

  • a
  • submit
  • input[type="iamge"]
  • input[type="button"]
  • button
  • label
  • select
a[href],
input[type="submit"],
input[type="image"],
input[type="button"],
label[for],
select,
button {
  cursor: pointer;
}

屏蔽 Webkit 移动浏览器中元素高亮效果

在访问移动网站时,你会发现,在选中的元素周围会出现一些灰色的框框,使用以下代码屏蔽这种样式

-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;

移除常用标签的浏览器默认的 margin 和 padding

pre、code、legend、fieldset、blockquote … 等标签不是很常用,所以就不一一列举,如果项目中使用到,可以自己单独写

body,
p,
h1,
h2,
h3,
h4,
h5,
h6,
dl,
dd,
ul,
ol,
th,
td,
button,
figure,
input,
textarea,
form {
  margin: 0;
  padding: 0;
}

统一 input、select、textarea 宽度

不同浏览器的 input、select、textarea 的盒子模型宽度计算方式不同,统一为最常见的 content-box

input,
select,
textarea {
  -webkit-box-sizing: content-box;
  -moz-box-sizing: content-box;
  box-sizing: content-box;
}

table {
  /*table 相邻单元格的边框间的距离设置为 0*/
  border-spacing: 0;
  /*默认情况下给 tr 设置 border 没有效果,如果 table 设置了边框为合并模式:「border-collapse: collapse;」就可以了*/
  border-collapse: collapse;
}

移除浏览器部分元素的默认边框

acronym、fieldset … 等其他标签不是很常用,就不会一一列举;如果项目中用到,可以自己单独写

img,
input,
button,
textarea {
  border: none;
  -webkit-appearance: none;
}

input {
  /*由于 input 默认不继承父元素的居中样式,所以设置:「text-align: inherit」*/
  text-align: inherit;
}

textarea {
  /*textarea 默认不可以放缩*/
  resize: none;
}

取消元素 outline 样式

由于以下元素的部分属性没有继承父节点样式,所以声明这些元素的这些属性为父元素的属性

a,
h1,
h2,
h3,
h4,
h5,
h6,
input,
select,
button,
option,
textarea,
optgroup {
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  font-style: inherit;
  line-height: inherit;
  color: inherit;
  outline: none;
}

取消超链接元素的默认文字装饰

另外 del、ins 标签的中划线、下划线还是挺好的,就不去掉

a {
  text-decoration: none;
}

ol,
ul {
  /*开发中 UI 设计的列表都是和原生的样式差太多,所以直接给取消 ol,ul 默认列表样式*/
  list-style: none;
}

button,
input[type="submit"],
input[type="button"] {
  /*鼠标经过是「小手」形状表示可点击*/
  cursor: pointer;
}

input::-moz-focus-inner {
  /*取消火狐浏览器部分版本 input 聚焦时默认的「padding、border」*/
  padding: 0;
  border: 0;
}

取消部分浏览器数字输入控件的操作按钮

input[type="number"] {
  -moz-appearance: textfield;
}

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  margin: 0;
  -webkit-appearance: none;
}

输入控件 placeholder 色设置 #999

input::-webkit-input-placeholder,
textarea::-webkit-input-placeholder {
  color: #999;
}

input:-moz-placeholder,
textarea:-moz-placeholder {
  color: #999;
}

input::-moz-placeholder,
textarea::-moz-placeholder {
  color: #999;
}

input:-ms-input-placeholder,
textarea:-ms-input-placeholder {
  color: #999;
}

template {
  /*由于部分浏览 template 会显示出来,所以要隐*/
  display: none;
}

position: fixed 的缩写

.pf {
  position: fixed;
  /*chrome 内核 浏览器 position: fixed 防止抖动*/
  -webkit-transform: translateZ(0);
}

利用绝对定位宽高拉升原理,中心居中元素

.middle {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
}

利用相对定位于 CSS3 使元素垂直居中

.v-middle {
  position: relative;
  top: 50%;
  -webkit-transform: -webkit-translateY(-50%);
  -moz-transform: -moz-translateY(-50%);
  -o-transform: -o-translateY(-50%);
  transform: translateY(-50%);
}

元素计算宽高的盒子模型以 border 为外界限「bb ==> border-box」

.bb {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

单行文本溢出显示省略号「to ==> text-overflow」

.to {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

初始化样式

不同的浏览器对各个标签默认的样式是不一样的,而且有时候我们也不想使用浏览器给出的默认样式,我们就可以用 reset.css 去掉其默认样式

body,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
p,
blockquote,
dl,
dt,
dd,
ul,
ol,
li,
pre,
form,
fieldset,
legend,
button,
input,
textarea,
th,
td {
  margin: 0;
  padding: 0;
}
body,
button,
input,
select,
textarea {
  font: 12px/1.5 tahoma, arial, \5b8b\4f53;
}
h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: 100%;
}
address,
cite,
dfn,
em,
var {
  font-style: normal;
}
code,
kbd,
pre,
samp {
  font-family: couriernew, courier, monospace;
}
small {
  font-size: 12px;
}
ul,
ol {
  list-style: none;
}
a {
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}
sup {
  vertical-align: text-top;
}
sub {
  vertical-align: text-bottom;
}
legend {
  color: #000;
}
fieldset,
img {
  border: 0;
}
button,
input,
select,
textarea {
  font-size: 100%;
}
table {
  border-collapse: collapse;
  border-spacing: 0;
}

强制换行/自动换行/强制不换行

/* 强制不换行 */
div {
  white-space: nowrap;
}

/* 自动换行 */
div {
  word-wrap: break-word;
  word-break: normal;
}

/* 强制英文单词断行 */
div {
  word-break: break-all;
}

table 边界的样式

table {
  border: 1px solid #000;
  padding: 0;
  border-collapse: collapse;
  table-layout: fixed;
  margin-top: 10px;
}
table td {
  height: 30px;
  border: 1px solid #000;
  background: #fff;
  font-size: 15px;
  padding: 3px 3px 3px 8px;
  color: #000;
  width: 160px;
}

绝对定位与 margin

当我们提前知道要居中元素的长度和宽度时,可以使用这种方式:

.container {
  position: relative;
  width: 300px;
  height: 200px;
  border: 1px solid #333333;
}
.content {
  background-color: #ccc;
  width: 160px;
  height: 100px;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-left: -80px; /* 宽度的一半 */
  margin-top: -50px; /* 高度的一半 */
}

绝对定位与 transform

当要居中的元素不定宽和定高时,我们可以使用 transform 来让元素进行偏移。

.container {
  position: relative;
  width: 300px;
  height: 200px;
  border: 1px solid #333333;
}
.content {
  background-color: #ccc;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate3d(-50%, -50%, 0);
  text-align: center;
}

line-height

line-height其实是行高,我们可以用行高来调整布局!

不过这个方案有一个比较大的缺点是:文案必须是单行的,多行的话,设置的行高就会有问题。

.container {
  width: 300px;
  height: 200px;
  border: 1px solid #333333;
}
.content {
  line-height: 200px;
}

table 布局

给容器元素设置display: table,当前元素设置display: table-cell

.container {
  width: 300px;
  height: 200px;
  border: 1px solid #333333;
  display: table;
}
.content {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
}

flex 布局

我们可以给父级元素设置为display: flex,利用 flex 中的align-itemsjustify-content设置垂直方向和水平方向的居中。这种方式也不限制中间元素的宽度和高度。

同时,flex 布局也能替换line-height方案在某些 Android 机型中文字不居中的问题。

.container {
  width: 300px;
  height: 200px;
  border: 1px solid #333333;
  display: flex;
  align-items: center;
  justify-content: center;
}
.content {
  background-color: #ccc;
  text-align: center;
}

图片上下左右居中

一种常用的方式是把外层的 div 设置为 table-cell;然后让内部的元素上下左右居中。当然也还有一种方式,就是把 img 当做 div,参考 6 中的代码进行设置。
CSS 代码如下:

.content {
  width: 400px;
  height: 400px;
  border: 1px solid #ccc;
  text-align: center;
  display: table-cell;
  vertical-align: middle;
}

html 代码如下:

<div class="content">
  <img src="./4.jpg" alt="img" />
</div>

标题两边的小横杠

我们经常会遇到这样的 UI 需求,就是标题两边有两个小横岗,之前是怎么实现的呢?比如用个border-top属性,然后再把中间的文字进行绝对定位,同时给这个文字一个背景颜色,把中间的这部分盖住。

现在我们可以使用伪元素来实现!

<div class="title">标题</div>
title {
  color: #e1767c;
  font-size: 0.3rem;
  position: relative;

  &:before,
  &:after {
    content: "";
    position: absolute;
    display: block;
    left: 50%;
    top: 50%;
    -webkit-transform: translate3d(-50%, -50%, 0);
    transform: translate3d(-50%, -50%, 0);
    border-top: 0.02rem solid #e1767c;
    width: 0.4rem;
  }
  &:before {
    margin-left: -1.2rem;
  }
  &:after {
    margin-left: 1.2rem;
  }
}

用 border 属性绘制元素

border 除了作为简单的绘制边框以外,还可以绘制三角形,梯形,星形等任意的多边形,以下为绘制的两个三角形和梯形

<div class="triangle1"></div>
<div class="triangle2"></div>
<div class="trapezoid"></div>
.triangle1 {
  /*锐角三角形*/
  width: 0;
  height: 0;
  border-top: 50px solid transparent;
  border-bottom: 100px solid #249ff1;
  border-left: 30px solid transparent;
  border-right: 100px solid transparent;
}
.triangle2 {
  /*直角三角形*/
  width: 0;
  height: 0;
  border-top: 80px solid transparent;
  border-bottom: 80px solid #ff5b01;
  border-left: 50px solid #ff5b01;
  border-right: 50px solid transparent;
}
.trapezoid {
  /*梯形*/
  width: 0;
  height: 0;
  border-top: none;
  border-right: 80px solid transparent;
  border-bottom: 60px solid #13dbed;
  border-left: 80px solid #13dbed;
}

用 border-radius 绘制元素

border-radius主要用于绘制圆点、圆形、椭圆、圆角矩形等形状,以下为简单绘制的两个图形。

<div class="circle"></div>
<div class="ellipse"><div></div></div>
.circle,
.ellipse {
  width: 100px;
  height: 100px;
  background: #249ff1;
  border-radius: 50%;
}
.ellipse {
  width: 150px;
  background: #ff9e01;
}

border-radius属性实际上可以设置最多 8 个值,通过改变 8 个值可以得到许多意想不到的图像

用 box-shadow 绘制元素

对于box-shadow,其完整的声明为box-shadow: h-shadow v-shadow blur spread color inset,各个值表示的意义分别为:s 水平方向的偏移,垂直方向的便宜,模糊的距离(羽化值),阴影的大小(不设置或为 0 时阴影与主体的大小一致),阴影的颜色和是否使用内阴影。实际应用时可以接收 3-6 个值,对应分别如下:

  • 3 个值: h-shadow v-shadow color
  • 4 个值: h-shadow v-shadow blur color
  • 5 个值: h-shadow v-shadow blur spread color
  • 6 个值: h-shadow v-shadow blur spread color inset

同时,border-shadow接受由多个以上各种值组成的以逗号分隔的值,通过这一特性,我们可以实现如多重边框的等效果。以下我们用该属性来实现一个单标签且不借助伪元素的添加图标代表目标的的图标

<div class="plus"></div>
<div class="target"></div>
.plus {
  width: 30px;
  height: 30px;
  margin-left: 50px; /*由于box-shadow不占空间,常常需要添加margin来校正位置*/
  background: #000;
  box-shadow: 0 -30px 0 red, 0 30px 0 red, -30px 0 0 red, 30px 0 0 red;
}
.target {
  width: 30px;
  height: 30px;
  background: red;
  border-radius: 50%;
  margin-left: 50px;
  box-shadow: 0 0 0 10px #fff, 0 0 0 20px red, 0 0 0 30px #fff, 0 0 0 40px red;
}

使用 CSS 渐变来绘制图标

CSS3 的渐变属性十分强大,理论上通过渐变可以绘制出任何的图形,渐变的特性和使用足足可以写一篇长文,以下为一个例子

<div class="gradient"></div>
.gradient {
  position: relative;
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background-color: silver;
  background-image: linear-gradient(335deg, #b00 23px, transparent 23px),
    linear-gradient(155deg, #d00 23px, transparent 23px), linear-gradient(
      335deg,
      #b00 23px,
      transparent 23px
    ), linear-gradient(155deg, #d00 23px, transparent 23px);
  background-size: 58px 58px;
  background-position: 0px 2px, 4px 35px, 29px 31px, 34px 6px;
}
  • 杯子
.cup {
  display: inline-block;
  width: 0.9em;
  height: 0.4em;
  border: 0.25em solid;
  border-bottom: 1.1em solid;
  border-radius: 0 0 0.25em 0.25em;
}
cup:before {
  position: absolute;
  right: -0.6em;
  top: 0;
  width: 0.3em;
  height: 0.8em;
  border: 0.25em solid;
  border-left: none;
  border-radius: 0 0.25em 0.25em 0;
  content: "";
}
  • 心形
.heart {
  display: inline-block;
  margin-top: 1.5em;
  width: 50px;
  height: 50px;
  background: green;
}
.heart:before,
.heart:after {
  position: absolute;
  width: 1em;
  height: 1.6em;
  background: #000;
  border-radius: 50% 50% 0 0;
  content: "";
  bottom: 0;
}
.heart:before {
  -webkit-transform: rotate(45deg);
  -webkit-transform-origin: 100% 100%;
  right: 0;
  background: red;
  opacity: 0.5;
  z-index: 5;
}
.:after {
  -webkit-transform: rotate(-45deg);
  -webkit-transform-origin: 0 100%;
  left: 0;
  opacity: 0.8;
}
  • 相机
.camera {
  display: inline-block;
  border-style: solid;
  border-width: 0.65em 0.9em;
  border-radius: 0.1em;
}
.camera:before {
  position: absolute;
  top: -0.3em;
  left: -0.3em;
  width: 0.4em;
  height: 0.4em;
  border-radius: 50%;
  border: 0.1em solid #fff;
  box-shadow: 0 0 0 0.08em, 0 0 0 0.16em #fff;
  content: "";
}
.camera:after {
  position: absolute;
  top: -0.5em;
  left: 0.5em;
  width: 0.2em;
  border-top: 0.125em solid #fff;
  content: "";
}
  • 月亮
.moon {
  display: inline-block;
  height: 1.5em;
  width: 1.5em;
  box-shadow: inset -0.4em 0 0;
  border-radius: 2em;
  transform: rotate(20deg);
}

浮动类

常规浮动 list 浮动 image 浮动

.float-left {
  float: left;
}
.float-right {
  float: right;
}
.float-li li,/*定义到li父元素或祖先元素上*/ li.float-li {
  float: left;
}
.float-img img,/*定义到img父元素或祖先元素上*/ img.float-li {
  float: left;
}
.float-span span,/*定义到span父元素或祖先元素上*/ span.float-span {
  float: right;
}

背景图片嵌入与定位

.bg-img {
  background-image: url("../img/bg.png");
  background-position: center top;
  background-repeat: no-repeat;
}
.bg01-img {
  background-image: url("../img/bg01.png");
  background-position: center top;
  background-repeat: no-repeat;
}
.bg02-img {
  background-image: url("../img/bg02.png");
  background-position: center top;
  background-repeat: no-repeat;
}
.bg03-img {
  background-image: url("../img/bg03.png");
  background-position: center top;
  background-repeat: no-repeat;
}
.bg04-img {
  background-image: url("../img/bg04.png");
  background-position: center top;
  background-repeat: no-repeat;
}

继承类

.inherit-width {
  width: inherit;
}
.inherit-min-width {
  min-width: inherit;
}
.inherit-height {
  height: inherit;
}
.inherit-min-height {
  min-height: inherit;
}
.inherit-color {
  color: inherit;
}

文本缩进

.text-indent {
  text-indent: 2rem;
}
/*16px*/
.text-indent-xs {
  text-indent: 1.5rem;
}
/*12px*/
.text-indent-sm {
  text-indent: 1.7rem;
}
/*14px*/
.text-indent-md {
  text-indent: 2rem;
}
/*18px*/
.text-indent-lg {
  text-indent: 2.4rem;
}
/*20px*/

行高

.line-height-xs {
  line-height: 1.3rem;
}
.line-height-sm {
  line-height: 1.5rem;
}
.line-height-md {
  line-height: 1.7rem;
}
.line-height-lg {
  line-height: 2rem;
}

.line-height-25x {
  line-height: 25px;
}
.line-height-30x {
  line-height: 30px;
}

ul 缩进

.ul-indent-xs {
  margin-left: 0.5rem;
}
.ul-indent-sm {
  margin-left: 1rem;
}
.ul-indent-md {
  margin-left: 1.5rem;
}
.ul-indent-lg {
  margin-left: 2rem;
}
.ol-list,
.ul-list {
  list-style: disc;
}

文本截断

.truncate {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hide {
  display: none;
}

图片、视频规范

.img-max,
.video-max {
  width: 100%;
  height: auto;
}
/*display显示方式*/
.inline {
  display: inline;
}
.inline-block {
  display: inline-block;
}
.block {
  display: block;
}

边框样式

.border-xs-black {
  border: 1px solid #000;
}
.border-sm-black {
  border: 2px solid #000;
}
.border-md-black {
  border: 3px solid #000;
}
.border-lg-black {
  border: 5px solid #000;
}

.border-xs-gray {
  border: 1px solid #9c9c9c;
}
.border-sm-gray {
  border: 2px solid #9c9c9c;
}
.border-md-gray {
  border: 3px solid #9c9c9c;
}
.border-lg-gray {
  border: 5px solid #9c9c9c;
}

背景颜色

.bg-white {
  background: #fff !important;
}
.bg-black {
  background: #1b1c1d !important;
}
.bg-gray {
  background: #767676 !important;
}
.bg-light-gray {
  background: #f8f7f7 !important;
}
.bg-yellow {
  background: #fbbd08 !important;
}
.bg-orange {
  background: #f2711c !important;
}
.bg-red {
  background: #db2828 !important;
}
.bg-olive {
  background: #b5cc18 !important;
}
.bg-green {
  background: #21ba45 !important;
}
.bg-teal {
  background: #00b5ad !important;
}
.bg-darkGreen {
  background: #19a97b !important;
}
.bg-threeGreen {
  background: #097c25 !important;
}
.bg-blue {
  background: #2185d0 !important;
}
.bg-violet {
  background: #6435c9 !important;
}
.bg-purple {
  background: #a333c8 !important;
}
.bg-brown {
  background: #a5673f !important;
}

分割线预设

hr,
.hr-xs-Silver,
.hr-sm-black,
.hr-sm-Silver,
.hr-xs-gray,
.hr-sm-gray {
  margin: 20px 0;
}
hr {
  border: none;
  border-top: 1px solid #000;
}
.hr-xs-Silver {
  border: none;
  border-top: 1px solid #c0c0c0;
}
.hr-sm-black {
  border: none;
  border-top: 2px solid #000;
}
.hr-sm-Silver {
  border: none;
  border-top: 2px solid #c0c0c0;
}
.hr-xs-gray {
  border: none;
  border-top: 1px solid #767676;
}
.hr-sm-gray {
  border: none;
  border-top: 2px solid #767676;
}

鼠标 a 标签 hover 效果

.hover-red a:hover,/*为a标签祖先元素添加类名 默认无智能提醒*/ a.hover-red:hover {
  color: red;
} /*单独为a标签添加类名*/
.hover-yellow a:hover,/*为a标签祖先元素添加类名 默认无智能提醒*/ a.hover-yellow:hover {
  color: #ffd700;
} /*单独为a标签添加类名*/
.hover-green a:hover,/*为a标签祖先元素添加类名 默认无智能提醒*/ a.hover-green:hover {
  color: #70aa39;
} /*单独为a标签添加类名*/
.hover-blue a:hover,/*为a标签祖先元素添加类名 默认无智能提醒*/ a.hover-blue:hover {
  color: blue;
} /*单独为a标签添加类名*/
.hover-gray a:hover,/*为a标签祖先元素添加类名 默认无智能提醒*/ a.hover-gray:hover {
  color: #9c9c9c;
} /*单独为a标签添加类名*/
.underline a:hover,
a.underline:hover {
  text-decoration: underline;
}

阴影效果

.shadow-text-xs {
  text-shadow: 4px 3px 0 #1d9d74, 9px 8px 0 rgba(0, 0, 0, 0.15);
} /*智能兼容ie10以上 暂不考虑*/

.shadow-xs {
  -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=1, Direction=100, Color='#cccccc')"; /* For IE 8 */
  filter: progid:DXImageTransform.Microsoft.Shadow(Strength=1, Direction=100, Color='#cccccc'); /* For IE 5.5 - 7 */
  -moz-box-shadow: 1px 1px 2px #cccccc; /* for firefox */
  -webkit-box-shadow: 1px 1px 2px #cccccc; /* for safari or chrome */
  box-shadow: 1px 1px 2px #cccccc; /* for opera or ie9 */
}
.shadow-sm {
  -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=2, Direction=120, Color='#cccccc')"; /* For IE 8 */
  filter: progid:DXImageTransform.Microsoft.Shadow(Strength=2, Direction=120, Color='#cccccc'); /* For IE 5.5 - 7 */
  -moz-box-shadow: 2px 2px 3px #cccccc; /* for firefox */
  -webkit-box-shadow: 2px 2px 3px #cccccc; /* for safari or chrome */
  box-shadow: 2px 2px 3px #cccccc; /* for opera or ie9 */
}
.shadow-md {
  -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=3, Direction=135, Color='#cccccc')"; /* For IE 8 */
  filter: progid:DXImageTransform.Microsoft.Shadow(Strength=3, Direction=135, Color='#cccccc'); /* For IE 5.5 - 7 */
  -moz-box-shadow: 3px 3px 5px #cccccc; /* for firefox */
  -webkit-box-shadow: 3px 3px 5px #cccccc; /* for safari or chrome */
  box-shadow: 3px 3px 5px #cccccc; /* for opera or ie9 */
}
.shadow-lg {
  -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=5, Direction=150, Color='#cccccc')"; /* For IE 8 */
  filter: progid:DXImageTransform.Microsoft.Shadow(Strength=5, Direction=150, Color='#cccccc'); /* For IE 5.5 - 7 */
  -moz-box-shadow: 5px 5px 8px #cccccc; /* for firefox */
  -webkit-box-shadow: 5px 5px 8px #cccccc; /* for safari or chrome */
  box-shadow: 5px 5px 8px #cccccc; /* for opera or ie9 */
}

圆角

.border-radius-xs {
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  border-radius: 3px;
}
.border-radius-sm {
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  border-radius: 5px;
}
.border-radius-md {
  -webkit-border-radius: 7px;
  -moz-border-radius: 5px;
  border-radius: 5px;
}
.border-radius-lg {
  -webkit-border-radius: 9px;
  -moz-border-radius: 9px;
  border-radius: 9px;
}

如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的肯定是我前进的最大动力😁

  • 附笔记链接,阅读往期更多优质文章可移步查看,喜欢的可以给我点赞鼓励哦:#29

参考文章

截屏2020-03-15上午10 53 06

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.