wscats / cv Goto Github PK
View Code? Open in Web Editor NEW:see_no_evil:Front End Engineer Curriculum Vitae - 面试宝典和简历生成器
Home Page: http://wscats.github.io/CV/omi/build/index.html
:see_no_evil:Front End Engineer Curriculum Vitae - 面试宝典和简历生成器
Home Page: http://wscats.github.io/CV/omi/build/index.html
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
作为最简单的排序算法之一,冒泡排序给我的感觉就像 Abandon 在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
当输入的数据已经是正序时(都已经是正序了,我还要你冒泡排序有何用啊)。
当输入的数据是反序时(写一个 for 循环反序输出数据不就行了,干嘛要用你冒泡排序呢,我是闲的吗)。
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;
}
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
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²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。
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;
}
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
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
}
插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
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;
}
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
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
}
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
希尔排序的基本**是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
按增量序列个数 k,对序列进行 k 趟排序;
每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
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;
}
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
}
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)的一个非常典型的应用。
作为一种典型的分而治之**的算法应用,归并排序的实现由两种方法:
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
设定两个指针,最初位置分别为两个已经排序序列的起始位置;
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
重复步骤 3 直到某一指针达到序列尾;
将另一序列剩下的所有元素直接复制到合并序列尾。
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;
}
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
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) 的排序算法表现要更好。
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
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;
}
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]
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]
}
//标准分割函数
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)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
堆排序的平均时间复杂度为 Ο(nlogn)。
创建一个堆 H[0……n-1];
把堆首(最大值)和堆尾互换;
把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
重复步骤 2,直到堆的尺寸为 1。
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;
}
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
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]
}
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
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;
}
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
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
}
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
当输入的数据可以均匀的分配到每一个桶中。
当输入的数据被分配到了同一个桶中。
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;
}
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
基数排序有两种方法:
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
//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;
}
var i = 0;
function drawTable() {
document.write("<tr><th>123</th><th>123</th></tr>");
i++
if(i <= 10) {
drawTable()
}
}
drawTable()
我平时使用 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
将当前索引的更改保存为一个新的提交,这个提交包括用户做出的更改与信息
# 提交暂存区到仓库区附带提交信息
$ 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] ...
显示当前工作空间和提交的不同
# 显示工作目录和索引的不同
$ git diff
# 显示索引和最近一次提交的不同
$ git diff --cached
# 显示工作目录和最近一次提交的不同
$ git diff HEAD
可以在版本库中快速查找
可选配置:
# 感谢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 \)
显示这个版本库的所有提交
# 显示所有提交
$ 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
合并就是将外部的提交合并到自己的分支中
# 将其他分支合并到当前分支
$ git merge branchName
# 在合并时创建一个新的合并后的提交
# 不要 Fast-Foward 合并,这样可以生成 merge 提交
$ git merge --no-ff branchName
重命名或移动一个文件
# 重命名
$ 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
$ 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]
从远端版本库合并到当前分支
# 从远端origin的master分支更新版本库
# git pull <远端> <分支>
$ git pull origin master
# 抓取远程仓库所有分支更新并合并到本地,不要快进合并
$ git pull --no-ff
$ git ci <file>
$ git ci .
# 将git add, git rm和git ci等操作都合并在一起做
$ git ci -a
$ git ci -am "some comments"
# 修改最后一次提交记录
$ git ci --amend
将一个分支上所有的提交历史都应用到另一个分支上
不要在一个已经公开的远端分支上使用 rebase.
# 将experimentBranch应用到master上面
# git rebase <basebranch> <topicbranch>
$ git rebase master experimentBranch
将当前的头指针复位到一个特定的状态。这样可以使你撤销 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
Element.prototype.on = Element.prototype.addEventListener;
NodeList.prototype.on = function (event, fn) {、
[]['forEach'].call(this, function (el) {
el.on(event, fn);
});
return this;
};
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;
};
function HtmlEncode(text) {
return text
.replace(/&/g, "&")
.replace(/\"/g, '"')
.replace(/</g, "<")
.replace(/>/g, ">");
}
// 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, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
// 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");
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;
}
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;
}
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;
}
function getCookie(name) {
var arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)"));
if (arr != null) return unescape(arr[2]);
return null;
}
// 用法:如果地址是 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);
}
function getPageScrollLeft() {
var a = document;
return a.documentElement.scrollLeft || a.body.scrollLeft;
}
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中的某参数值,不区分大小写,
// 默认是取'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;
}
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;
}
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/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();
}
}
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('&'))
}
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);
};
(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);
}
}
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())
);
}
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("");
}
如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的肯定是我前进的最大动力😁
写一段脚本,实现:当页面上任意一个链接被点击的时候,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加载完之后,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的区别
遍历一个某一元素下的所有子元素(包括子元素的子元素)的方法,打印出所有子元素的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, 设置一个函数输出一下的值
难点:打印和相加计算,会分别调用toString
或valueOf
函数,所以我们重写temp的toString
和valueOf
方法,返回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后,既返回了函数,还打印出了结果
实现下面的函数
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)可以为真吗?
const a = { value: 0 }
a.valueOf = function () {
return this.value += 1
}
console.log( a == 1 && a == 2 && a == 3 );
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
有这样一个热门问题:
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
alert(a.x); // --> undefined
alert(b.x); // --> {n: 2}
「时光不负,创作不停,本文正在参加2021 年终总结征文大赛」
每年的 12 月总像是一场告别,2021 年从起点到终点也该时候跟你说一声再见,忙碌的一年难得周五请假搬完了家,在夕阳下写下这篇文章回忆总结这一年,这一年回望来时的路,至少有一些同事给我鼓励和安慰,至少有些事不曾莫名伤心和流泪,至少还有家人相互依偎。
回顾了今年在掘金的表现,说实话有点惭愧,只发表了四篇文章,虽然没有多少阅读量,但我每篇都写得很用心,其中写了一篇关于前端黑产技术的实现原理,好不容易才通过了审核发表,然后又花了两个月把 Fackboook
的单元测试框架从头到尾看了一篇,写了篇如何从零开始实现一个类 Jest
的单元测试框架,写完 Fackboook
就改名为 Meta
了,后面华为孟晚舟被释放成功回国,激动之下写了一篇关于鸿蒙 OpenHarmony
的开发分享。先定了个小目标,希望明年能写完关于单元测试专题的掘金小册并发表,目前只写了两三万字,距离发表还很遥远,加油吧!
除了在掘金产出比较低之外,今年在开源社区的贡献也不是很多,因为大部分都需要在周末或者业余时间完成,观察了下提交记录,在三四季度基本断开连接,一句话总结就是越到后面越有心无力了,或许也是因为平时工作也越来越忙了,不像上几年沉迷于研究各种技术框架,给 VSCode
和 Omi
贡献一些代码。只有在年初给国产的 Hbuilder
写了些插件,年底参加了国内码云的鸿蒙 OpenHarmony
比赛,移植了一个表格渲染引擎,收获了 7k
奖金和开发板能勉强说一下了。
Github
收获了 2k
的关注和 13k
星星 https://github.com/wscatsSegmentfault
社区收获了 2k
点赞和 12k
关注 https://segmentfault.com/u/wscats知乎
社区收获了 5k
关注KM
社区文章收获了 16k
浏览量VSCode
插件下载量 900k
https://marketplace.visualstudio.com/publishers/wscatsHbuilder
插件下载量 80k
https://ext.dcloud.net.cn/publisher?id=183012
次,公司外技术分享 1
次Styletron
,React Dnd
和 Jest
等源代码Gitee
拿了开源一等奖和二等奖这一年其实没积累到太多的知识,也忘记了很多的东西,用得比较多的是 React
,less
和 Typescript
等技术栈,说实话我甚至都忘了 Vue
和 Angular
等框架是怎么用的了,如果让我写一个 Vue
相关的项目,我可能需要去官网花好长时间去重温,Vue3
也没好好学习,忘了怎么使用路由和状态管理,有哪些生命周期,有哪些钩子,怎么实现一个 loader
和 plugin
,怎么写一个 VSCode
和 Chrome
插件,怎么手写 Promise
和防抖节流,怎么实现设计模式,怎么算空间和时间复杂度,如果有人问我这些问题,我可能真的不能再详细答出来了。
肌肉记忆都保留在公司的产品调试链路,如何环境切换,如何定位 Bug
,如何解决用户的反馈问题,记忆也感觉差了很多,不知道是不是熬夜变多了,休息不充分,回头看看自己的工作提交记录,除了假期大部分时间都是在线的,提交密度和代码量还是远远大于开源社区的。
今年在开发功能的时候,要经常思考怎么写一个易维护,既方便别人和自己的组件,在维护模块修复 Bug
的时候,要经常思考前面的人为什么这么写,他可能遇到什么困难,那个时期可能有那个时期的局限性,可能那个时候有上线压力,也可能那时候并没有那么思考长远,也可能那时候并没有规划好。
当下的我能做好的是对未来的自己或者别人负责,若干年之后自己或者别人再看回你写的这段代码能不能减少他一点疑惑,写的再好的代码远不如一条注释让人阔然开朗,至少让他明白为什么我要这样写,甚至有时候我觉得写注释是一种自信的表现,当别人 Code Review
你的代码的时候会更清晰也更能看出你的态度,如果它是相对稳定的代码我会在维护的时候尽可能多留下点注释,如果它是比较不稳定代码,我会思考重构部分或者整体,并留下单元测试。
所以在今年我写下了比以往任何时候都多的注释和单测,勿以善小而不为,勿以恶小而为之,有些代码经历了各种时期的变动,但注释可能还是当初的样子,因为我发现删代码是很常见,但是删注释真的比较少,那它存在的意义相对多点,比翻修改的历史记录来的更高效,如果最后代码和单测都被删掉了,那这才是它最终的归宿和使命,希望自己自己明年写的单测和注释再好一点,不求尽如人意但求问心无愧。
翻了下相册,看到了这张代表着运气爆棚的照片,不得不说下,人生首次人品爆发,在年初参加部门年会抽到了手机,从座位到领奖台只有十几米,章子怡花了十七年才能登台拿到了影后,而我花光了一年所有的运气才拿到了一部手机哈哈,但我依然很喜欢站在奖台上,因为那需要很大的力气和运气,当然希望今年也能抽到奖品。
身旁几位大佬带我一个坑,也让我在台上领到了梦寐以求的部门奖金,教会了我如何实现大型前端项目函数调用链跟踪和分析方案,教会落地商业化实现的方案,教会我如何各种姿势薅羊毛,教会我如何花式凡尔赛,教会我攻克难题优化性能,教会了我 try catch
用得好,性能翻一番...
紧接着部门架构的升级调整,搬到了新的办公地,虽然,又认识到一批非常优秀的同事,让我每周节目都很丰富,好好给这些可爱的同事点下名:
xunxun
,json
,ziming
和杜兰特等大腿去打篮球KTV
和打羽毛球让我可以有机会跟漂亮妹子切磋下大圣
,JC
,凡凡,国春的豪华代练团队,让我明白打王者,LOL
和怪猎原来那么轻松solin
的篮球比赛让我热血沸腾,好想舔下朱芳雨给他发的冠军戒指Typescript
让我顶礼膜拜,CR
人肉扫描让我受益匪浅正因为有一个优秀的部门和一群优秀的同事,埋在春天的那些种子,都在之后冬夏逐渐焕发。明年我们都要加油,继续迎接着下一个阶段全新的挑战!
今年我也去了很多的地方,去了些离工作地不会很远的地方去旅游,因为疫情和工作的原因,要随时待命赶回来处理工作,怕 14
天隔离,回来工位都没了哈哈,在生活也解锁了很多新的技能,部门组织去了花都融创滑雪,第一次滑雪,从 66
米高,坡度 21
度一直龟速滑下来,当然免不了摔了无数次,但很刺激,以后有时间还要来玩。
去打了第一次的棒球,初体验很好就是有点累,但规则对于我这种头脑简单的人来说实在是太复杂了,就记得要跑得快要跑准垒位,说好的没有身体接触的运动,居然还有触杀这种反人类的设计,有点费脑费脚,场下试了下用棒子击飞一个固定的球的难度也很大,属于随缘碰运气,更别用说要打高打远了,所以我给自己定了个需求,人生一定要实现一次全垒打。
坐船去了一趟珠海,在台风天看到了传说中的港珠澳大桥,沿途晕船吐了一路,在长隆看到了鲸鱼,海牛和海豚,坐了可怕的鹦鹉过山车,在拱北情侣路逛街吃烧烤,用 iPad
画了几幅油画。
坐高铁去了长沙参加旧同事的婚礼,喝了茶颜悦色,吃了臭豆腐,串串,小龙虾和辣椒炒肉,走过湘江,橘子洲,岳麓山和黄兴广场。
参加公司的团建坐飞机去了海南,住进了网红酒店亚特兰蒂斯,让我体验了一波高大上的五星级酒店,去了水世界爽了一天,近距离看看海洋世界,玩到了海神之跃各种旋转大喇叭。
租车自驾游去了新会和韶关,玩了动力伞,走过玻璃桥,泡了温泉,但最难忘的是经历第一次交通事故,由于是新手上路,又不熟悉路况,在一个双十字路口转弯的时候没注意,刮到了别人的车,学会了打电话等交警然后报保险冷静处理,当然幸好是没有人员受伤,也算是给自己一个教训。
很多时候当你经历一些成长的事情,就会慢慢发现学会让自己开心,不为难自己,懂得在生活中,事业中和感情中与自己和解真的很重要,我依然觉得自己几年前在剃光头进手术室前是最帅最勇敢的,但我更觉得当下的我是开心和幸运的,学会和自己和解,真的会少走很多弯路,人生还很长,我还有很多 Switch
游戏,电脑和手机游戏想玩,还有很多美丽的地方想去,还有很多有趣的人等着我去认识。
最后给明年的自己一些目标,多读几本读书,多刷几部剧,少吃点垃圾食品,多睡点觉,多陪陪家人,保持健康坚持学习直到我生命停止呼吸。坚持自己的初心,用心工作开心生活,纵使饱经世故,亦持续单纯,也希望明年有更多志同道合的人加入我们腾讯 AlloyTeam
团队,一起去探索和遨游!分享总结不易,如果文章能给您一点启发,请不要稀罕你的赞~
如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的肯定是我前进的最大动力😁
function noRepeat(arr) {
return [...new Set(arr)];
}
function arrayMax(arr) {
return Math.max(...arr);
}
function arrayMin(arr) {
return Math.min(...arr);
}
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);
}
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));
}
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;
}
function everyNth(arr, nth) {
return arr.filter((v, i) => i % nth === nth - 1);
}
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;
}
/**
* 告知浏览器支持的指定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,
};
}
function getURLParameters(url) {
return url
.match(/([^?=&]+)(=([^&]*))/g)
.reduce(
(a, v) => (
(a[v.slice(0, v.indexOf("="))] = v.slice(v.indexOf("=") + 1)), a
),
{}
);
}
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";
}
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;
}
}
默认为当前时间转换结果
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()}`;
}
/**
* 功能描述:一些业务场景,如弹框出现时,需要禁止页面滚动,这是兼容安卓和 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);
}
function bottomVisible() {
return (
document.documentElement.clientHeight + window.scrollY >=
(document.documentElement.scrollHeight ||
document.documentElement.clientHeight)
);
}
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;
}
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("退出全屏");
}
});
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...
欢迎您的关注和交流😁
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
作为最简单的排序算法之一,冒泡排序给我的感觉就像 Abandon 在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
当输入的数据已经是正序时(都已经是正序了,我还要你冒泡排序有何用啊)。
当输入的数据是反序时(写一个 for 循环反序输出数据不就行了,干嘛要用你冒泡排序呢,我是闲的吗)。
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;
}
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
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²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。
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;
}
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
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
}
插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
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;
}
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
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
}
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
希尔排序的基本**是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
按增量序列个数 k,对序列进行 k 趟排序;
每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
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;
}
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
}
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)的一个非常典型的应用。
作为一种典型的分而治之**的算法应用,归并排序的实现由两种方法:
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
设定两个指针,最初位置分别为两个已经排序序列的起始位置;
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
重复步骤 3 直到某一指针达到序列尾;
将另一序列剩下的所有元素直接复制到合并序列尾。
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;
}
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
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) 的排序算法表现要更好。
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
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;
}
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]
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]
}
//标准分割函数
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)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
堆排序的平均时间复杂度为 Ο(nlogn)。
创建一个堆 H[0……n-1];
把堆首(最大值)和堆尾互换;
把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
重复步骤 2,直到堆的尺寸为 1。
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;
}
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
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]
}
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
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;
}
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
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
}
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
当输入的数据可以均匀的分配到每一个桶中。
当输入的数据被分配到了同一个桶中。
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;
}
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
基数排序有两种方法:
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
//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;
}
在我们腾讯文档项目中,我们常用的顶部工具栏会根据编辑权限,屏幕宽度,设备等场景配置对应的显示内容。
我们的菜单或者底部栏,某些时间段内需要出现引导用户的红点,子菜单栏在某些交互行为下显示不同内容项,切换英文的时候菜单栏内容变更为中文等。
上述的这些场景会导致我们的业务代码会高几率出现下面类似的写法,大量的条件判断等,如果后续需要更改条件或者增加条件,都需要在大量的条件判断里面去补充逻辑,并且工具栏的显示效果可能会根据不同业务场景改变外观,比如图标,排序和样式,那么每次第三方接入或者运营想额外配置都需要开发人员在代码里面做修改,这显然是不利于维护和低效的。
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 的时候,条件判断为真,图标就出现了。
既然 Vscode 支持利用插件加载配置文件来配置编辑器的显示功能,那么我们其实同样也可以使用这个思路来对我们的工具栏进行配置。
下面我们就详细讲解 Vscode 的插件机制,并利用它的思路实现一个属于我们自己腾讯文档UI可配置化的机制,Vscode 提供了一个 ExtensionsRegistry 的实例,该实例下有两个关键的方法:
首先使用 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 的视图。
这里会提供 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 视图的绑定。
要实现这种方案本质其实就是把 when: xxx
这种配置参数解析为一个布尔值,Vscode 为了实现这个目的,在内部自己实现了一个简单的表达式解析器,目前支持以下表达式:
Vscode 只实现了上面这些简单的表达式解析就很好的支持了上万个插件的配置,那说明上面这些解析器正常情况是够用的,也是 Vscode 鼓励我们去使用的规范。
我们如果自己实现一个复杂点的解析器,可以考虑支持以下表达式。
注意大于和小于均不支持,所有我们刚才 "when": "canEdit == true && window.innerWidth < 1080 && window.innerWidth >= 855"
这类写法我们是不支持,需要自己拓展,在腾讯文档的插件机制里面是支持这部分的。
这里简单说下思路,我们可以封装一个 deserialize
方法去解析 "when": "canEdit == true || platform == pc && window.innerWidth >= 1080"
这段字符串,里面涉及了 ==,&&,>=
三个表达式的解析,使用 indexOf
和 split
进行分词,一般切割成三部分,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 |
具体的分词规则也很简单,以下面这颗树生成的思路为例子,遵循我们常用表达式的一些语法规范和优先级规则,优先切割 ||
两边所有的表达式,然后遍历两边的表达式往下去切割 &&
表达式,切完所有的 ||
和 &&
再处理子节点的 !=
、==
和 >=
等这些符号。
当我们把切割完整个 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 的对象进行处理,它提供两个关键的接口 setValue
和 getValue
方法,简单的实现如下。
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;
}
关于这方面的相关文章不多,一路走来跳了不少的坑,感谢团队成员的支持,并让这个方案最终成功落地,也希望有更多志同道合的人加入我们腾讯文档团队,一起去探索和遨游,最后也希望这篇文章能给到你们一些启发吧😁
Element.prototype.on = Element.prototype.addEventListener;
NodeList.prototype.on = function (event, fn) {、
[]['forEach'].call(this, function (el) {
el.on(event, fn);
});
return this;
};
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;
};
function HtmlEncode(text) {
return text
.replace(/&/g, "&")
.replace(/\"/g, '"')
.replace(/</g, "<")
.replace(/>/g, ">");
}
// 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, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
// 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");
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;
}
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;
}
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;
}
function getCookie(name) {
var arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)"));
if (arr != null) return unescape(arr[2]);
return null;
}
// 用法:如果地址是 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);
}
function getPageScrollLeft() {
var a = document;
return a.documentElement.scrollLeft || a.body.scrollLeft;
}
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中的某参数值,不区分大小写,
// 默认是取'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;
}
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;
}
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/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();
}
}
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('&'))
}
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);
};
(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);
}
}
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())
);
}
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("");
}
如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的肯定是我前进的最大动力😁
Visual Studio Code 的插件对于在提升编程效率和加快工作速度非常重要。这里有 30 个最受欢迎的 VSCode 插件,它们将使你成为更高效的搬砖摸鱼大师。这些插件主要适用于前端开发人员,但也有一些通用插件也可以适用于任何开发环境。以下是我将介绍的 VSCode 插件:
Settings Sync
可为你节省大量跨设备安装插件的时间
在开始左右安装插件之前,最好知道Settings Sync的存在。它允许你将在 VSCode 上自定义的几乎所有内容同步到 Github,从设置到键盘快捷键到其他 VSCode 插件。
这样,你就可以从任何你想要的设备访问你喜欢的 IDE,而不必在新设备上从普通
VSCode 环境中进行编程,也不必再次手动设置所有内容。
立即查看浏览器中反映的代码更改
这是我最喜欢的插件之一。Live Server启动本地开发服务器,并为静态和动态页面提供实时重新加载功能。
每次保存代码时,你都会立即看到浏览器中反映的更改。你会更快地发现错误,并且可以更轻松地对你的代码进行一些快速实验。
Tabnine 是一款广受欢迎的 VSCode 人工智能助手,适用于所有主要编程语言,因此毫无疑问,无论你的技能如何,你都会发现它很有用。
通过研究公开共享的代码库,Tabnine 使用深度学习算法来预测你的需求,并提供一键代码完成功能,让你可以快速高效地完成项目。 对于新手编码员来说,这让学习变得轻而易举,因为它为你提供了发挥想法的空间,而无需记住编码语法或搜索 StackOverflow。
而且,如果你是一位经验丰富的开发人员,那么你会发现 Tabnine 为你提供了运行所需的构建块,从而为你的工作节省了大量时间。
使用任何带有 SSH 服务器的远程机器,该SSH插件可以让你使用任何远程计算机与 SSH 服务器作为开发环境。这使得在各种场景中开发和或故障排除变得更加容易。
你也不需要本地机器上的任何源代码,因为插件直接在远程机器上运行命令和其他插件。
花更少的时间格式化代码
Prettier是一个自以为是的代码格式化程序,如果你有多人在一个项目上工作,它会特别有效,因为该插件强制执行一致的样式。
你可以对其进行设置,以便在每次保存代码时格式化你的代码,从而显着减少你花在格式化代码上的时间。
每个人都喜欢对代码着色,Bracket Pair Colorizer提供了匹配颜色的左括号和右括号,从而更容易知道哪些括号属于谁。
还可以配置自定义括号字符,你也可以为活动范围添加背景颜色。
自动重命名标签,虽然 VSCode 固有地突出显示匹配的标签并在你键入开始标签时立即添加结束标签,但自动重命名标签插件会自动重命名你更改的标签。
该插件适用于 HTML、XML、PHP 和 JavaScript,无需更改标签名称两次。
增强你的 Git 能力GitLens 增强了Visual Studio Code 的 Git 功能。这是一个强大的插件,可让你查看代码行随时间变化的人、原因和方式以及许多其他功能。
GitLens 是一个高度可定制的插件。如果你不喜欢某个特定设置,你可以在设置中轻松将其关闭。
获得 git 日志,并显示漂亮的视觉效果
与 GitLens 类似,Git History是一个 VSCode 插件,它提供了 git 日志的可视化。你不需要再终端中查看 git log。
插件也非常全面。它允许你跨提交比较分支、提交和文件。也可以查 Github 头像,挺整洁的。
这是一款在 Vue 2 或者 Vue 3 开发中提供代码片段,语法高亮和格式化的 VSCode 插件,能极大提高你的开发效率。你可以在 VSCode 编辑器底部栏右下角打开 Auto Format Vue 开关,它可能帮你在代码保存的时候自动格式化 vue 文件的格式,默认是关闭状态。
如果你不想自动格式化 vue 文件,你也可以在 vue 文件中点击鼠标右键,在出现的菜单栏中选择 Format Document 菜单项,则文件会执行一次格式化。
插件你的 HTML 文件以查看你的 CSS 代码
这个插件对于前端开发人员来说是无价的。受 IDE Brackets 中类似功能的启发,CSS Peek允许你插件 HTML 和 ejs 文件以在源代码中显示 CSS/SCSS/LESS 代码。
如果你知道类或 ID 名称,它还允许你快速跳转到对应 CSS 代码的位置。
提供很多 JS 代码块提示,虽然 VSCode 包括内置的 JS IntelliSense,但JS 代码片段插件通过添加大量导入、导出触发器、类助手和方法触发器来增强这种体验。
该插件支持 JS、TypeScript、JS React、TS React、HTML 和 Vue。在 VSCode Marketplace 中,也可以轻松获得其他风格(例如 Angular)的代码片段。
只需输入前缀名称,就会自动完成完整的代码片段。
一款让您在代码中进行搜索或者翻译的 VSCode 插件。你可以在编辑器中,选中代码中对应的关键词,然后点击鼠标右键,在出现的菜单面板中选择 Search Online 菜单项,插件会自动帮你打开默认浏览器,并搜索对应的关键词和显示搜索结果。
你还可以选中对应的关键词后,使用快捷键去打开浏览器进行搜索。
更改 VSCode 实例的颜色,非常实用。Peacock允许你更改 Visual Studio Code 环境的颜色,因此你可以快速识别刚刚切换到的实例。
查看你在风格指南中使用的颜色,使用Colorize立即将 CSS/SASS/Less/... 文件中的 CSS 颜色可视化。这使得一目了然地看到你在何处使用了哪些颜色变得非常容易。
让你代码不再有拼写错误,虽然拼写错误不是致命问题,但我更喜欢我的代码没有拼写错误。代码拼写检查器插件在其字典文件中无法识别的单词下划线。
该插件有许多不同的语言版本,并支持医学术语等行话。
在 VSCode 中调试你的 JS 代码,由 Microsoft 开发的Debugger for Chrome允许你在 VSCode 中调试你的 JS 代码。与其他 IDE 中的调试器相反,它非常流畅。
你可以设置断点、逐步执行代码、调试动态添加的脚本等等。
提供各种图标供你使用!Icon Fonts提供了各种图标字体的片段,包括流行的 Font Awesome v5 图标包。
对于那些不使用 VSCode 的人,这个包也可用于 Atom 和 Sublime Text。
自动创建有意义的日志消息,该控制台显示日志\插件自动创建一个有意义的日志信息的过程。它使调试更容易,因为你将有一些有用的控制台输出来找出问题所在。
立即发现代码中的 TODO,很多程序员习惯在代码中写 TODO,然后完全忘记它们。Todo Highlight使它们更加突出。
你可以切换突出显示,也可以列出所有突出显示的注释并从相应的文件中显示它们。
等等,不是每个人都喜欢图标吗?你不会认为图标有很大的不同,但它们确实有至少对我来说。VSCode Icons为你的 IDE 增添了一抹色彩和可爱的小图标,我已经爱上了它。
创建正则表达式的预览,正则表达式可能是一个很困难的难题。Regex Previewer为你提供与你的正则表达式匹配的辅助文档。
该插件提供了多个示例进行匹配,因此为各种用例快速准确地编写正则表达式变得更加容易。
为你的代码添加书签,尽管 VSCode 有行号,但Bookmarks允许你在代码中添加书签,帮助你快速导航并轻松来回跳转。
当你开发 Web 应用程序或网站时,很容易制作一些臃肿的东西——尤其是作为一个新手开发者。 这其中的一个重要因素是,许多开发人员在通过代码导入时并不总是知道包有多大。
Import Cost 是一个 VSCode 扩展,可以内联显示导入包的大小,因此你可以确切地知道在开发过程中导入该包的成本是多少。 因此,它将帮助你更好地优化你的应用程序和网站,特别是对于通常因膨胀而遭受更多痛苦的移动用户。
如果你选择的语言是 JavaScript 或 TypeScript,那么你一定会喜欢 Quokka.js。 此扩展旨在通过在编写代码时在 IDE 中显示运行时值来加快开发速度,因此你可以专注于编写代码,而不是仅仅为了尝试新事物而构建自定义配置。
这是一个简单、轻量级的扩展,非常适合经验丰富的开发人员和新手。 它也可以免费供社区使用,但如果你是 JavaScript/TypeScript 专家,你还可以购买 Pro 许可证,让你无需更改代码即可修改运行时值。
如果你需要格式化程序和规范代码,那么这个插件适合你。
它可以自动格式化你的代码并查找代码中的错误。
此外,它允许你在书签代码之间选择代码区域,这对于日志文件分析等非常有用。
它旨在帮助开发人员和程序员提供智能代码完成建议。
它默认支持 Python、TypeScript/JavaScript、React 和 Java。
通过 VSCode 管理数据库的工具。
它支持许多驱动程序,你可以使用它来做很多事情,例如连接资源管理器、查询运行程序、智能感知、书签、查询历史记录。
Better Comments 扩展将帮助你在代码中创建更人性化的注释。
每种颜色都可以作为表示评论类型(注意、待办事项等)的一种方式。
这是我个人介绍的 30 个 VSCode 插件,可在不影响质量的情况下提高你的编程效率。,如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,文章同步持续更新,往期文章也收录在 https://github.com/Wscats/CV
欢迎您的关注和交流,你的肯定是我前进的最大动力 😁
在前几天腾讯文档给 VSCode 合入了大概 400 行核心代码,主要涉及到 VSCode 配置化的部分,增强其插件化能力,提供更多的匹配接口,整理部分代码结构和补充功能单测。
由于腾讯文档最近在完善我们的配置化系统,在完善的过程也探索了多种实现方案,也分析了很多产品的实现方式,如大名鼎鼎的 VSCode,我们也希望把我们积累经验贡献给开源社区,一起共同进步。
其中合入腾讯文档提供的代码是微软 VSCode 团队现主要负责人之一 Alexdima,也是 Monaco Editor 负责人(VSCode前身),也是同 Erich Gamma (VSCode之父) 来自苏黎世同一个团队,特别感谢他和他团队的支持,还帮我们挖坑的代码写了不少单测...
由于我们是给 VSCode 贡献了这部分代码,那我们就从 VSCode 本身开始聊起,我们给 VSCode 的配置化贡献了什么?我相信大部分的开发者都使用过 VSCode,所以配置化应该不陌生,由于使用者众多,任何编辑器其实都不能做到面面俱到去满足所有的使用者,如果什么用户的需求都要满足,就需要把所有的功能都塞进去,这不但臃肿,还不好维护。
所以我们需要配置化去丰富和拓展,减轻编辑器本身的包袱,把部分内容移交给用户/合作方去定制,就如我们可以在 VSCode 的设置面板选择编辑器的颜色,更换它的主题背景。
也可以在快捷键面板里面绑定或者解绑我们的快捷键,更换我们的字体大小和改变我们的悬浮信息等,这些其实都离不开背后实现的一套配置化系统。
上面举例的都是有默认的配置,可以通过面板去更改的,当然还有些隐藏的配置我们无需在面板改变也能实现配置,比如缩小 VSCode 的界面大小,某些功能就会自动隐藏,这种也是属于配置化。
我们除了通过面板可视化操作,还可以通过插件来配置界面,VSCode 中插件的核心就是一个配置文件 package.json
,里面拥有提供了配置点,只需按要求编写正确的配置点就可以改变 VSCode 的视图状态,里面最主要的字段就是 contributes
字段:
这是更换编辑器各个位置颜色的配置参数,很简单明了。
{
"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
文件填写对应的参数,那么压力就会转嫁到使用者或者接入者身上。
这种写法看似美好,但会出现很多致命情况,darken
和 lighter
在执行前已经被带条件表达式给装饰了,后面如果二次执行 darken
和 lighter
方法则不会再执行装饰器中条件表达式的判断,本质上这两个函数的 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);
};
}
};
};
而正常情况下客户端侧 isLinux
,isMacintosh
和 isWindows
是不会发生改变的,但是 window.innerWidth
在客户端却是有可能持续发生变化,所以一般情况下对待客户端环境经常变化的值或者需要通过作用域判断值,我不建议写成上面装饰器暴露接口的方案,但是如果这是一个比较固定的配置值,那么这种方案配合 webpack
的 DefinePlugin
会有意外的收获。
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.darken
和 JSON.lighter
分别是对应 JSON 文件中的配置项,所以实际在代码运行时的时候接受的字符串参数:
@If("isMacintosh || window.innerWidth > 855")
darken() {
return darken(this.color);
}
@If("isWindows || window.innerWidth <= 855")
lighter() {
return lighter(this.color);
}
这是大部分配置化绕不开的问题,简单的配置只需要传承好字符串语义即可,但是复杂的配置化可能是带有条件表达式,代码语法等东西,截一张 VSCode 官方插件的配置代码,满满都是配置表达式。
本质上这些字符串最终是要解析为布尔值,作为开关去启动 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
{
"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 这方面还不是最完整的。
in
和typeof
=
、不等!
&&
、或||
>
、小于<
、大于等于>=
、小于等于<=
的比较运算!
等逻辑运算我们的配置解析器支持上述所有的方法,在文章中 大型前端项目如何实现 UI 可配置化 有详细讲解这部分的原理,这里再具体讲述下思路:
我们使用下面这个再复杂的例子来概括不同的情况:
"when": "canEdit == true || platform == pc && window.innerWidth >= 1080"
我们可以封装一个 deserialize
方法去解析 "when": "canEdit == true || platform == pc && window.innerWidth >= 1080"
这段字符串,里面涉及了 ==,&&,>=
三个表达式的解析,使用 indexOf
和 split
进行分词,一般切割成三部分,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
进行等值比对。
条件表达式分词规则再用一张图来表示,以下面这颗树生成的思路为例子,遵循我们常用表达式的一些语法规范和优先级规则,优先切割 ||
两边所有的表达式,然后遍历两边的表达式往下去切割 &&
表达式,切完所有的 ||
和 &&
再处理子节点的 !=
、==
和 >=
等这些符号。
当我们把切割完整个 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
}
}
我们要上面也说了,"window.innerWidth"
,canEdit
和 "platform"
这些是字符串,不是真正可用于判断的值,这些 key 有些是运行时才会得到值,有些是在某个作用域下才会得到值,我们也需要将这些 key 进行转化,我们借鉴了 Vscode 的做法,在 Vscode 中,它会将这部分逻辑交给一个叫 context 的对象进行处理,它提供两个关键的接口 setValue
和 getValue
方法,简单的实现如下。
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 来进行个性化配置。
那么最后只要封装上面例子用到 contextMatchesRules
方法,先读取 json 配置文件为对象,遍历出每一个 when,并关联 context 最终得出一个布尔值,这个布尔值其实来之不易,生成的最终其实是一个带布尔值的策略树,这棵树的前后最终节点的目的都是为了求出布尔值,如果是服务端下发的动态配置本质可以是 0 和 1 的策略树即可。
要知道实现一个强大的配置系统还能保证整体的质量和性能确实是很不容易的,上图是在我们实际项目其中的一个改造例子,左边的表达式收集会转化成右边表达式配置,左边所有的 if
会收到配置表里面转嫁给接入方或者可视化配置界面,以后每当变动配置表的信息,就可以配合作用域收集得到全信的策略树来渲染视图或者更新视图。
希望有更多志同道合的人加入我们腾讯文档团队,一起跟腾讯文档和开源社区成长,介绍下我们团队部分开源项目成员。
关于这方面的相关文章不多,一路走来跳了不少的坑,感谢团队成员的支持,并让这个方案落地,后续希望能贡献更多代码回馈开源社区,也希望有更多志同道合的人加入我们腾讯文档团队,一起去探索和遨游,最后也希望这篇文章能给到你们一些启发吧 😁
当我第一次看到这一题目的时候,我是比较震惊的,分析了下很不合我们编程的常理,并认为不大可能,变量a
要在同一情况下要同时等于1,2和3这三个值,这是天方夜谭吧,不亚于哥德巴赫1+1=1
的猜想吧,不过一切皆有可能,出于好奇心,想了许久之后我还是决定尝试解决的办法。
我的思路来源于更早前遇到的另外一题相似的面试题:
// 设置一个函数输出一下的值
f(1) = 1;
f(1)(2) = 2;
f(1)(2)(3) = 6;
当时的解决办法是使用toString
或者valueOf
实现的,那我们先回顾下toString
和valueOf
方法,方便我们更深入去了解这类型的问题:
比如我们有一个对象,在不重写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
欢迎您的关注和交流😁
超文本标记语言(HyperText Markup Language),在浏览器上运行的一种标记语言;
HTML 不是一种编程语言,而是一种标记语言;
标记语言是一套标记标签 ,HTML 使用标记标签来描述网页 。
必须是小写字母开头,后接下划线、数字、字母,不可使用中文或特殊字符作为文件名。
<html>
<b>
和</b>
<br/>、<img/>
(1)<!DOCTYPE>
标签
<!DOCTYPE>
文档类型 | 分类 | 备注 |
---|---|---|
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>
标签
作用:设置页面描述信息,设置页面关键字,设置页面的编码格式
例如:
<meta charset=UTF-8">
Charset(字符集格式)常见:
h系列的标签(Header) | 作用 |
---|---|
h1,h2,h3,h4,h5,h6 | 把页面上的文字加上标题的语义,其中h1定义标题的语义化最重 |
注意:一个页面只能有一个h1标签。
p段落标签 | 作用 |
---|---|
p | 给页面的上一段文字加上段落的语义 |
p段落标签 | 作用 |
---|---|
hr | 在页面显示一条横线,默认占整行。hr 元素可用于分隔内容 |
br段落标签 | 作用 |
---|---|
br | 换行 |
a:超链接,锚链接。
属性 | 属性值 |
---|---|
href | a标签的跳转目标地址 |
target | 设置连接的跳转方式:target设置连接的跳转方式(1)_blank:保留原始页面,再进行跳转(2)_self:在当前页面进行跳转 |
补充:<base target=“blank”>
为页面上所有a标签设置跳转方式(一般放在titile标签下面)
a标签的其它用法:
href=”#”
<p id=”mubiao”>这是目标</p>
<a href="1.zip">下载那个神奇的文件</a>
标签 | 作用 |
---|---|
img | 在页面显示一张图片 |
属性 | 属性值 |
---|---|
src | 图片显示的路径 |
alt | 若图片加载不出来,显示出来文字 |
title | 鼠标上移显示的文字(介绍图片) |
涉及到的路径问题:
第一种:绝对路径
带有盘符的路径:C:\上课内容\上课视频\firstday\源代码\wscat.jpg
缺点:可移植性不强,一般情况下不直接使用绝对路径。
第二种:相对路径
a.如果页面与图片在同一目录下面:
总结:平时开发一般都是用相对路径:因为相对路径的可移植性要强。
(1)b , u , i , s:视觉要素
标签 | 作用 |
---|---|
b(Bold) | 加粗 |
u (Underlined) | 下划线 |
i(Italic) | 倾斜 |
s(Strikethrough) | 删除线 |
没有语义的标签,<i></i>
标签经常会用于不需要语义化的位置,例如放一个小图标
(2)strong,em,del,ins:表达要素
标签 | 作用 |
---|---|
strong | 加粗,加强语气 |
ins | 下划线 |
em | 倾斜 |
del | 删除线 |
(3)sub、sup标签
标签 | 作用 |
---|---|
sub | 定义下标字 |
ins | 定义上标字 |
HTML 输出空格
早期网站开发中,人们喜欢作用表格布局,使用非常的泛滥。到了2008年之后,人们意识到表格的局限性,所以改为用div+css模式。2002.Sina.com.cn、2004.sina.com.cn
作用:用来将数据以表格形式显示出来。
最简单的表格格式:
常用属性 | 作用 |
---|---|
border | 给表格加上了边框 |
width | 给表格设置宽 |
height | 给表格设置高 |
cellspacing | 规定单元格之间的空白 |
cellpadding | 规定单元边沿与其内容之间的空白 |
align:center,left,right | 设置table上,决定表格显示的位置 |
例如 | <table border="1" width="800" height="400" cellspacing="0" cellpadding="0"> |
(1)无序列表(ul):(重点)
标签 | 作用 |
---|---|
ul | 显示一列没有排列顺序的数据 |
注意:
(2)有序列表(ol):(用的很少)
标签 | 作用 |
---|---|
ol | 显示一段有顺序的数据 |
(3)自定义列表(dl)
标签 | 作用 |
---|---|
dl,dt,dd | 显示一段数据,格式自己定义 |
注意:一个dl中可以有多个dt和多个dd。
1.表单标签(<form></form>
)
<form>
...
input 元素
...
</form>
属性:
2. 表单元素(注册页面上能够填写的内容)
作用:用来收集用户的信息,将来提交到服务器。
标签 | 作用 | 属性 |
---|---|---|
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 | 文本域 |
注意:
checked=”checked”
selected="selected"
往网页中输入特殊字符,需在HTML代码中,转变成以&开头的字母组合或以&#开头的数字,浏览器会用HTML命令对这些特殊代码进行翻译。常见的如:
(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属性,使用浮动(后面会讲)
1.搜索引擎(网络爬虫)负责收集页面信息,对我们的页面进行一个归类排序。
2.搜索引擎(百度)是如何对页面进行搜索排序的?
这篇文章会记录我们平时常用到的 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: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 类型的设置 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[href],
input[type="submit"],
input[type="image"],
input[type="button"],
label[for],
select,
button {
cursor: pointer;
}
在访问移动网站时,你会发现,在选中的元素周围会出现一些灰色的框框,使用以下代码屏蔽这种样式
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
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 的盒子模型宽度计算方式不同,统一为最常见的 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;
}
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;
}
.pf {
position: fixed;
/*chrome 内核 浏览器 position: fixed 防止抖动*/
-webkit-transform: translateZ(0);
}
.middle {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
.v-middle {
position: relative;
top: 50%;
-webkit-transform: -webkit-translateY(-50%);
-moz-transform: -moz-translateY(-50%);
-o-transform: -o-translateY(-50%);
transform: translateY(-50%);
}
.bb {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.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 {
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;
}
当我们提前知道要居中元素的长度和宽度时,可以使用这种方式:
.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 来让元素进行偏移。
.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
其实是行高,我们可以用行高来调整布局!
不过这个方案有一个比较大的缺点是:文案必须是单行的,多行的话,设置的行高就会有问题。
.container {
width: 300px;
height: 200px;
border: 1px solid #333333;
}
.content {
line-height: 200px;
}
给容器元素设置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;
}
我们可以给父级元素设置为display: flex
,利用 flex 中的align-items
和justify-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 除了作为简单的绘制边框以外,还可以绘制三角形,梯形,星形等任意的多边形,以下为绘制的两个三角形和梯形
<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
主要用于绘制圆点、圆形、椭圆、圆角矩形等形状,以下为简单绘制的两个图形。
<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: h-shadow v-shadow blur spread color inset
,各个值表示的意义分别为:s 水平方向的偏移,垂直方向的便宜,模糊的距离(羽化值),阴影的大小(不设置或为 0 时阴影与主体的大小一致),阴影的颜色和是否使用内阴影。实际应用时可以接收 3-6 个值,对应分别如下:
同时,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;
}
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-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;
}
.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;
}
如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的肯定是我前进的最大动力😁
<!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>
- | Angular | Vue | React |
---|---|---|---|
维护团队 | 国人 Even You | ||
类型 | 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"))
其实就是类似jQ的字符串拼接,然后再把拼接好的完整的html结构插入到对应的节点,我们就不会再对html结构实现增删查改,以后要重新操作DOM,只要重新修改新的html结构再插入即可
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>
<!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>
排序算法是面试及笔试中必考点,本文通过动画方式演示,通过实例讲解,最后给出JavaScript版的排序算法
现有一组数组 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)
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)
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)
<!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>
昨天除了 NPM 被微软收购的消息外,微软旗下的 Github 也正式发布了 GitHub移动版 ,它是 iOS 和 Android上对 GitHub 网页桌面版的完全体验版。现在,我们可以随时随地在移动设备上与我们的团队保持联系,分类问题,甚至合并代码。
之前开发者对 Beta 的反应令人难以置信,Beta 测试人员仅在过去的几周内就累计评论,和审查合并了超过近十万个拉取请求。自从 Github 首次发布测试版以供下载以来,已经有成千上万的团队互动。如今,适用于移动设备的 GitHub 的 iOS 和 Android 版本除了Beta版之外,正式版已全面上市。
我们可以从以下商店获取并安装该应用:
使用 GitHub 移动版,我们可以随时随地分类:
借助移动版 GitHub,无论我们是在办公桌上还是在旅途中,我们都可以享受到更加无缝的通知体验,现在我们还可以享受最新通知的更新功能。
Google 或 App Store 现已针对个人计划,团队和企业云提供了适用于移动设备的 GitHub。他们正在努力添加功能和 API,以便在今年晚些时候可以支持 Enterprise Server。
如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的肯定是我前进的最大动力😁
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)是一个描述字符模式的对象。
为什么要使用正则表达式
正则表达式能够进行强大的“模式匹配”和“文本检索与替换”功能。前端往往有大量的表单数据校验的工作,采用正则表达式会使得数据校验的工作量大大减轻
第一个参数就是我们的模式“字符串”
var reg= new RegExp('study');
//使用特殊字符
var reg= new RegExp('\d\w+');\d\w+
第二个参数可选,模式修饰符
var reg = new RegExp('study', 'ig');
var reg = /study/gi;
'a,b ,c , d, e'.split(/\s*,\s* /);
/xx/.exec(字符串)
所有字母和数字都是按照字面量进行匹配,和字符串匹配等效
/good/gi
字符类(只记小写字母即可)
PS:以上所有字符类都只是匹配“一个”字符
特殊符号
^ $ . * + ? = ! : | \ / () [] {}
[]: 代表任意“单个字符” ,里面的内容表示“或”的关系
(): 表示分组(n是以最左边括号出现的顺序排列)
PS: 编写的正则分组数量越少越好
|: 表示或者
锚点定位
表示数量,对前一个字符计数,
\d{5}: 匹配5个数字
\d{5,10}: 匹配5个到10个数字
\d{5,}: 匹配5个或5个以上的数字
PS:
1)数量词*,+,{5,},会尽可能多的去匹配结果(贪婪)
2)在后面加一个?表示尽可能少的去匹配结果(非贪婪)
google,goooogle ==> /go+/
[案例]
[练习]
Unicode编码中的汉字范围 /^[\u2E80-\u9FFF]+$/
445655 19900707 2165
445655 19900707 211x
1999/05/08
1999-5-8
19990508
[作业]
//正则
//字面量
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));
</2016><2017>
这个网站给我们提供了很多的配色方案,我们直接使用就OK了。使用方法也很简单,鼠标移动到对应的颜色上,我们就可以看到颜色的十六进制码,复制这个颜色到工具里就可以使用了。
这个是Adobe公司出的,他提供了多种配色方案。我们点击圆盘中间的点,就可以调整出我们想要的配色方案。
这是一个提取现有图片配色方案的工具。我们上传一张图片,它就会帮我们把图片的配色提取出来供我们使用。
这个网站是为WEB设计,开发中经常用到的安全色。网站内列出了颜色的十六进制码和RGB码,复制粘贴就可以了。
这是一个在线RGB和十六进制颜色码转换工具。在对应的位置填入十六进制代码,点击转换,我们就可以获取到RGB颜色的代码了。
这是阿里巴巴旗下的图标库网站,直接搜索关键词就可以找到大批的图标。下载图标的时候我们还可以选择颜色、大小、格式,根据自己的需要下载就好了。
这也是一个非常有名的图标库,与上面那个不同的是,这里的图标不是单一颜色的,而是设计好的颜色。下载图标也很简单,直接点击对应图标上面的格式就可以下载。
奥森图标(Font Awesome)提供丰富的矢量字体图标—通过CSS可以任意控制所有图标的大小 ,颜色,阴影。
这个网站分享jQuery插件和提供各种jQuery特效的详细使用方法,在线预览,jQuery插件下载及教程
H-ui前端框架,一个轻量级前端框架,简单免费,兼容性好,服务**网站。
Unsplash是一个分享免费高质量照片的网站,照片分辨率都挺大,而且都是真实的摄影师作品,图片多是风景和静物。
PS字体库,包含了几乎所有类型的字体,下载好安装,PS中就可以使用了。
关键代码拆解成如下图所示(无关部分已省略):
起初我认为可能是这个 getRowDataItemNumberFormat
函数里面某些方法执行太慢,从 formatData.replace
到 unescape
(已废弃,官方建议使用 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
此时应该是会报错的,因为 c
是 undefined
,这个错误会被 try catch
捕捉到,而上面的代码耗时出现了巨大的变化,上升到 40 ms,相差了将近 200 倍!并且上述代码和首图的 getRowDataItemNumberFormat 函数代码均出现了 Minor GC
,注意这个 Minor GC
也是会耗时的。
这可以解释一部分原因了,我们上面运行的代码是一个性能比较关键的部分,不应该使用 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
应该通过检查属性或使用其他适当的单元测试来完全避免使用此构造,因为这些构造会极大地影响性能,因此应尽量减少使用它们。
如果一个函数被重复调用,或者一个循环被重复求值,那么最好避免其中包含这些构造。它们最适合仅执行一次或仅执行几次且不在性能关键代码内执行的代码。尽可能将它们与其他代码隔离,以免影响其性能。
例如,可以将它们放在顶级函数中,或者运行它们一次并存储结果,这样你以后就可以再次使用结果而不必重新运行代码。
getRowDataItemNumberFormat
在经过上述思路改造后,运行效率得到了质的提升,在实测 300 多次循环中减少的时间如下图,足足优化了将近 2s 多的时间,如果是 3000 次的循环,那么它的优化比例会更高:
由于上面的代码是从项目中改造出来演示的,可能并不够直观,所以我重新写了另外一个相似的例子,代码如下,这里面的逻辑和上面的 getRowDataItemNumberFormat
函数讲道理是一致的,但是我让其发生错误的时候进入 catch
逻辑执行任务。
事实上 plus1
和 plus2
函数的代码逻辑是一致的,只有代码语义是不相同,一个是返回 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
这会在我们维护和检查代码的时候提升不必要的成本,有可能分散并浪费我们的注意力。
当我们预感代码片段有可能出错,更应该是集中注意力去处理 success
和 error
的场景,而非使用 try catch
来保护我们的代码,更多时候 try catch
反而会让我们忽略了代码存在的致命问题。
if (container) container.innerHTML = "I'm alloyteam";
else // todo
在简单代码中应当减少甚至不用 try catch
,我们可以优先考虑 if else
代替,在某些复杂不可测的代码中也应该减少 try catch
(比如异步代码),我们看过很多 async
和 await
的示例代码都是结合 try catch
的,在很多性能场景下我认为它并不合理,个人觉得下面的写法应该是更干净,整洁和高效的。
因为 JavaScript
是事件驱动的,虽然一个错误不会停止整个脚本,但如果发生任何错误,它都会出错,捕获和处理该错误几乎没有任何好处,代码主要部分中的 try catch
代码块是无法捕获事件回调中发生的错误。
通常更合理的做法是在回调方法通过第一个参数传递错误信息,或者考虑使用 Promise
的 reject()
来进行处理,也可以参考 node
中的常见写法如下:
;(async () => {
const [err, data] = await readFile();
if (err) {
// todo
};
})()
fs.readFile('<directory>', (err, data) => {
if (err) {
// todo
}
});
结合了上面的一些分析,我自己做出一些浅显的总结:
try catch
来捕获异常。try catch
,确保异常路径在需要考虑性能情况下优先考虑 if else
,不考虑性能情况请君随意,而异步可以考虑回调函数返回 error
信息对其处理或者使用 Promse.reject()
。try catch
使用,也不要用它来保护我们的代码,其可读性和可维护性都不高,当你期望代码是异常时候,不满足上述1,2的情景时候可考虑使用。最后,笔者希望这篇文章能给到你我一些方向和启发吧,如有疏漏不妥之处,还请不吝赐教!
// 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()
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)
// 判断arr中的数是否都大于10
var res = arr.every(function(item) {
return item > 10;
});
console.log(res);
// 只要数组中有一项符合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'));
/*
取出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;
}
}
<!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>
以前我们都是使用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);
数组+字符串
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);
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.