最近使用了下Grunt和WebPack,这里总结下使用中的一些问题。

Part1 版本问题

安装grunt-contrib-image提示

WARN node unsupported "[email protected]" is incompatible with
[email protected][email protected][email protected][email protected][email protected][email protected]^5.0.0, expected [email protected]>=0.10.0 <7

imagemin安装版本v2.0.1,期望的版本是[email protected]>=0.10.0 <7 与8.9.3不匹配,这个警告并不影响imagemin的使用,配置好image插件之后,输入grunt命令就自动执行压缩

配置如下

	imagemin: {
	  dist: {
	    options: {
	      optimizationLevel: 6,
	      progressive: true
	    },
	    files: [{
	      expand: true,
	      cwd: 'images/',
	      src: '{,*/}*.{png,jpg,jpeg,ico}',
	      dest: 'images/'
	    }]
	  }
	}

Part2 配置说明

配置插件选项的说明的一些链接,以便于之后配置使用

jshint: https://jshint.com/docs/options/

其他大部分可以在npmjsgrunt搜索找到说明或在github的readme中,部分链接如下:

concat: https://github.com/gruntjs/grunt-contrib-concat

uglify;https://www.npmjs.com/package/grunt-contrib-uglify

htmlmin: https://www.npmjs.com/package/grunt-contrib-htmlmin

Part1. 函数参数

js中函数实参是可以可变的,可以通过标识符arguments来得到,arguments是指向实参对象的列表

Try 下面是一个求和的函数的例子:


	function tryFunc(){
		var sum;
		for(var i = 0,sum=0; i<arguments.length; i++){
			sum += arguments[i];
		}
		return sum;
	}

	console.log(tryFunc(1,2,3,4,5));		// 15

js实参对类型没有检查,所以在写函数的时候可以通过注释来对参数进行说明,或者在函数中加入判断即可

Part2. 函数属性

函数作为特殊的对象,也具有对象的特点。来看一个栗子

Try:


	function pro(a,b){
		return a+b+pro.x;
	}
	pro.x = 1;
	console.log(1,2);			// 4

这里调用了pro的自有属性x作为值参与运算,得出结果为4.接下来再看一个栗子

Try:

	
	function factorial(n){
		if(isFinite(n) && n>0 && n == Math.round(n)){
			if(!(n in Factorial)){
				factorial[n] = n * factorial(n-1);
			}
			return factorial[n];
		}
		else return NaN;
	}
	factorial[1] = 1;

这个例子通过自身属性存储n阶乘的值,之后通过访问factorial[n]就能快速得出结果

Part3. 闭包

定义:函数对象可以通过作用域链相互关联起来,函数体内的变量都可以保存在函数作用域内,这种特性称为“闭包”。js中函数都是闭包

Try看下js函数重载的栗子:


	var text = "global";
	function test(){
		var text = "local";
		function f(){console.log(text);}
		return f();
	}
	test();				// local

	funtion test1(){
		var text = "local";
		function f(){console.log(text);}
		return f;
	}
	test1()();			// local

可以看到test函数返回了f函数的执行结果,f函数打印了函数作用域中的text的值 test1函数中返回了函数f,执行f函数后,发现返回的仍然是local。这是因为嵌套的函数f在test1作用域中,所以text(local)会绑定到f中,不论何时何处都会是local

Try利用闭包实现js函数重载:


