JavaScript数据结构和算法

转自:https://github.com/zoro-web/blog

前言

在过去的几年中,得益于Node.js的兴起,JavaScript越来越广泛地用于服务器端编程。鉴于JavaScript语言已经走出了浏览器,程序员发现他们需要更多传统语言(比如C++和Java)提供的工具。这些工具包括传统的数据结构(如链表,栈,队列,图等),也包括传统的排序和查找算法。本文主要是总结什么情况下使用何种数据结构较好,并没有细讲里面的原理和实现方式,仅仅提供给阅读过《数据结构和算法》的同学作为总结和参考笔记,如果未细究过数据结构和算法的同学,本文也可以作为一个方向,希望能引导你去深究数据结构和算法。

为什么要学习数据结构和算法

数据结构和算法对于很多前端工程师来说,一直觉得是可有可无的,但其实不然,个人觉得,前端工程师其实是最需要重视数据结构和算法的人,因为前端所做的东西是用户访问网站第一眼看到的东西,特别在移动浪潮到来之后,对用户体验越来越高,对前端提出了更高的要求,面对越来越复杂的产品,需要坚实的数据结构和算法基础才能驾驭。
如果没有学习过计算机科学的程序员,当我们在处理一些问题时,比较熟悉的数据结构就是数组,数组无疑是一个很好的选择。但很多时候,对于很多复杂的问题,数组就显得太过简陋了,当学习了数据结构和算法之后,对于很多编程问题,当想到一个合适的数据结构后,设计和实现解决这些问题的算法就手到擒来。

相关知识点——数据结构、排序算法和查找算法

相关讲解细分:
数据结构:列表、栈、队列、链表、字典、散列、图和二叉查找树
排序算法:冒牌排序、选择排序、插入排序、希尔排序、归并排序和快速排序
查找算法:顺序查找和二分查找

列表

在日常生活中,人们经常使用列表:待办事项列表、购物清单、最佳十名榜单等等。而计算机程序也在使用列表,在下面的条件下,选择列表作为数据结构就显得尤为有用:

  • 数据结构较为简单
  • 不需要在一个长序列中查找元素,或者对其进行排序

反之,如果数据结构非常复杂,列表的作用就没有那么大了。

栈是一种特殊的列表,栈内的元素只能通过列表的一端访问,这一端称为栈顶。想象一下,我们平常在饭馆见到的一摞盘子就是现实世界常见的栈的例子,只能从最上面取盘子,盘子洗干净后,也只能放在最上面。栈被称为一种后入先出的数据结构。是一种高效的数据结构,因为数据只能在栈顶添加或删除,所以这样的操作很快。
使用条件:

  • 只要数据的保存满足后入先出或先进后出的原理,都优先考虑使用栈

images

队列

队列也是一种列表,不同的是队列只能在队尾插入元素,在队首删除元素。想象一下,我们在银行排队,排在最前面的人第一个办理业务,而后面来的人只能排在队伍的后面,直到轮到他们为止。
使用条件:

  • 只要数据的保存满足先进先出、后入后出的原理,都优先考虑使用队列

常见应用场景:

  • 队列主要用在和时间有关的地方,特别是操作系统中,队列是实现多任务的重要机制
  • 消息机制可以通过队列来实现,进程调度也是使用队列来实现

images

链表

链表也是一种列表,为什么需要出现链表,JavaScript中数组的主要问题时,它们被实现成了对象,与其他语言(比如C++和Java)的数组相对,效率很低。如果你发现数组在实际使用时很慢,就可以考虑使用链表来代替它。
使用条件:

  • 链表几乎可以用在任何可以使用一维数组的情况中。如果需要随机访问,数组仍然是更好的选择。

images

字典

字典是一种以键-值对行驶存储数据的数据结构,JavaScript中的Object类就是以字典的形式设计的。JavaScript可以通过实现字典类,让这种字典类型的对象使用起来更加简单,字典可以实现对象拥有的常见功能,并相应拓展自己想要的功能,而对象在JavaScript编写中随处可见,所以字典的作用也异常明显了。

散列

散列(也称为哈希表)是一种的常用的数组存储技术,散列后的数组可以快速地插入或取用。散列使用的数据结构叫做散列表。在散列表上插入、删除和取用数据都非常快,但对于查找操作来说却效率低下,比如查找一组数组中的最大值和最小值。这些操作需要求助于其他数据结构,比如下面介绍的二叉查找树。

散列表在JavaScript中可以基础数组去进行设计。数组的长度是预先设定的,所有元素根据和该元素对应的键,保存在数组的特定位置,这里的键和对象的键是类型的概念。使用散列表存储数组时,通过一个散列函数将键映射为一个数字,这个数字的范围是0到散列表的长度。

即使使用一个高效的散列函数,依然存在将两个键映射为同一个值得可能,这种现象叫做碰撞。常见碰撞的处理方法有:开链法和线性探测法(具体概念有兴趣的可以网上自信了解)

使用条件:

  • 可以用于数据的插入、删除和取用,不适用于查找数据

images

图由边的集合及顶点的集合组成。地图是我们身边很常见的现实场景,比如每两个城镇都由某种道路相连。上面的每个城镇可以看作一个顶点,连接城镇的道路便是边。边由顶点对(v1, v2)定义,v1和v2分别是图中的两个顶点。顶点也有权重,也成为成本。如果一个图的顶点对是有序的,则称之为有向图(例如常见的流程图),反之,称之为无序图。
使用场景(用图对现实中的系统建模):

  • 交通系统,可以用顶点表示街道的十字路口,边可以表示街道。加权的边可以表示限速或者车道的数量。可以用该系统判断最佳路线及最有可能堵车的街道。
  • 任何运输系统都可以用图来建模。比如,航空公司可以用图来为其飞行系统建模。将每个机场看成顶点,将经过两个顶点的每条航线看作一条边。加权的边可以表示从一个机场到另一个机场的航班成本,或两个机场间的距离,这取决于建模的对象是什么。

搜索图的算法主要有两种: 深度优先搜索和广度优先搜索。

二叉树和二叉查找树

树是计算机科学中经常用到的一种数据结构。树是一种非线性的数据结构,以分层的方式存储数据。
二叉树每个节点的子节点不允许超过两个。一个父节点的两个子节点分别称为左节点和右节点,通过将子节点的个数限定为2,可以写出高效的程序在树中插入、查找和删除数据。
二叉查找树(BST)是一种特殊的二叉树,相对较小的值保存在左节点中,较大的值保存在右节点中。这一特性使得查找的效率很高,对于数值型和非数值型的数据,比如单词和字符串,都是如此。
二叉查找树实现方法

function Node(data, left, right) { // 创建节点
  this.data = data;
  this.left = left;
  this.right = right;
  this.show = show
}

function show () { // 显示树的数据
  return this.data
}

function BST () { // 二叉查找树类
  this.root = null;
  this.insert = insert;
  this.inOrder = inOrder; // inOrder是遍历BST的方式
}

function insert (data) { // 向树中插入数据
  var n = new Node(data, null, null)
  if (this.root == null) {
    this.root = n;
  } else {
    var current = this.root;
    var parent;
    while (true) {
	  parent = current
	  if (data < current.data) {
		current = current.left;
		if (current == null) {
		  parent.left = n;
		  break;
		}
	  } else {
		current = current.right;
		if (current == null) {
		  parent.right = n;
		  break;
		}
	  }
    }
  }
}

images
遍历BST的方式有三种:中序遍历(以升序访问树中所有节点,先访问左节点,再访问根节点,最后访问右节点)、先序遍历(先访问根节点,再以同样的方式访问左节点和右节点)、后序遍历(先访问叶子节点,从左子树到右子树,再到根节点)

排序算法

基本排序算法

基本排序算法,其核心思想是指对一组数组按照一定的顺序重新排列。重新排列时用到的技术是一组嵌套的for循环。其中外循环会遍历数组的每一项,内循环则用于比较元素。

冒泡排序

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

