偶然看到JavaScript Tips。这个项目是天天分享一个JavaScript Tip,受到这个项目的启发,我也打算天天更新一个小Tip,我会参考原文,若是以为写的很不错,我也会仅仅充当翻译工,可是每个Tip都会加入我本身的思考,思考若是是我来写这个Tip的话,我会怎么写。git
经验不足,能力有限,还望轻拍。程序员
2016-01-07 至 2016-01-16:github
var arr = ["abc", "undefined","abc","xyz"]; var arr2 = arr.filter(function(item, index){ return arr.indexOf(item) === index; }); console.log(arr2); // ["abc", "undefined", "abc", "xyz"]
上面这种去重方式使用到了indexOf
这个函数,返回第一次查找到该元素的索引,用来与该元素在数组中的位置进行比较数组
关键词:数组去重、indexOf闭包
(function(name){ console.log(name); })("sunyuhui");
上面这种写法就是当即执行函数(更准确的叫法是当即执行匿名函数)。其中包含了两个知识点:框架
()
能够执行函数。使用当即执行函数的好处是能够避免函数里定义的变量污染全局变量。由于JS里没有局部变量这个概念(ES2015开始有局部变量的概念)。因此在须要使用到一些比较短的代码同时又不但愿污染全局变量的时候,就可使用当即执行函数。函数
关键词:当即执行函数性能
===
和==
的区别在于===
不会进行类型转换,而==
会进行类型转换。测试
console.log('1' == 1); // true console.log('1' === 1); // false
在使用时我建议使用===
,至少我在工做中是这样的,不过也有可能会有须要使用==
的场景,这里须要注意一下。优化
关键词:===、==
一般的作法是使用Number()
.
var a = '1'; var b = Number(a); console.log(b + ' ' +typeof b); // 1 number
这里介绍一种更加简单的方法。
var a = '1'; var b = +a; console.log(b + ' '+ typeof b); // 1 number
使用运算符+
能够快速转换为Number类型的数字。
关键词:String、Number
清空数组一般的作法是直接使用arr = []
的方式。不过这里想介绍另外一种方式:使用属性length
。
var a = [1,2,3]; a.length = 0; console.log(a); // []
其实我在2016-01-07:往数组中插入一个元素就提到过,JS中直接操做数组的length
属性,会影响到数组具体的内容。这里咱们再来看看这两种清空数组的方式 有什么不一样。
var a = [1,2,3]; var b = a; a = []; console.log(a); // [] console.log(b); // [1,2,3] var c = [1,2,3,4]; var d = c; c.length = 0; console.log(c); // [] console.log(d); // []
使用将属性length设置为零的方式会把从原数组复制的数组也清空,而使用arr = []
的方式则不会。
关键词:length、清空数组
实现链式调用一般是在jQuery
或者其余一些框架里,那咱们如何在原生的JavaScript
里实现链式调用呢?
function Person(name){ this.name = name; this.setName = function(name){ this.name = name; return this; } this.sayName = function(){ console.log(this.name); return this; } } var person = new Person("sunyuhui"); person.sayName().setName("sunyuhui2").sayName();// "sunyuhui" "sunyuhui2"
关键词:链式调用、this
拼接字符串一般的作法是使用加号+
,如c = '1' + '2';
使用加号+
的前提是要确保参数都是字符串,万一有参数不是字符串,就得不到指望的结果:
var a = 1; var b = 2; var c = '3'; var d = a+b+c; console.log(d); //"33"
这种时候可使用concat函数
.
var a = 1; var b = 2; var c = '3'; var d = ''.concat(a,b,c); console.log(d); //"123"
注意:Array
也有concat
函数,要注意区分。
关键词: concat
var a = 1; function get(){ console.log(a); } get(); //1 function get2(){ var a = 2; console.log(a); } get2(); //2
上面代码的输出结果你们确定明白,可是下面的代码呢?
var a = 1; function get(){ console.log(a); } function get3(){ var a = 2; get(); } get3(); //1
输入结果为1的缘由是因为函数嵌套时,做用域是在定义时决定的,而不是调用时决定的。函数get
是在全局做用域下定义的,而运行是在函数get3
的做用域下定义的,获取变量a
的值是在全局做用域下获取的,而不是在函数get
的做用域里获取的。
关键词: 嵌套、做用域
function Person(name,age){ this.name = name; this.age = age; } var person1 = new Person("sunyuhui", "22");
咱们以上面的代码为例说明new
这个关键字作了哪些事。
Person
的做用域赋给person1,这样对象person1就能够访问构造函数Person
里的代码。this就指向对象person1。name
和age
分别被赋值为sunyuhui、22。关键词: new
典型的递归函数是求一个数的阶乘:
function getTotal(num){ if (num<2){ return 1; } else { return num * getTotal(num-1); } } console.log(getTotal(3)); // 6
这里说到的优化主要指的是函数在调用自身的时候能够不用指定函数名,防止函数被从新赋值致使调用出错。
function getTotal(num){ if (num<2){ return 1; } else { return num * arguments.callee(num-1); } } console.log(getTotal(3)); // 6
其中arguments
指向函数的参数,arguments.callee
指向正在执行的函数,
关键词: 递归、arguments.callee
在【2016-01-15:实现JS中的继承】中我提到Man.prototype=
的形式会破坏构造函数的原型链。那在这种状况下咱们有什么办法不破坏吗?
固然有
function Person(name){ this.name = name; } Person.prototype = { //constructor: Person, getName: function(){ return this.name; } } var man = new Person("sunyuhui"); console.log(man.constructor === Person); // false
每个构造函数的原型都有一个construtor
属性,指向构造函数自己,上面这种设置原型的方式至关于重写了原型,却没有为原型的constructor
属性赋值,就破坏了Person
的原型链。将注释的部分取消注释就ok了。
关键词:constructor、原型链
function Person (name,age,pro){ this.name = name; this.age = age; this.pro = pro; } Person.prototype.getPro = function(){ return this.pro; } function Man (name,age,pro){ this.name = name; this.age = age; } Man.prototype = new Person("sunyuhui","22","fe"); var man1 = new Man("sunyuhui2","23"); console.log(man1.name); // sunyuhui2 console.log(man1.age); // 23 console.log(man1.getPro()); // fe
将构造函数Person
的实例赋予构造函数Man
的原型,这样就实现了Man
对Person
的继承,从中能够看到man1
的属性name和属性age覆盖了继承的属性,而属性fe被来自于继承。
注意:Man.prototype = ***
的这种形式会修改原型链,使Man
的实例对象的constructor
属性不为Man
.这个点之后单独说。
关键词: 继承
说到console
咱们一般使用console.log()
来输出变量或者打印日志,这里介绍一个不太经常使用的console.time()
和console.timeEnd()
。
console.time("time test"); var arr = new Array(1000); for(var i=0;i<arr.length;i++){ arr[i] = {}; } console.timeEnd("time test"); // time test: 4.037ms
须要注意console.time()
和console.timeEnd()
的参数须要保持相同。
咱们一般会使用typeof
、instanceof
、isArray
来检测数据的类型,这里我介绍一种万能型的:调用Object
的toString
方法。
function a(){console.log("yes");} console.log(Object.prototype.toString.call(a)); // [object Function] var b = 123; console.log(Object.prototype.toString.call(b)); // [object Number] var c = "sunhui"; console.log(Object.prototype.toString.call(c)); // [object String] var d = true; console.log(Object.prototype.toString.call(d)); // [object Boolean] var e = [1,2,3]; console.log(Object.prototype.toString.call(e)); // [object Array] var f; console.log(Object.prototype.toString.call(f)); // [object Undefined] var g = null; console.log(Object.prototype.toString.call(g)); // [object Null] var h = {"name":"sunyuhui"}; console.log(Object.prototype.toString.call(h)); // [object Object]
使用call
调用Object
的toString
方法,将会返回一个遵循[object NativeConstructorName]格式的字符串。其中NativeConstructorName指的就是变量的构造函数名。
在JavaScript里 变量声明 是让系统知道这个变量存在,而变量定义是给这个变量赋值。变量声明会被提早到顶部,而 变量定义 不会。
console.log(a); // "ReferenceError: a is not defined"
上面的代码报变量没有定义的错误。
console.log(a); // undefined var a;
上面的代码提示undefined
,这说明咱们对a
的声明被提早了。再来看:
console.log(a); // undefined var a = 3;
咱们在这里对a
同时作了声明和定义两个操做,从结果来看,只有声明被提早了,而定义却没有。
接下来看函数:
getName(); // "sunyuhui" function getName(){ console.log("sunyuhui"); }
结果说明咱们对函数的声明被提早了。
getAge(); // "TypeError: getAge is not a function" var getAge = function(){ console.log(22); }
事实上,第一种方式叫函数声明,这种方式能被提早到顶部,第二种方式叫函数表达式,不会被提早。
关键词:声明、 提早
咱们常常须要用到对象中的某个属性,为了保证程序的健壮性,在使用以前咱们须要判断这个属性是否存在,能够用if
来判断
if(obj[property] !== undefined){ // do something }
更方便的方式是使用这两个方法:hasOwnProperty
和in
。
function Person (name) { this.name = name; } Person.prototype.age = '22'; var person = new Person("sunhui"); console.log(person.hasOwnProperty('name')); // true console.log("name" in person); // true console.log(person.hasOwnProperty('age')); // false console.log("age" in person); // true
hasOwnProperty
和in
均可以用来判断某个属性是否存在于对象中,区别就是hasOwnProperty
不能搜索到从原型链继承的属性,而in
能够。
关键词:hasOwnProperty、in、原型链
function compare(prop){ return function(obj1,obj2){ var prop1 = obj1[prop]; var prop2 = obj2[prop]; if(prop1<prop2){ return -1; } else if(prop1>prop2){ return 1; } else { return 0; } } } var arr = [ {"name":"bsunhui","age":"23"}, {"name":"csunhui","age":"22"} ]; arr.sort(compare("age")); console.log(arr); //[{"name":"csunhui","age":"22"},{"name":"bsunhui","age":"23"}] arr.sort(compare("name")); console.log(arr); //[{"name":"bsunhui","age":"23"},{"name":"csunhui","age":"22"}]
compare
函数返回了一个闭包,这样就能够根据传入的属性进行排序。当比较字符串时,最好用 localeCompare()
方法来比较字符串(字符会按字母表的先后位置关系比较),不然比较的字符的是ascii码。注意数字字符串会被隐式转换为数值类型。
关键词:闭包、对象数组按属性排序、字符串比较
举例:
var a = "sun"; var b = ["sun","hui"];
如今须要将a
转换成大写,将b
中的每一个元素都转换成大写,一般的作法是:
console.log(a.toUpperCase()); // "SUN" for(var i=0;i<b.length;i++){ console.log(b[i].toUpperCase()); // "SUN" "HUI" }
这种作法至关于写了两套代码,看看下面的方式。
function toUpper(words){ var elements = [].concat(words); for(var i=0;i<elements.length;i++){ console.log(elements[i].toUpperCase()); } } toUpper("sun"); // "SUN" toUpper(["sun","hui"]); // "SUN" "HUI"
关键点是:concat
的用法,str.concat(arg)
的参数arg
若是是一个数组会把数组里的(不是嵌套数组的)每一项都拿出来做为新数组的元素。
关键词:concat
undefined
表示变量没有被声明或者声明了可是没有被赋值。null
表示这个变量不是一个值。undefined
。null
,只有程序员本身才能把一个值设置为null
。undefined
不可用,而null
是可用的。type of undefined
的值为undefined
。type of null
的值为object
。null
和undefined
的布尔值都为false。null == undefined
的值为true。null === undefined
的值为false。关键词:null、undefined
通常而言,对数组元素的操做咱们会用到这些函数:push
,pop
,unshift
,shift
,splice
。
var a = [1, 2, 3]; a.push(4); console.log(a); //[1,2,3,4] a.pop(); console.log(a); //[1,2,3] a.unshift(5); console.log(a); //[5,1,2,3] a.shift(); console.log(a); //[1,2,3] a.splice(0,2,6); console.log(a) //[6,3]
我在这里想介绍的是利用length
属性来进行数组元素的增减。
var a = [1, 2, 3]; a.length = 1; console.log(a); //[1] a.length = 5; console.log(a); //[1,undefined,undefined, undefined, undefined] a = [1,2,3]; a.splice(a.length/2,0,4,5,6); //在数组的中间插入元素 console.log(a); //[1,4,5,6,2,3]
值得注意的是以上全部方法都改变了原数组。咱们再来一个:
var a = [1,2,3]; var b = [4,5].concat(a); console.log(a); //[1,2,3] console.log(b); //[4,5,1,2,3]
使用concat
来增长数组元素就会返回一个新数组,不会改变原数组。
关键词:数组、length