//addMethod
function addMethod(object, name, fn) {
	var old = object[name];
	object[name] = function() {
		if(fn.length === arguments.length) {
			return fn.apply(this, arguments);
	} else if(typeof old === "function") {
			return old.apply(this, arguments);
		}
	}
}
	 
	 
	var people = {
		values: ["Dean Edwards", "Alex Russell", "Dean Tom"]
	};
	 
	/* 下面开始通过addMethod来实现对people.find方法的重载 */
	 
	// 不传参数时,返回peopld.values里面的所有元素
	addMethod(people, "find", function() {
		return this.values;
	});

	console.log(people);
	 
	// 传一个参数时,按first-name的匹配进行返回
	addMethod(people, "find", function(firstName) {
		var ret = [];
		for(var i = 0; i < this.values.length; i++) {
			if(this.values[i].indexOf(firstName) === 0) {
				ret.push(this.values[i]);
			}
		}
		return ret;
	});

	console.log(people);
	 
	// 传两个参数时,返回first-name和last-name都匹配的元素
	addMethod(people, "find", function(firstName, lastName) {
		var ret = [];
		for(var i = 0; i < this.values.length; i++) {
			if(this.values[i] === (firstName + " " + lastName)) {
				ret.push(this.values[i]);
			}
		}
		return ret;
	});

	console.log(people);
	 
	// 测试:
	console.log(people.find()); //["Dean Edwards", "Alex Russell", "Dean Tom"]
	console.log(people.find("Dean")); //["Dean Edwards", "Dean Tom"]
	console.log(people.find("Dean Edwards")); //["Dean Edwards"]

Part4. 函数call,apply,bind方法

我们经常能看到call和apply,他们可以间接的调用函数.功能一样,但是参数不同

Try:


	var a = 20;
	var o = {a:15};
	function f(b,c){
	    console.log(this.a+b+c);
	}
	f.call(this,1,2);			// 23
	f.apply(o,[1,2]);			// 18
	
	//or
	Array.prototype.slice.call(a, n||0);

slice用法

Try bind方法:


	var o = {o:1};
	function f(){
		//"use strict";
		console.log(arguments);		// [1,2,3,4,5]
		return this;
	}
	f();					// 非严格模式:window,严格模式: undefined
	f.bind(o,1,2,3,4,5)();			// 返回对象o


call和bind的参数都一样,但是call调用之后直接调用函数,bind只是返回了一个函数,需要再调用,apply和bind、call参数不同。

Part1 准备工作

一个特效基本构成有以下两点:

1. 构成特效基本元素,例如原点、线条、图案或者多种组合等
2. 动画效果,实现基本元素的运动

那么接下来就需要按照步骤进行,以下面的加载效果为例


Part2 特效基本元素准备

例如我们需要原点作为基本元素

  1. 可以使用设置了border-radius的div来作为基本元素
  2. 使用一个icon来作为基本元素