function bubbleSort (arr) {
	var i = arr.length;
	while (i > 0) {
		var pos = 0
		for (var j = 0; j < i; j++) {
			if (arr[j] > arr[j+1]){
				pos = j
				var temp = arr[j]
				arr[j] = arr[j+1]
				arr[j+1] = temp
			}
		}
		i = pos
	}
	return arr
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(bubbleSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

images

选择排序

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

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[minIndex]
		arr[minIndex] = arr[i]
		arr[i] = temp
	}
	return arr
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(selectionSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

images

插入排序

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

function insertSort (arr) {
	var len = arr.length
	for (i = 1; i < len; i++) {
		var key = arr[i]
		var j = i - 1
		while (j >= 0 && arr[j] > key) {
			arr[j+1] = arr[j]
			j--;
		}
		arr[j+1] = key
	}
	return arr
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(insertSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

images

高级排序算法

高级数据排序算法,通常用于处理大型数据集的最高效排序算法,它们处理的数据集可以达到上百万个元素,而不仅仅是几百个或者几千个,下面我们将介绍希尔排序、归并排序和快速排序。

希尔排序

1959年Shell发明,第一个突破O(n^2)的排序算法;是简单插入排序的改进版;它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。

function shellSort (arr) {
	var len = arr.length;
	var temp, gap = 1;
	while (gap < len /3 ) {
		gap = gap * 3 + 1
	}
	while (gap >= 1) {
		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
		}
		gap = (gap - 1) / 3
	}
	return arr
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(shellSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

5555

归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

function mergeSort (arr) {
	var len = arr.length
	if (len < 2) {
		return arr
	}
	var middle = Math.floor(len / 2)
	var left = arr.slice(0, middle)
	var 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
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(mergeSort(arr));

images

快速排序

快速排序是处理 大数据集最快的排序算法之一。它是一种分而治之的算法,通过递归的方法将数据依次分解为包含较小元素和较大元素的不同子序列。该算法不断重复这个步骤知道所有数据都是有序的。
这个算法首先要在列表中选择一个元素作为基准值。数据排序围绕基准值进行,将列表中小于基准值的元素移到数组的底部,将大于基准值的元素移到数组的顶部。

function qSort (arr) {
	if (arr.length == 0) {
		return []
	}
	var left = []
	var right = []
	var pivot = arr[0]
	for (var i = 1; i < arr.length; i++) {
		if (arr[i] < pivot) {
			left.push(arr[i])
		} else {
			right.push(arr[i])
		}
	}
	return qSort(left).concat(pivot, qSort(right))
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(qSort(arr));

images

检索算法

在列表中查找数据有两种方式:顺序查找和二分查找。顺序查找适用于元素随机排列的列表;二分查找适用于元素已排序的列表。二分查找效率更高,但是必须在进行查找之前花费额外的时间将列表中的元素排序。

顺序查找

对于查找数据,最简单的方法就是从列表的第一个元素开始对列表元素逐个进行判断,直到找到了想要的结果,或者直到列表结尾也没有找到。这种方法称为顺序查找,有时也被称为线性查找。

function seqSearch (arr, data) {
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] == data) {
      return i;
    }
  }
  return -1;
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(seqSearch(arr, 15))

二分查找

二分法查找,也称折半查找,是一种在有序数组中查找特定元素的搜索算法。查找过程可以分为以下步骤:

  • 首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素(即要查找的元素),则搜索过程结束,否则进行下一步。
  • 如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。
  • 如果某一步数组为空,则表示找不到目标元素。
function binSearch (arr, data) {
	var low = 0;
	var high = arr.length - 1
	while (low <= high) {
		var middle = Math.floor((low + high) / 2)
		if (arr[middle] < data) {
			low = middle + 1
		} else if (arr[middle] > data) {
			high = middle - 1
		} else {
			return middle
		}
	}
	return -1
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(binSearch(arr, 15))

最后

  • 非常谢谢观看,看到这里相信很不容易,毕竟相对枯燥的知识点很多,但却是必不可少的。希望这篇文章能让你对数据结构和算法有一个新的认识,或者产生一些新的想法,那么写这篇文章的意义就达到了,当然发现文章写得有问题的,也非常欢迎指出,一起共同成长。
  • 欢迎关注我的github—-https://github.com/zoro-web/blog,你的关注是我整理知识的更大动力,我的博客会定期整理发布一些文章。

你有必要知道的25个JavaScript面试题

原文地址:http://www.jb51.net/article/77140.htm

 

1、使用 typeof bar === “object” 判断 bar 是不是一个对象有神马潜在的弊端?如何避免这种弊端?

使用 typeof 的弊端是显而易见的(这种弊端同使用 instanceof):

1
2
3
4
5
6
let obj = {};
let arr = [];
console.log(typeof obj === ‘object’); //true
console.log(typeof arr === ‘object’); //true
console.log(typeof null === ‘object’); //true

从上面的输出结果可知,typeof bar === “object” 并不能准确判断 bar 就是一个 Object。可以通过 Object.prototype.toString.call(bar) === “[object Object]” 来避免这种弊端:

1
2
3
4
5
6
let obj = {};
let arr = [];
console.log(Object.prototype.toString.call(obj)); //[object Object]
console.log(Object.prototype.toString.call(arr)); //[object Array]
console.log(Object.prototype.toString.call(null)); //[object Null]

另外,为了珍爱生命,请远离 ==:

而 [] === false 是返回 false 的。

2、下面的代码会在 console 输出神马?为什么?

1
2
3
4
5
6
(function(){
 var a = b = 3;
})();
console.log(“a defined? ” + (typeof a !== ‘undefined’));
console.log(“b defined? ” + (typeof b !== ‘undefined’));

这跟变量作用域有关,输出换成下面的:

1
2
console.log(b); //3
console,log(typeof a); //undefined

拆解一下自执行函数中的变量赋值:

b = 3;
var a = b;

所以 b 成了全局变量,而 a 是自执行函数的一个局部变量。

3、下面的代码会在 console 输出神马?为什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
var myObject = {
 foo: “bar”,
 func: function() {
 var self = this;
 console.log(“outer func: this.foo = ” + this.foo);
 console.log(“outer func: self.foo = ” + self.foo);
 (function() {
 console.log(“inner func: this.foo = ” + this.foo);
 console.log(“inner func: self.foo = ” + self.foo);
 }());
 }
};
myObject.func();

第一个和第二个的输出不难判断,在 ES6 之前,JavaScript 只有函数作用域,所以 func 中的 IIFE 有自己的独立作用域,并且它能访问到外部作用域中的 self,所以第三个输出会报错,因为 this 在可访问到的作用域内是 undefined,第四个输出是 bar。如果你知道闭包,也很容易解决的:

1
2
3
4
(function(test) {
 console.log(“inner func: this.foo = ” + test.foo); //’bar’
 console.log(“inner func: self.foo = ” + self.foo);
}(self));

如果对闭包不熟悉,可以参考本文:从作用域链谈闭包

4、将 JavaScript 代码包含在一个函数块中有神马意思呢?为什么要这么做?

换句话说,为什么要用立即执行函数表达式(Immediately-Invoked Function Expression)。

IIFE 有两个比较经典的使用场景,一是类似于在循环中定时输出数据项,二是类似于 JQuery/Node 的插件和模块开发。

1
2
3
4
5
for(var i = 0; i < 5; i++) {
 setTimeout(function() {
 console.log(i);
 }, 1000);
}

上面的输出并不是你以为的0,1,2,3,4,而输出的全部是5,这时 IIFE 就能有用了:

1
2
3
4
5
6
7
for(var i = 0; i < 5; i++) {
 (function(i) {
 setTimeout(function() {
 console.log(i);
 }, 1000);
 })(i)
}

而在 JQuery/Node 的插件和模块开发中,为避免变量污染,也是一个大大的 IIFE:

1
2
3
(function($) {
 //代码
 } )(jQuery);

5、在严格模式(‘use strict’)下进行 JavaScript 开发有神马好处?

消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
消除代码运行的一些不安全之处,保证代码运行的安全;
提高编译器效率,增加运行速度;
为未来新版本的Javascript做好铺垫。
6、下面两个函数的返回值是一样的吗?为什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo1()
{
 return {
 bar: “hello”
 };
}
function foo2()
{
 return
 {
 bar: “hello”
 };
}

在编程语言中,基本都是使用分号(;)将语句分隔开,这可以增加代码的可读性和整洁性。而在JS中,如若语句各占独立一行,通常可以省略语句间的分号(;),JS 解析器会根据能否正常编译来决定是否自动填充分号:

1
2
3
var test = 1 +
2
console.log(test); //3

在上述情况下,为了正确解析代码,就不会自动填充分号了,但是对于 return 、break、continue 等语句,如果后面紧跟换行,解析器一定会自动在后面填充分号(;),所以上面的第二个函数就变成了这样:

1
2
3
4
5
6
7
function foo2()
{
 return;
 {
 bar: “hello”
 };
}

所以第二个函数是返回 undefined。

7、神马是 NaN,它的类型是神马?怎么测试一个值是否等于 NaN?

NaN 是 Not a Number 的缩写,JavaScript 的一种特殊数值,其类型是 Number,可以通过 isNaN(param) 来判断一个值是否是 NaN:

1
2
3
4
5
6
7
8
9
console.log(isNaN(NaN)); //true
console.log(isNaN(23)); //false
console.log(isNaN(‘ds’)); //true
console.log(isNaN(‘32131sdasd’)); //true
console.log(NaN === NaN); //false
console.log(NaN === undefined); //false
console.log(undefined === undefined); //false
console.log(typeof NaN); //number
console.log(Object.prototype.toString.call(NaN)); //[object Number]

ES6 中,isNaN() 成为了 Number 的静态方法:Number.isNaN().

8、解释一下下面代码的输出

1
2
console.log(0.1 + 0.2); //0.30000000000000004
console.log(0.1 + 0.2 == 0.3); //false

JavaScript 中的 number 类型就是浮点型,JavaScript 中的浮点数采用IEEE-754 格式的规定,这是一种二进制表示法,可以精确地表示分数,比如1/2,1/8,1/1024,每个浮点数占64位。但是,二进制浮点数表示法并不能精确的表示类似0.1这样 的简单的数字,会有舍入误差。

由于采用二进制,JavaScript 也不能有限表示 1/10、1/2 等这样的分数。在二进制中,1/10(0.1)被表示为 0.00110011001100110011…… 注意 0011 是无限重复的,这是舍入误差造成的,所以对于 0.1 + 0.2 这样的运算,操作数会先被转成二进制,然后再计算:

0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)
双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100110011001100110011001100110011001100…因浮点数小数位的限制而截断的二进制数字,这时候,再把它转换为十进制,就成了 0.30000000000000004。

对于保证浮点数计算的正确性,有两种常见方式。

一是先升幂再降幂:

1
2
3
4
5
6
7
8
9
10
function add(num1, num2){
 let r1, r2, m;
 r1 = (”+num1).split(‘.’)[1].length;
 r2 = (”+num2).split(‘.’)[1].length;
 m = Math.pow(10,Math.max(r1,r2));
 return (num1 * m + num2 * m) / m;
}
console.log(add(0.1,0.2)); //0.3
console.log(add(0.15,0.2256)); //0.3756

二是是使用内置的 toPrecision() 和 toFixed() 方法,注意,方法的返回值字符串。

1
2
3
4
function add(x, y) {
 return x.toPrecision() + y.toPrecision()
}
console.log(add(0.1,0.2)); //”0.10.2″

9、实现函数 isInteger(x) 来判断 x 是否是整数

可以将 x 转换成10进制,判断和本身是不是相等即可:

1
2
3
function isInteger(x) {
 return parseInt(x, 10) === x;
}

ES6 对数值进行了扩展,提供了静态方法 isInteger() 来判断参数是否是整数:

1
2
3
4
5
Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false
Number.isInteger(“15”) // false
Number.isInteger(true) // false

JavaScript能够准确表示的整数范围在 -2^53 到 2^53 之间(不含两个端点),超过这个范围,无法精确表示这个值。ES6 引入了Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限,并提供了 Number.isSafeInteger() 来判断整数是否是安全型整数。

10、在下面的代码中,数字 1-4 会以什么顺序输出?为什么会这样输出?

1
2
3
4
5
6
(function() {
 console.log(1);
 setTimeout(function(){console.log(2)}, 1000);
 setTimeout(function(){console.log(3)}, 0);
 console.log(4);
})();

这个就不多解释了,主要是 JavaScript 的定时机制和时间循环,不要忘了,JavaScript 是单线程的。详解可以参考 从setTimeout谈JavaScript运行机制。

11、写一个少于 80 字符的函数,判断一个字符串是不是回文字符串

1
2
3
4
function isPalindrome(str) {
 str = str.replace(/\W/g, ”).toLowerCase();
 return (str == str.split(”).reverse().join(”));
}

这个题我在 codewars 上碰到过,并收录了一些不错的解决方式,可以戳这里:Palindrome For Your Dome

12、写一个按照下面方式调用都能正常工作的 sum 方法

1
2
console.log(sum(2,3)); // Outputs 5
console.log(sum(2)(3)); // Outputs 5

针对这个题,可以判断参数个数来实现:

1
2
3
4
5
6
7
8
9
10
11
function sum() {
 var fir = arguments[0];
 if(arguments.length === 2) {
 return arguments[0] + arguments[1]
 } else {
 return function(sec) {
 return fir + sec;
 }
 }
}

13、根据下面的代码片段回答后面的问题

1
2
3
4
5
6
for (var i = 0; i < 5; i++) {
 var btn = document.createElement(‘button’);
 btn.appendChild(document.createTextNode(‘Button ‘ + i));
 btn.addEventListener(‘click’, function(){ console.log(i); });
 document.body.appendChild(btn);
}

1、点击 Button 4,会在控制台输出什么?

2、给出一种符合预期的实现方式

  • 1、点击5个按钮中的任意一个,都是输出5
  • 2、参考 IIFE。

14、下面的代码会输出什么?为什么?

1
2
3
4
5
6
var arr1 = “john”.split(”); j o h n
var arr2 = arr1.reverse(); n h o j
var arr3 = “jones”.split(”); j o n e s
arr2.push(arr3);
console.log(“array 1: length=” + arr1.length + ” last=” + arr1.slice(-1));
console.log(“array 2: length=” + arr2.length + ” last=” + arr2.slice(-1));

会输出什么呢?你运行下就知道了,可能会在你的意料之外。

reverse() 会改变数组本身,并返回原数组的引用。

slice 的用法请参考:slice

15、下面的代码会输出什么?为什么?

1
2
3
4
5
6
console.log(1 + “2” + “2”);
console.log(1 + +”2″ + “2”);
console.log(1 + -“1” + “2”);
console.log(+”1″ + “1” + “2”);
console.log( “A” – “B” + “2”);
console.log( “A” – “B” + 2);

输出什么,自己去运行吧,需要注意三个点:

多个数字和数字字符串混合运算时,跟操作数的位置有关

1
2
console.log(2 + 1 + ‘3’); / /‘33′
console.log(‘3′ + 2 + 1); //’321’

数字字符串之前存在数字中的正负号(+/-)时,会被转换成数字

1
2
console.log(typeof ‘3’); // string
console.log(typeof +’3′); //number

同样,可以在数字前添加 ”,将数字转为字符串

1
2
console.log(typeof 3); // number
console.log(typeof (”+3)); //string

对于运算结果不能转换成数字的,将返回 NaN

1
2
console.log(‘a’ * ‘sd’); //NaN
console.log(‘A’ – ‘B’); // NaN

这张图是运算转换的规则

16、

如果 list 很大,下面的这段递归代码会造成堆栈溢出。如果在不改变递归模式的前提下修善这段代码?

1
2
3
4
5
6
7
8
9
10
var list = readHugeList();
var nextListItem = function() {
 var item = list.pop();
 if (item) {
 // process the list item…
 nextListItem();
 }
};

原文上的解决方式是加个定时器:

1
2
3
4
5
6
7
8
9
10
var list = readHugeList();
var nextListItem = function() {
 var item = list.pop();
 if (item) {
 // process the list item…
 setTimeout( nextListItem, 0);
 }
};

解决方式的原理请参考第10题。

17、什么是闭包?举例说明

可以参考此篇:从作用域链谈闭包

18、下面的代码会输出什么?为啥?

1
2
3
for (var i = 0; i < 5; i++) {
 setTimeout(function() { console.log(i); }, i * 1000 );
}

请往前面翻,参考第4题,解决方式已经在上面了

19、解释下列代码的输出

1
2
3
4
console.log(“0 || 1 = “+(0 || 1));
console.log(“1 || 2 = “+(1 || 2));
console.log(“0 && 1 = “+(0 && 1));
console.log(“1 && 2 = “+(1 && 2));

逻辑与和逻辑或运算符会返回一个值,并且二者都是短路运算符:

逻辑与返回第一个是 false 的操作数 或者 最后一个是 true的操作数

1
2
3
console.log(1 && 2 && 0); //0
console.log(1 && 0 && 1); //0
console.log(1 && 2 && 3); //3

如果某个操作数为 false,则该操作数之后的操作数都不会被计算

逻辑或返回第一个是 true 的操作数 或者 最后一个是 false的操作数

1
2
3
console.log(1 || 2 || 0); //1
console.log(0 || 2 || 1); //2
console.log(0 || 0 || false); //false

如果某个操作数为 true,则该操作数之后的操作数都不会被计算

如果逻辑与和逻辑或作混合运算,则逻辑与的优先级高:

1
2
3
console.log(1 && 2 || 0); //2
console.log(0 || 2 && 1); //1
console.log(0 && 2 || 1); //1

在 JavaScript,常见的 false 值:

0, ‘0’, +0, -0, false, ”,null,undefined,null,NaN
要注意空数组([])和空对象({}):

1
2
3
4
console.log([] == false) //true
console.log({} == false) //false
console.log(Boolean([])) //true
console.log(Boolean({})) //true

所以在 if 中,[] 和 {} 都表现为 true:

20、解释下面代码的输出

1
2
console.log(false == ‘0’)
console.log(false === ‘0’)

请参考前面第14题运算符转换规则的图。

21、解释下面代码的输出

1
2
3
4
5
6
7
8
var a={},
 b={key:’b’},
 c={key:’c’};
a[b]=123;
a[c]=456;
console.log(a[b]);

输出是456。

22、解释下面代码的输出

1
console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(10));

结果是10的阶乘。这是一个递归调用,为了简化,我初始化 n=5,则调用链和返回链如下:

23、解释下面代码的输出

1
2
3
4
5
(function(x) {
 return (function(y) {
 console.log(x);
 })(2)
})(1);

输出1,闭包能够访问外部作用域的变量或参数。

24、解释下面代码的输出,并修复存在的问题

1
2
3
4
5
6
7
8
9
10
11
var hero = {
 _name: ‘John Doe’,
 getSecretIdentity: function (){
 return this._name;
 }
};
var stoleSecretIdentity = hero.getSecretIdentity;
console.log(stoleSecretIdentity());
console.log(hero.getSecretIdentity());

将 getSecretIdentity 赋给 stoleSecretIdentity,等价于定义了 stoleSecretIdentity 函数:

1
2
3
4
var stoleSecretIdentity = function (){
 return this._name;
}
stoleSecretIdentity

的上下文是全局环境,所以第一个输出 undefined。若要输出 John Doe,则要通过 call 、apply 和 bind 等方式改变 stoleSecretIdentity 的this 指向(hero)。

第二个是调用对象的方法,输出 John Doe。

25、给你一个 DOM 元素,创建一个能访问该元素所有子元素的函数,并且要将每个子元素传递给指定的回调函数。

函数接受两个参数:

  • DOM
  • 指定的回调函数

原文利用 深度优先搜索(Depth-First-Search) 给了一个实现:

1
2
3
4
5
6
7
function Traverse(p_element,p_callback) {
 p_callback(p_element);
 var list = p_element.children;
 for (var i = 0; i < list.length; i++) {
 Traverse(list[i],p_callback); // recursive call
 }
}

以上就是为大家分享的25个JavaScript面试题,希望对大家参加面试有所帮助。

程序人生

IT人表示屁股上还得纹一个</body> , 要不中间来个hello world!

真正的程序员喜欢兼卖爆米花,他们利用CPU散发出的热量做爆米花,可以根据米花
爆裂的速度听出正在运行什么程序。

十年生死两茫茫,写程序,到天亮。
千行代码,Bug何处藏。
纵使上线又怎样,朝令改,夕断肠。
领导每天新想法,天天改,日日忙。
相顾无言,惟有泪千行。
每晚灯火阑珊处,夜难寐,又加班。

老婆给当程序员的老公打电话:“下班顺路买三个包子带回来,如果看到卖西瓜的,买一个。”
当晚,程序员老公手捧一个包子进了家门。。。
老婆怒道:“你怎么就买了一个包子?!”
老公答曰:“因为看到了卖西瓜的。”

程序员爱情观:爱情就是死循环,一旦执行就陷进去了;
爱上一个人,就是内存泄漏–你永远释放不了;
真正爱上一个人的时候,那就是常量限定,永远不会改变;
女朋友就是私有变量,只有我这个类才能调用;
情人就是指针用的时候一定要注意,要不然就带来巨大的灾难。

某程序员对书法十分感兴趣,退休后决定在这方面有所建树。
于是花重金购买了上等的文房四宝。
一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风范,又具颜真卿气势,
定神片刻,泼墨挥毫,郑重地写下一行字:hello world.

一个程序员在海滨游泳时溺水身亡。
当时海滩上有许多救生员,救生员们只听见有人大声喊“F1!”“F1!”,谁都不知道“F1”究竟是什么意思。

你能让一个程序员一天到晚呆在淋浴房里吗?
给他一瓶洗发香波,上面写着:
for(;;)
{
涂抹香波;
温水冲洗;
}

打,打,打劫
一女黑夜遇抢劫,颤抖曰:“大哥,我是搞java的,两个月没发工资了,还刚被裁员,你看报道就知道了,真的没有钱… …”
劫匪听后竟然痛哭流涕,“妹子,同行,俺原来是做C++的,金融危机闹得做劫匪也
不踏实,你拿好工牌,后面那帮抢劫是做.NET的,你放心,我们绝不抢自己人。”
“对了,边上那条路不要走,那边是搞PHP的… …”

有一个物理学家,工程师和一个程序员驾驶着一辆汽车行驶在阿尔卑斯山脉上,在下山的时候,
忽然,汽车的刹车失灵了,汽车无法控制地向下冲去,眼看前面就是一个悬崖峭壁,
但是很幸运的是在这个悬崖的前面有一些小树让他们的汽车停了下来,而没有掉下
山去。三个惊魂未定地从车里爬了出来。
物理学家说,“我觉得我们应该建立一个模型来模拟在下山过程中刹车片在高温情况下失灵的情形”。
工程师说,“我在车的后备厢来有个扳手,要不我们把车拆开看看到底是什么原因”。
程序员说,“为什么我们不找个相同的车再来一次以重现这个问题呢?”

有一个小伙子在一个办公大楼的门口抽着烟,一个妇女路过他身边,并对他说,
“你知道不知道这个东西会危害你的健康?我是说,你有没有注意到香烟盒上的那个警告(Warning)?”
小伙子说,“没事儿,我是一个程序员”。
那妇女说,“这又怎样?”
程序员说,“我们从来不关心Warning,只关心Error”

两字符串走进一个酒吧坐下,酒保过来,“两位来点什么?”
第一个字符串说,“我要一杯湿啤 fulk boorg jdk^CjfdLk jk3s d#f67howe%^U r89nvy~~owmc63^Dz x.xvcu。”
“对不起,”第二个字符串对酒保说,“我这哥们没有\0结尾。

为API生,为框架死,为debug奋斗一辈子,吃符号亏,上大小写的当,最后死在需求上

程序员眼中的女人
有的女人就像Windows 虽然很优秀,但是安全隐患太大。
有的女人就像UNIX 她条件很好,然而不是谁都能玩的起。
有的女人就像C# 长的很漂亮,但是家务活不行。
有的女人就像C++,她会默默的为你做很多的事情。
有的女人就像JAVA,只需一点付出她就会为你到处服务。
有的女人就像JAVA script,虽然对她处处小心但最终还是没有结果。
有的女人就像汇编 虽然很麻烦,但是有的时候还得求它。

一百万只猴子,给他们一百万个键盘,其中的一个会写出Java程序,其它的写的都是Perl程序。

十个顶尖软件工程师参加培训管理人员的课程。
老师提出了这样一个问题:“假如你供职的公司是为航空电子设备提供软件的。有一天你乘飞机出差,当你上了飞机后你发现一个牌子上写着这个飞机用的是你的团
队开发的Beta版软件,你会下飞机吗?”
九个软件工程师举了手。
老师看着第十个问道:“你为什么愿意留在飞机上?”
这人回答说:“如果是我的团队写的这个软件,飞机根本无法起飞,根本谈不上坠毁。”

这个笑话大概来自70年代的米国:一个新手在试着修复一台坏了的LISP机器,他的
方法就是反复关上电源然后打开。专家看到之后,严厉地批评说:“你这样是没用
的,你必须要明白出错的深层次原因。”
专家关掉电源,然后打开。运行正常了。

为什么程序员总是分不清万圣节和圣诞节?因为 Oct 31 == Dec 25。

小沈阳版程序员~~~
程序员其实可痛苦的了……需求一做一改,一个月就过去了;嚎~
需求再一改一调,一季度就过去了;嚎~
程序员最痛苦的事儿是啥,知道不?就是,程序没做完,需求又改了;
程序员最最痛苦的事儿是啥,知道不? 就是,系统好不容易做完了,方案全改了;
程序员最最最痛苦的事儿是啥,知道不? 就是,系统做完了,狗日的客户跑了;
程序员最最最最最痛苦的事儿是啥,知道不? 就是,狗日的客户又回来了,程序给删没了!

这个世界上只有10种人:懂得二进制的和不懂得二进制的。

Borland说我很有前途,Sun笑了;Sun说我很有钱,IBM笑了;IBM说我很专业,
Sybase笑了;Sybase说我数据库很牛,Oracle笑了;Oracle说我是开放的,Linux笑
了;Linux说我要打败Unix,微软笑了;微软说我的系统很稳定,我们都笑了。

程序员的祝福
祝大家在以后的日子里. 男生象Oracle般健壮; 女生象win7般漂亮; 桃花运象IE中
毒般频繁; 钱包如Gmail容量般壮大, 升职速度赶上微软打补丁 , 追女朋友像木马
一样猖獗, 生活像重装电脑后一样幸福, 写程序敲代码和聊天一样有**。

一个合格的程序员是不会写出 诸如 “摧毁地球” 这样的程序的,他们会写一个函
数叫 “摧毁行星”而把地球当一个参数传进去。

【 程序猿的三重境界】第一重:无尽bug常作客,困闷调试伴不眠。第二重:千行
代码过,bug不沾身。第三重:编码间,bug灰飞烟灭。

四个2B青年掐架。
A:你丫等着,我爹是敏感词!
B:操你大爷!你丫牛逼神马,我爹在百度索根本无法显示!!
C: 我爹404 not found!!!
D:我爹 Connection Reset !!!!

天冷了,办公室的门上出现了一张告示,赫然写着,“不关门,有BUG”,然后就见
每个出去的程序员,非常虔诚的把门关上了。

程序员找不到对象,一般有三种情况:1、 C# JAVA都有对象,但是经常找不到对象
。2、ASM C直接没有对象。3、javascript都是伪对象,最多算暧昧。但C++日子一
直都好过,因为C++是多继承,富二代呀!!!

汇编,C和C++是好朋友,每天一起吃饭。可是前天晚上C++一个人去吃饭了。问他怎
么了,他说“汇编和C没有对象,他们去过节了。。。

栈和队列的区别是啥? 吃多了拉就是队列;吃多了吐就是栈

CSDN社区网友on1y_1onely留言:“C确实不适合做应用层,C有C的地盘。”网友
zbdbao回复:“就是传说中的C盘么。”当时我就笑喷了……

一个IT经理走进一家拉面馆说:“你们需要客户端吗?” 老板说:“面一般是伙计
端,忙的时候才需要客户端。

一同学问我,软件外包是什么。解释了几句还没明白,遂想了一下:包工头知道吧?顿悟!

正在码代码ing,医院回来的同事一脸的苦逼样子,问他怎么了?他回答:得了类风
湿性关节炎了,我怕会遗传给下一代啊。我一脸的问号:谁说类风湿性关节炎能遗
传的?丫一脸诧异:类不是继承的吗?

世界上最遥远的距离不是生与死,而是你亲手制造的BUG就在你眼前,你却怎么都找不到她。。。

真的勇士,敢于直面惨淡的warning、敢于正视淋漓的error。

成功chroot过很多系统,却从未成功chroot过妹子的心。

某小偷潜入某IT公司欲行窃,没想到始终有人,他只好等啊等啊等,结果始终有一
大拨人在加班。过了一个月,小偷终于逮到机会溜出来,同伙问:去哪了?小偷:
在IT公司呆了一个月。同伙:收获不错吧?小偷苦笑:别TM提了,三十个大夜下来
,我现在已经学会写程序了。

继承,是幸福的延续;重载,是幸福的重生。

一同事最近bug特别多,假装关切问他有多少个bug,他回答:10。过几日,再问,
他回答:10。我:“这么多天过去了,怎么一点变化都没有?”,他:”哪里没有
变化啊,以前是十进制,现在变成十六进制。

假如生活欺骗了你,找50个程序员问问为什么编程;假如生活让你想死,找50个程
序员问问BUG改完了没有;假如你觉得生活拮据,找50个程序员问问工资涨了没有;
假如你觉得活着无聊,找50个程序员问问他们一天都干了什么!

经考证,C语言是女的…原因如下:1.无论你让她干什么,她绝对不可能自己找到方
法。2.总是用复杂的方法解决简单的问题。3.不可能自主认识到本身错误。4.浪费
时间是十分正常的事情。5.无论跟她说什么,都得用她能理解的方式做充分说明。
6.只要你有一点错,她一定可以挑出来。7.反正,是你的错。

必备技能:去得了公司,回得了厨房;不羡慕好车,不想买新房;不惊动腾讯,不
激怒同行;写得了代码,查得出异常;做得出产品,看得准市场;接触过VC,见识
过投行。

如果你的朋友最近没和你联系,要理解!只有三种可能:第一,他死了;第二,他
改行当程序猿了;第三,需求又改了!

某日和同学聊起对象问题,我答道:哥最不缺的就是对象了,要知道哥是程序员,
每天不知道要new多少个对象出来,而且想要啥类型就有啥类型。如果你想要,哥给
你来个for循环嵌套,new上她千个百个

做为一名程序员,我每次看到工资单的时候,都会想:那些数字为神马不是16进制。

一个程序出错信息: Keyboard not found … press F1 to continue

大家喝的是啤酒,这时你入座了。
你给自己倒了杯可乐,这叫低配置。
你给自已倒了杯啤酒,这叫标准配置。
你给自己倒了杯茶水,这茶的颜色还跟啤酒一样,这叫木马。
你给自己倒了杯可乐,还滴了几滴醋,不仅颜色跟啤酒一样,而且不冒热气还有泡泡,这叫超级木马。
你的同事给你倒了杯白酒,这叫推荐配置。
菜过三巡,你就不跟他们客气了。
你向对面的人敬酒,这叫p2p。
你向对面的人敬酒,他回敬你,你又再敬他……,这叫tcp。
你向一桌人挨个敬酒,这叫令牌环。
你说只要是兄弟就干了这杯,这叫广播。
有一个人过来向这桌敬酒,你说不行你先过了我这关,这叫防火墙。
你的小弟们过来敬你酒,这叫一对多。
你是boss,所有人过来敬你酒,这叫服务器。
酒是一样的,可是喝酒的人是不同的。
你越喝脸越红,这叫频繁分配释放资源。
你越喝脸越白,这叫资源不释放。
你已经醉了,却说我还能喝,叫做资源额度不足。
你明明能喝,却说我已经醉了,叫做资源保留。
喝酒喝到最后的结果都一样
你突然跑向厕所,这叫捕获异常。
你在厕所吐了,反而觉得状态不错,这叫清空内存。
你在台面上吐了,觉得很惭愧,这叫程序异常。
你在boss面前吐了,觉得很害怕,这叫系统崩溃。
你吐到了boss身上,只能索性晕倒了,这叫硬件休克。

与女友分手两月有余,精神萎靡,面带菜色。家人介绍一女孩,昨日与其相亲。女孩果然漂亮,一向吝啬的我决定破例请她吃晚饭。
选了一个蛮贵的西餐厅,点了比较贵的菜。女孩眉开眼笑,与我谈得很投机。聊着聊着,她说:“我给你讲个笑话吧。”“ok”
“一只螳螂要给一只雌蝴蝶介绍对象,见面时发现对方是只雄蜘蛛。见面后螳螂问蝴蝶‘如何?’,‘他长的太难看了’,‘别看人家长的丑,人家还有网站呢’。”
“呵呵………”我笑。忽然她问:“你有网站吗?”

系统程序员
1、头皮经常发麻,在看见一个蓝色屏幕的时候比较明显,在屏幕上什幺都看不见的时候尤其明显;
2、乘电梯的时候总担心死机,并且在墙上找reset键;
3、指甲特别长,因为按F7到F12比较省力;
4、只要手里有东西,就不停地按,以为是Alt-F、S;
5、机箱从来不上盖子,以便判断硬盘是否在转;
6、经常莫名其妙地跟踪别人,手里不停按F10;
7、所有的接口都插上了硬盘,因此觉得26个字母不够;
8、一有空就念叨“下辈子不做程序员了”;
9、总是觉得9号以后是a号;

10、不怕病毒,但是很害怕自己的程序;

原文地址:http://blog.csdn.net/qy1387/article/details/7957394

Webpack 打包优化之体积篇

原文出处: 晚晴幽草轩

 

谈及如今欣欣向荣的前端圈,不仅有各类框架百花齐放,如Vue, React, Angular等等,就打包工具而言,发展也是如火如荼,百家争鸣;从早期的王者Browserify, Grunt,到后来赢得宝座的 Gulp, 以及独树一帜的 fis3, 以及下一代打包神器 Rollup ;在 browserify,grunt,gulp,rollup,webpack 可以一窥其中部分对比。在本文要探究的是,当前打包工具绝对霸者 Webpack。

webpack

Webpack,当前各大主流框架默认配备的打包方案,对其如何使用,已有较完备中英文文档;并且,各主流框架也有对应 CLI 予以基础配置,故不作为探讨范畴。从产品层来讲,如何使得构建的包体积小、运行快,这有必要不断摸索实践,提炼升级,使之臻于最佳。本文将从以下些许方面,对 Webpack 打包体积方面,做下优化探讨(备注: Webpack实践版本: 3.3.0):

定位 webpack 大的原因

这里推荐使用 webpack-bundle-analyzer —— Webpack 插件和 CLI 实用程序,她可以将内容束展示为方便交互的直观树状图,让你明白你所构建包中真正引入的内容;我们可以借助她,发现它大体有哪些模块组成,找到不合时宜的存在,然后优化它。我们可以在 项目的 package.json 文件中注入如下命令,以方便运行她(npm run analyz),默认会打开 http://127.0.0.1:8888 作为展示。

“analyz”: “NODE_ENV=production npm_config_report=true npm run build”

93f72404-b338-11e6-92d4-9a365550a701

 

当然,同类型的还有 webpack-chart 以及 webpack-analyse,这两个站点也是以可视方式呈现构造的组件,可以让你清楚的看到模块的组成部分;不过稍显麻烦的是,你需要运行以下命令,生成工具分析所需要的 json 文件:

JavaScript

1
2
3
webpack –profile –json > stats.json
// 如果,运行指定的 weboack 文件,可用此命令
webpack –config build/webpack.prod.conf.js  –profile –json > stats.json

引入 DllPlugin 和 DllReferencePlugin

DllPlugin 和 DllReferencePlugin 提供了以大幅度提高构建时间性能的方式拆分软件包的方法。其中原理是,将特定的第三方NPM包模块提前构建👌,然后通过页面引入。这不仅能够使得 vendor 文件可以大幅度减小,同时,也极大的提高了构件速度。鉴于篇幅,具体用法可参见:webpack.dll.conf.js

外部引入模块(CDN)

如今前端开发,自然是使用ES6甚至更高版本,撸将起来才更嗨。但由于浏览器兼容问题,仍得使用 babel 转换。而这 babel-polyfill 也得引入以确保兼容;还比如项目开发中常用到的 moment, lodash等,都是挺大的存在,如果必须引入的话,即考虑外部引入之,再借助 externals 予以指定, webpack可以处理使之不参与打包,而依旧可以在代码中通过CMD、AMD或者window/global全局的方式访问。

JavaScript

1
2
3
4
5
6
7
8
9
// webpack 中予以指定
externals: {
  // ‘vue’: ‘Vue’,
  // ‘lodash’: ‘_’,
  ‘babel-polyfill’: ‘window’
}
//
<script src=”//cdn.bootcss.com/autotrack/2.4.1/autotrack.js”></script>
<script src=”//cdn.bootcss.com/babel-polyfill/7.0.0-alpha.15/polyfill.min.js”></script>

需要补充的是 externals 中:key 是 require 的包名,value 是全局的变量。

让每个第三包“引有所值”

确定引入的必要性

前端发展到如今时期,倘若项目采用了 MVVM模式框架,数据双向绑定,那么像 jQuery 这般类库,不能说没有丝毫引入的必要,至少可以说确实没有引入的必要。对此,如果还有些顾虑,完全可以参考下 YOU MIGHT NOT NEED JQUERY;用原生写几行代码就可以解决的事儿,实在不易引入这么个庞然大物,平添烦恼。

避免类库引而不用

倘若这类情况发生,对整个打包体积,不仅大而且亏。项目一旦大了,很难人为保证每个引入的类库,都被有用到,尤其是二次开发。所以工具的利用十分必要,强烈推荐类如 Eslint这般工具,并且注入对应规则,对声明却未使用的代码,给予强制提醒;这不仅可以有效的规避类似情形发生(也适用于普通变量的检测),而且还能使得团队代码风格,尽可能地保持相似;要知道代码足够遵守规则,也可让压缩工具更有效压缩代码,一举多得,何乐不为?

尽量使用模块化引入

如果说 jQuery 确实没有引入必要,很多人会同意;但对于 lodash 这类依赖的工具,并不是所有人都会去造一发轮子的。然而全包引入 400kb 的体量,可否有让你心肝一颤?幸好的是,lodash 提供了模块化的引入方式;可按需引入,快哉快哉:

JavaScript

1
2
3
4
5
import { debounce } from ‘lodash’
import { throttle } from ‘lodash’
// 改成如下写法
import debounce from ‘lodash/debounce’
import throttle from ‘lodash/throttle’

擅懒如你的优秀程序员,是否也发现这样写颇为麻烦?那么恭喜你,这个问题已经被解决;lodash-webpack-pluginbabel-plugin-lodash 的存在(组合使用),即是解决这问题的。它可将全路径引用的 lodash, 自动转变为模块化按使用引入(如下例示);并且所需配置也十分简单,就不在此赘述(温馨提示:当涉及些特殊方法时,尚需些留意)。

JavaScript

1
2
3
4
// 引入组件,自动转换
import _ from ‘lodash’
_.debounce()
_.throttle()

额外补充的是,即便采用如上写法,还是不够快捷,每个用到的文件,都写一遍 import,实在多有不便。更可取的是,将项目所需的方法,统一引入,按需添加,组建出本地 lodash 类库,然后 export 给框架层(比如 Vue.prototype),以便全局使用;详情可参见:vue-modular-import-lodash

JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// helper 文件夹下 lodash,统一引入你需要的方法
import _ from ‘lodash’
export default {
  cloneDeep: _.cloneDeep,
  debounce: _.debounce,
  throttle: _.throttle,
  size: _.size,
  pick: _.pick,
  isEmpty: _.isEmpty
}
// 注入到全局
import _ from ‘@helper/lodash.js’
Vue.prototype.$_ = _
// vue 组件内运用
this.$_.debounce()

尽可能引入更合适的包

作为前端开发的你,想必知道有 momentjs 的存在(Parse, validate, manipulate, and display dates in javascript.);更多的是,你想必知道它很好用,然而它的体态却十分丰满(丰盈),没念及此,是否有重新造轮子的冲动?SpaceTime: A lightweight way to manipulate, traverse, compare, and format dates and times across planet Earth。 具有与 monent 相似 api 的新类库,其体积又相对小很多(当然,据观察其灵活度略逊一筹);date-fns:现代JavaScript日期实用程序库( Modern JavaScript date utility library ),如 lodash 一样,可支持模块化;知道这些或者更多的你,会如何选择?

按需异步加载模块

关于前端开发优化,重要的一条是,尽可能合并请求及资源,如常用的请求数据合并,压缩合并 js,构造雪碧图诸此等等(当然得适当,注意体积,过大不宜);但,同时也当因需制宜,根据需要去异步加载,避免无端就引入早成的浪费。webpack 也是内置对这方面的支持; 假如,你使用的是 Vue,将一个组件(以及其所有依赖)改为异步加载,所需要的只是把:

JavaScript

1
import Foo from ‘./Foo.vue’

改为如下写法:

JavaScript

1
const Foo = () => import(‘./Foo.vue’)

如此分割之时,该组件所依赖的其他组件或其他模块,都会自动被分割进对应的 chunk 里,实现异步加载,当然也支持把组件按组分块,将同组中组件,打包在同个异步 chunk 中。如此能够非常有效的抑制 Javascript 包过大,同时也使得资源的利用更加合理化。

生产环境,压缩混淆并移除console

现代化中等规模以上的开发中,区分开发环境、测试环境和生产环境,并根据需要予以区别对待,已然成为行业共识;可能的话,还会有预发布环境。对待生产环境,压缩混淆可以很有效的减小包的体积;同时,如果能够移除使用比较频繁的 console,而不是简单的替换为空方法,也是精彩的一笔小优化。如果使用 UglifyJsPlugin 插件来压缩代码,加入如下配置,即可移除掉代码中的 console:

JavaScript

1
2
3
4
5
6
7
8
new webpack.optimize.UglifyJsPlugin({
  compress: {
    warnings: false,
    drop_console: true,
    pure_funcs: [‘console.log’]
  },
  sourceMap: false
})

Webpack3 新功能: Scope Hoisting

截止目前(17-08-06), Webpack 最新版本是 3.4.1;Webpack在 3.0 版本,提供了一个新的功能:Scope Hoisting,又译作“作用域提升”。只需在配置文件中添加一个新的插件,就可以让 Webpack 打包出来的代码文件更小、运行的更快:

JavaScript

1
2
3
4
5
module.exports = {
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
}

据悉这个 Scope Hoisting 与 Tree Shaking,最初都是由 Rollup 实现的。在个人中实践中,这个功能的注入,对打包体积虽有影响,却不甚明显,有兴趣的盆友可以试下;更对关于此功能讯息,可参见 Webpack 3 的新功能:Scope Hoisting

本文自本月(08)四号开始陆陆续续写,原本的内容意图是,涉及 Webpack 打包优化的体积和速度两个方面;岂料,临近写完的时候(06号晚),已记不得多久没关机的 Mac,竟然被重启了下;屋漏多半会偏逢连夜雨,那一向会自动同步保存(30min)的作业部落,竟然没给同步,WTF!整个周末的敲敲打打,皆付之东流,泪崩泪目😂。无奈之下,只得重新写过,直到夜深,才补齐关于体积优化那部分;毕竟涉及内容较多,干脆,就分成两部分来完成😪。也在此提醒广大笔友,及时备份数据并确认,这很重要😂。在此也预告下一篇 《Webpack 打包优化之速度篇》,当然,此文也扔在完善中。

提高网页可访问性的十条建议

这篇文章提供了十条有关于提高网页可访问性的指导原则,这些原则将保证提高你网站的可访问性。

引用万维网(W3C)的创始人@Tim Berners-Lee一句话来说网络的力量是它的普遍性。作为靠制作网站谋生的人,我们有责任确保每个人都能更好接触到他们。网页可访问性似乎是一项艰巨的任务,但它确实比听起来要容易很多。

这十条网页可访问性准则旨在确保所有网站都是通用的。

这不仅能帮助屏幕阅读器用户,而且还能改善浏览体验,以减缓连接速度。我们已经按照实施时间对我们的指导方针进行了排序,让您清楚地了解在这个过程中需要付出多大的努力。在你不知所措之前,请相信我的话,这是值得的。

什么是网页可访问性?

根据W3C的说法,Web可访问性意味着每个人都可以感知、理解、导航、与Web交互,并为Web做出贡献。在这方面,网站的可访问性包括所有影响网站访问的条件,包括视觉、听觉、物理、语言、认知和神经功能障碍。

你会在网上找到很多关于这个话题的内容,如果这个话题让你感兴趣的话,你应该更深入地了解网站可访问性倡议WAI)。

考虑到这一点,以下是我们提出的十条提高网站可访问性的建议。

不要依靠颜色

颜色是一种强大的工具,我们经常用来表达情感和在网上交流信息。然而,我们不应该把所有的信息都用颜色来传达,用来表达我们的用户的意思和信息。

为什么?

例如,人们普遍认为绿色意味着是正确,红色意味着错误,但是当我们把它作为我们唯一的沟通方式时,会发生什么呢?

web-accessibility-color-blindness-1

色盲是最常见的视力缺陷之一。全球人口总数大约有

4.5%

的人有这方面的缺陷(这个用户量已经超过了IE11用户数量)。

如果我们用户界面中显示的重要信息只使用颜色来传达,那么也意味着全球将有4.5%的人受影响。

颜色应该只是错误或确认信息的补充,但不能成为我们使用的唯一工具。为了确保我们的重要信息能够触达到所有的用户,我们应该要在表单中添加标签或图标来填充正确的信息。

web-accessibility-color-blindness-2

Caniuse.com提供了一个非常有趣的解决方案,它提供了一个替代颜色调色板的兼容列表:

web-accessibility-caniuse-1

在设计时检查颜色的识别力和对比是比较理想的,所以要确保你和你的设计团队有正确的工具。我们强烈推荐使用Sketch的Stark插件,可以帮助你设计可访问性!

不要阻止放大

在响应式设计的时代,我们可能犯了一些不负责任的错误。

其中

maximun-scale=1.0

就是幽灵之一,它让移动设备的网页无法放大。

在欧洲和亚洲,散光影响着30%~60%的成年人,但模糊的视觉会影响到所有年龄和民族的人。

放大的能力不仅仅是一个WCAG指导方针,而是日常生活中一个简化的工具。所以下次你在建立一个响应式网站的时候,要考虑到视力模糊的人群,比如我们的妈妈。

除了让用户可以自由地在移动设备上缩放之外,还要记得检查PC端浏览器上高达200%的放大特性。

重新重视

alt

属性

不管你制作网站多久,你可能会惊讶地知道下面这些关于

alt

属性的建议:

  • alt

    属性是

    img

    元素的一个强制性属性,但是

    alt

    的属性值是空的话完全有效。如果图像是装饰的或者没有必要的阐述页面的内容,那么你可以简单地使用

    alt=""
  • 屏幕阅读器会告诉用户
    img

    是一个图像,其中

    alt

    的值告诉用户这个图片表达的内容

  • 图像的功能和它的含义一样的重要,如果你的网站Logo链接到你的网站的主页,那么img
     

    alt

    的属性值应该是“首页”,而不是“Logo”

  • 图像替换文本不仅仅是关于可访问性的。有时候,对于网络慢的用户为了提高浏览器的检验(更快)会禁用网页的图像。对于这些用户群体,你就需要记得给img
     

    alt

    属性添加对应的属性值

但不是所有的图片都是使用

img

元素,对吧?你可能会用一个两个SVG或者一套SVG图标。

我们如何让每个人都能访问SVG?幸运的是,SVG(Scalable Vector Graphics)标准已经覆盖面已经很广!为了描述我们的SVG,我们可以使用

 

 

元素来进行简述和详细的描述。

给视频添加标题和子标题

这可能是WCAG最麻烦的原则之一,这不是因为技术上的困难,而是因为它可能是费时的。不过有一些方法可以做到这一点:

  • 让我们以YouTube为例。一旦你在这个平台上上传了一个视频,你就可以启用关闭标题。这些都是自动生成的,可能在某些情况下是不准确的,这取决于语言、背景噪音或说话人的口音。不过,这些都很容易实现,而且在大多数讲英语的视频中都能很好的工作
  • 如果我们很难看到百分百准确的标题,很难相信YouTube会有好的复制,所以我们必须自己写标题,或者雇佣第三方人员来做。如果我们不用任何字幕软件,或者我们希望我们的社区帮助我们翻译内容,YouTube将采用最常的副标题格式(
    .srt

    .sub

    .sbv

    )以及让我们在平台上写副标题。而不让管理员访问我们的账号,这将是非常方便的

web-accessibility-translating-subtitles-youtube-tool-1

  • 也许你不想把YouTube当作你的主机平台。也许您希望在你的服务器上使用一个HTML5视频。HTML5有一个
     

    标签,可以使用它方便让你添加你喜欢的标题和字幕软件,你可以使用你喜欢的WebVTT(翻译FTW)


语义等于可访问性

font

标签,还记得吗?我希望你不记得,那些是“古时代”的产物了。

尽管我们有着共同的信念,但语义并不是与生俱来的。自从第一个HTML页面诞生以来,他们一直与我们在一起,自那时候已经有了很大的进步。有了HTML5标准之后,新的语义标签就被引入到我们的日常使用当中。

web-accessibility-first-website

那么,语义不是仅仅为了SEO吗?

不一定。当您有意识地在&lt;p&gt;

&lt;span&gt;

选择中使用了

&lt;h1&gt;

标签时,这也意味着你也故意更改元素的含义,提供了层次结构,同时也构建页面信息的树形结构。

屏幕阅读器并没有忘记这一点。事实上,语义化是它最有用的武器之一

请记住,拥有强大的能力会带来很大的责任,所以一定要为每个元素使用合适的语义标签,从

 
h1

到全新main的

 

标签。

使用正确的标记

作为一个后续的观点,我想讨论一些不友好和有争议的地方。

Time vs. Datetime

time

元素显示了日期格式、时区的很多类型和使用ISO 8601标准来表示日期和时间的时间。

datetime

是一个可选属性,可以帮助表示time

 

的内容。让我们看看一些例子:

 

del

ins

Web不断变化,但没有必要让这些变化被忽视。我们可以将

ins

del

这样的HTML标签与

datetime

属性结合在一起使用。

ins

元素表示添加了一个文档:

del

元素表示删除一个内容:

button

vs. <a>

 

对于

button

和<a>

 

标签,我们什么时候使用更合适呢?

我们一起来看看。

&lt;a&gt;

元素的意思是链接一个文件或打开一个新标签或当前的链接。然而,每当我们希望触发一个Hamburger菜单或图片画廊之类的动作时,这个标签就有点不理想。

button

元素对于这些情况就更为比较适合,而且通常可以用JavaScript实现。

此外,

button

标签很容易与

input type="button"

混淆,但他们的区别在于前者能够获取更多的内容(文、图像加文或仅图像)。

当使用

button

标签时,有两件事情需要考虑:

首先,如果

button

的内容不够明确(例如,在关闭按钮中使用

X

),我们必须添加一个

aria-label

属性来帮助解释其功能。

其次,如果添加

href

属性是意义的(比如搜索组件或lightbox gallery),那么我们不仿使用一个

a

标签和使用JavaScript来覆盖链接行为。如果没有启用JavaScript,使用带有

href

标记的图像库将优雅地降级。

必要时使用

role

为了告诉屏幕阅读器用户,我们的链接触发了一个动作,实际上它不是一个普通的<a>

 

标签,使用

 

标签时,我们必须给他添加一个

role

属性。

但是要小心。

当你编写你的JavaScript时,你不仅需要在点击时调用你的函数,还需要在用户按下空格键时调用你的函数。这是必要的,因为用于按钮的行为不同于用于链接的行为,用户应该能够触发这些命令中的任何一个操作。

有关于这方面更多的信息可以在MDN上了解。

记住,除非你打破规则,否则通常不需要aria role规则,比如上面的示例。HTML语义元素已经应用了默认的

role

,比如

nav

标记表示的是导航,

 

标签表示的是链接等等。这意味着当我们希望更改这些默认值时,

role

属性只是必需的。

隐藏元素

使用HTML和CSS有一些方法可以隐藏东西。下面的列表将帮助你找到每一种情况的最佳选择:

方法 行为 屏幕阅读器行为 兼容性
CSS:

visibility:hidden;
从视觉中隐藏元素,但其原始空间仍然被占用(很像

opacity:0

不可读 到处可用(兼容性好)
CSS:

display:none;
从视觉中隐藏元素,它的原始空间丢失,下一个元素将取代它的位置 不可读 到处可用(兼容性好)
HTML5:

hidden

属性

类似于

display: none;
不可读 IE11+
aria-hidden = "true"
内容会显示在浏览器中,但通过技术不会传递给用户 不可读 IE11+
CSS:

.visuallyHidden

从视觉中隐藏元素,并从工作流中删除它 可读 到处可用(兼容性好)

如果你想隐藏元素,但仍然让屏幕阅读器可以知道它们,那么最后一个选项是最好的选择。

这在表单的

label

或Skip-to-content链接中非常有用。

visuallyHidden

类是一个CSS代码,它什么你收藏,因为每个项目都容易用到它。是的,如果你愿意,你可以改名字(我的建议是

.pottersCloak

)。

遵循Web可访问性标准

Web可访问性标准和指南在这里可以给我们提供很强的帮助。

本文前面的所有要点都是

button

是如何工作的?我们应该什么时候使用它?

display:none

hidden

属性的区别是什么?

首先,它可能是非常枯燥的,但W3C标准WCAG指南非常有用,它们还有非常强的意义。去吧,在他们当中提供了很多可用的信息。我向你保证,你将发现你从未接触的代码和实践!

Audit and review

一旦你应用到了所有这方面的知识,那就到了测试它的时候了。以下提供了一些工具测试你的网站可访问性:

Aerolab的可访问性经验

我们试着养成经常测试的工作习惯。我们的下一个产品应该总是比上一个更好。虽然我们有时候会犯错,但我们会不断地去改进,更不用说从每个挑战中学习一些东西了。

我们希望我们的产品能为用户提供最好的体验,这就是为什么我们逐渐开始将可访问性标准添加到我们的工作流程中。

我们还有很长的路要走,还有些要改进的空间,但我们很高兴选择了这条路。

我们为Xapo所做的登录页面就是我们如何给网站添加可访问性标准的一个例子,你可以从这个例子中查到这方面的使用代码:

web-accessibility-xapo-network-landing-page

总结

网站可访问性并不是那么容易就能实现,但如果你能把它变成你日常工作的一部分(而不是最后才来检查),随着时间的推移,它的实现和测试就会变得很容易。

当有疑问时,不要害怕询问其他的开发人员或者做一些这方面的研究。我最喜欢从这些渠道获取一些有用的信息:

另外,如果你有更多更好的资源,请在下面的评论中告诉我们。