第一种较为简单,第二种可以使用FontAwesome等图标字体库来实现 这里介绍下第二种方法

	
	<!-- html代码 -->
	<div id ="bg">
		<div class="ball"></div>
		<div class="ball"></div>
		<div class="ball"></div>
		<div class="ball"></div>
		<div class="ball"></div>
	</div>

	/*css代码*/
	#bg{
		position: relative;
		text-align: center;
		height: 100px;
	}
	.ball:after{
		content:'\f013';		/*使用字库代码,可随意改变*/
		font-family: FontAwesome;	/*使用字库*/
	.ball{
		position: absolute;
		width: 80px;
		height: 80px;
	}

FontAwesome中的字体代码库或者直接用相关FontAwesome的类,官网中有栗子

基本元素有了,接下来就要有动画效果

Part3 动画效果

使用css3的animation,@keyframes就可以搞定

思路:图案逐个旋转,设置延迟不同,从0度旋转至360度,ease-in-out保证开始和结束较过程中缓慢些。 也可以通过过程改变旋转速度达到不同效果,代码如下:

	
		.ball:nth-child(1){
			animation: rota 2s 1000ms infinite ease-in-out;
		}
		.ball:nth-child(2){
			animation: rota 2s 800ms infinite ease-in-out;
		}
		.ball:nth-child(3){
			animation: rota 2s 600ms infinite ease-in-out;
		}
		.ball:nth-child(4){
			animation: rota 2s 400ms infinite ease-in-out;
		}
		.ball:nth-child(5){
			animation: rota 2s 200ms infinite ease-in-out;
		}
		@keyframes rota{
			0%{transform: rotate(0deg);}
			100%{transform: rotate(360deg);}

		}

数组也是对象的特殊形式

Part1 数组基本操作

Try 创建数组:


	var a = [];				//	空数组
	var b = [{1,2},{3,4},"123",123];	// 不同类型元素的数组
	var c = [,,];				// 两个uundefined元素

	var d = new Array();			// 空数组
	var e = new Array(10);			// 一个长度为10的数组
	var f = new Array(10,10,11,11);		// 当参数大于1,传入参数初始化为数组元素[10,10,11,11]

	// 二维数组
	var g = new Array(5);
	for(var i = 0; i < g.length; i++){
		g[i] = new Array(10);
	}
	//init
	for(var i = 0; i < g.length; i++){
		for(var j = 0; j < g[i].length; j++){
			g[i][j] = i*j;
		}
	}

Try 数组读写和遍历:

	
	var a = [1,2,3,4,5];
	for( var x in a ){
		console.log(a[x]);				// 遍历读取数组
	}

	for( var i = 0; i < a.length; i++ ){
		a[i] += 5;					// 遍历写数组,为每个数组+5
	}

Try 数组添加删除:


	var a = [1,2];
	a[2] = 3;						// 添加元素

	a.push(4);						// 末尾追加元素,长度+1
	a.pop();						// 删除最后一个元素,长度-1

	a.shift();						// 删除第一个元素,长度-1
	a.unshift(5);						// 头部增加元素

	delete a[0];						// 删除第一个元素,长度不变,索引处不再有元素empty

	var b = [1,2,3,4,5,6,7];
	b.splice(5);						// 返回删除数组[6,7],b为[1,2,3,4,5]
	b.splice(2,2);						// 返回[3,4],b为[1,2,5]
	b.splice(1,1,10);					// 返回[2],b为[1,10,5]
	b.splice(1,0,20);					// 返回[],b为[1,20,10,5]

权威指南第六版,p154,splice中最后栗子有误,var a=[1,2,3,4,5];a.splice(2,2,[1,2],3); a应该为[1,2,[1,2],3,5]

Part2 数组方法(部分)

Try:

	
	//join将数组转换为字符串,是String.split逆向操作
	var a = [1,2,3,["a","b"]];
	a.join("");						// 返回"123a,b"
	a.join();						// 返回"1,2,3,a,b"
	a.join(" ");						// 返回"1 2 3 a,b"

	//reverse()将数组元素颠倒
	var a = [1,2,3];
	a.reverse();						// a是[3,2,1]

	//sort,按照字母表顺序排序,返回排序好的数组
	var a = ["b","c","a"];
	a.sort();						// 返回["a","b","c"]
	var a= [2,1,3];
	a.sort(function(a,b){return a-b;})			// 返回[1,2,3]


	//concat链接数组元素
	var a = [1,2,3];
	a.concat(4,5);						// 返回[1,2,3,4,5]
	a.concat([4,5]);					// 返回[1,2,3,4,5]
	a.concat([4,[5,6]]);					// 返回[1,2,3,4,[5,6]]

	//slice返回子数组
	var a = [1,2,3,4,5];
	a.slice(0,2);						// 返回[1,2]
	a.slice(3);						// 返回[4,5]
	a.slice(1,-1);						// 返回[2,3,4]

	//forEach遍历数组为每个元素调用函数
	var a = [1,2,3,4,5];
	a.forEach(function(v,i,a){ a[i] = v+1; }) 		//a是[2,3,4,5,6]

	...等等方法

参考文献:《javascript参考指南》第7章,MDN

Part1 创建对象

有三种创建对象的方法

Try空对象:


	var a = {}			// 对象直接量
	var b = new Object();			  // 创建一个空Object对象
	var c = Object.create(Object.prototype);			//效果和前两种方法一致
	var d = Object.create(null);			// 创建空对象,不继承Object.prototype

a,b,c都继承了Object的prototype,参考charpter2 instanceof,

Try非空对象:

	
	var a = {x:1,y:2};			// 对象直接量
	var b = new Object(a);			// b => {x:1,y:2}
	var c = Object.create(a);			// {}
	//b,c的功能并不一样

b使用new Object会调用Object的构造函数,将a传入作为初始值,所以得到{x:1,y:2} Object.create()创建一个新对象,原型对象为传入的第一个参数(即{x:1,y:2}),生成对象{}的__proto__(原型对象)为{x:1,y:2,proto}

Try Object.create:


	var a = {x:1,y:30};
	var b = Object.create(a);
	b.x = 5;
	var c = Object.create(b);
	c.x = 10;
	console.log(b.x,b.y);			// 5 30
	console.log(c.x,c.y);			// 10 30
	console.log(c.z);			//undefined

对象的属性值首先会在该对象的属性中寻找,如未找到就在其原型对象中查找,如未找到就在该原型对象的原型对象中查找直到找到为止,未找到返回undefined z in c -> z in c._proto__ -> z in c.proto.proto -> return undefined

Part2 对象操作

Try 一些直观的栗子:

	
	var a = {name:"deak",sex:"male"};

	a.name; a["name"];			// 读取对象属性

	a.age = 40;			// 添加对象属性

	delete a.age;			// 删除对象属性,无法删除继承属性
	delete a.name.toString;			// false

	"name" in a;				// true,检测对象属性

	a.hasOwnProperty("name");			// true,检测自有属性
	a.hasOwnProperty("toString");			// false
	a.propertIsEnumerable("name");			// true.检测自有属性是否可枚举
	Object.prototype.propropertIsEnumerable("toString");			// false,不可枚举

Part3 setter & getter

由setter和getter定义的属性乘坐存储器属性,不同于数据属性

Try:


	var a = {
	    data : 12,
	    set op(x){
	        this.data = this.data + x;
	    },
	    get op(){
	        return this.data;
	    },          //存取器属性定义一个或两个属性同名的函数
	    get ox(){
	        return this.data*12;
	    }
	};
	a.op = 8;
	console.log(a.op);          // 20
	console.log(a.ox);          // 240

要注意关键字set,get之后的函数没有function关键字,op,ox等setter,getter属性跟普通属性一样读写

Part5 属性特征

获取属性特征:

Object.getOwnPropertyDescriptor(),Object.getOwnPropertyDescriptors()

设置属性特征:

Object.defineProperty(),Object.defineProperties()

Try:

	
	// Object.definePropery 第一个参数为对象,第二个参数为对象属性,第三个参数为需要设置的对象
	//这里定义一个objectId属性,不可枚举,不可配置,不可写,设置getter属性
	Object.defineProperty(Object.prototype,"objectId",{
	    get: IdGetter,			// getter属性
	    wirtable false,
	    enumerable: false,
	    configurable: false
	});

	function IdGetter(){
	    if(!(idprop in this)){
	        if(!Object.isExtensible(this)){
	            throw Error("error");
	        }
	        Object.defineProperty(this,idprop,{
	            value: nextid++,
	            writable: false,
	            enumerable: false,
	            configurable: false
	        });
	    }
	    return this[idprop];
	}


	// Object.defineProperties批量设置属性
	function Range(from , to){
	    // var prop = {
	    //     from:{value: from, enumerable: true, writable:false, configurable:false},
	    //     to:{value: to, enumerable: true, writable:false, configurable:false}
	    // };

	    // if(this instanceof Range)
	    //     Object.defineProperties(this,prop);
	    // else
	    //     return Object.create(Range.prototype,prop);
	    this.from = from;
	    this.to = to;
	    Object.defineProperty(this,"from",{enumerable: true, writable:false, configurable:false});
	    Object.defineProperty(this,"to",{enumerable: true, writable:false, configurable:false});

	}			//一个Range类,注释部分和未注释效果一样

	Object.defineProperties(Range.prototype,{
	    includes:{
	        value:function(x){return this.from <= x &&x <= this.to}
	    },
	    foreach:{
	        value:function(f){
	            for(var x= Math.ceil(this.from);x<=this.to;x++)(fx);
	        }
	    },
	    toString:{
	        value:function(){return this.from + this.to;}
	    }
	});

	var range = new Range(1,50);
	console.log(range.includes(20));

	range.from  = 20;
	console.log(range.from);

	console.log(Object.getOwnPropertyNames(range));
	console.log(Object.getOwnPropertyDescriptors(range));			// 批量获取属性描述符
	console.log(Object.getOwnPropertyDescriptor(range,"from"));			// 获取特定属性描述符