Javascript 技法100第一篇

1. 神乎其技的 + 号

//使用 `+` 运算符可以快速将一个字符串数值转化为数字
console.log(typeof '1');  //string
console.log(typeof +'1');  //number

//Date类型会转化为number类型的时间戳,精确到ms
console.log(+new Date())  //1484219585488

//相比这种方式转换时间戳简便,Date.parse()只精确到s,ms的位置都为0
console.log(Date.parse(new Date()))  //'1484219585000'

//无法转换为有效的数字一般会得到NaN
console.log(+'abc') //NaN
console.log(+function(){})  //NaN

//数组有点奇特
console.log(+[1,2,3]);  //NaN
console.log(+[]);  //0
console.log(+[5]);  //5

//这还就导致了
console.log(++[[]][+[]]+[+[]])  //10

//为什么呢?一脸懵逼的你一定愿意看看推导

+[] = 0
  =>  ++[[]][+[]]+[+[]] = ++[[]][0]+[0] 

[[]][0] = []
  =>  ++[[]][0] = ++[] = [] + 1 = '' + '1' =  '1'

//++运算符得到的结果一定是number,所以有必要用 + 号再一次转换类型

++[] = +( [] + 1 ) = +'1' = 1
  =>  1 + [0] = '1' + '0' = '10'

2. 妙用数组length属性

var a = [1, 2, 3, 4];

console.log(a.length)   //4

//清空数组
a.length = 0;   //a = []

//截取数组
a.length = 2;   //a = [1, 2]

//扩张数组,用undefined填充
a.length = 5;   //a = [1, 2, 3, 4, undefined]

3. 两个数花式交换

//方案一
var a = 1, b =2;
a = [b, b = a][0];
console.log(a, b);  //2 1

//方案二
var a = 1, b =2;
a = a ^ b;
b = a ^ b;
a = a ^ b;
console.log(a,b)  //2 1

4. 在读取length的循环中缓存length

var a = [1, 2, 3];
for(var i = 0; i < a.length; i++){
    console.log(a[i])
}

尽管上面的做法没有异议,但每次循环都会额外做一个计算数组length的操作,数组足够小的时候这当然没有任何问题,但当它足够大,它就会成为拖垮性能的一个元凶。假如你尝试过在java中处理一个超大文件的每一个字节时使用上面的做法,你可能会懂得它对性能的破坏力有多大。

必要的时候采用下面这种做法吧:

var a = [1, 2, 3];
for(var i = 0, len = a.length; i < len; i++){
    console.log(a[i]);
}

5. 可动态指定地访问Object属性

var o = {
    name : 'cmx',
    age : 24
}
//可以这么访问一个key
console.log(o.name);

//还可以这么访问一个key
console.log(a['name']);

//再直白一点
var key = 'name';
console.log(a[key]);

看完你能知道应用场景吗?

6. 妙用And和Or

//设置变量的初始默认值
function(a){
    a = a || '默认值';
}

//代替if语句,利用短路特性,如果a变量是有效值(非undefined、null、""、false、0等),执行方法dosomething
a && dosomething(a);

//同理,a为无效值时执行dosomething函数
a || dosomething();

7. 数组拼接

存在两种方式去拼接两个数组:

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

//生成一个新的数组,不破坏原数组
a = a.concat(b)    //a=[1,2,3]

//以第一个参数数组作为this,第二个参数数组作为遍历参数,push进第一个数组,最终第一个数组为两个数组的拼接,第二个数组不改变
Array.prototype.push.apply(a, b)    //a=[1,2,3] b=[3]

//相当于
a.push(b[0], b[1], b[2]..)

什么时候用哪种呢?concat由于生成新的数组,必然是占内存的,但它不限制合并数组大小。而第二种方法存在合并数组个数限制(实际上是函数的形参个数限制,不超过65536),内存则相对于concat减少了一个合并后数组的大小。性能上相差不多。

8. 两个操作符typeof跟instanceof

  • typeof判断变量的类型,为一元运算符
  • instanceof判断变量的实例(原型链),为二元运算符

用法上,

console.log(typeof {name:'chenmuxin'}) //'object'

console.log({name:'chenmuxin'} instanceof Object)  //true

typeof返回的是一个全小写字符串,依据变量的类型可能会返回:

'object''number''string''undefined''function''boolean''symbol'

如你所见,不存在数组类型。数组返回的是'object',不仅数组,所有诸如DateRegExp都被作为object看待

'number'类型:

typeof 1
typeof(1)   //也可以这样使用,类似java
typeof 3.1415   //js的number类型统一了整型浮点型等
typeof NaN  //Not a number表示的是无穷和非数值,它是一个number类型
typeof Number(1)    //Number类直接返回的类型也是number

'undefined'类型:

typeof undefined
typeof i_am_undeclared  //一个未声明变量

var i_am_declared_but_undefined_value;  //一个已声明但未赋值变量
typeof i_am_declared_but_undefined_value

最容易迷惑情形:

typeof new Number(1)    //返回'object',使用new操作符时,诸如String、Boolean等引用类返回的是object
typeof Number(1)    //'number'

typeof null //返回'object',js诞生之时起,null就是跟object同类型,这显然不合适。但es6的提案typeof null == 'null'被否决,所以目前都还只能这么认识它

typeof /a/  //正则在不同浏览器上可能有不同类型,可能是function,也可能是object,标准是object。判断正则应使用instanceof

使用instanceof时,首先要保证对象可以new,只有允许new才会存在所谓实例。所以想使用instanceof判断一个基本变量的类型是不可行的。

console.log(1 instanceof Number) //false

数组无法使用typeof来判断,但可以使用instanceof(一定场景下会失败,例如多重iframe)

console.log([1] instanceof Array)  //true

当然也可以使用更稳妥的数组的方法:

console.log(Array.isArray([1])) //true

总结:像数组、正则等继承自Object但具体的对象(首先可以new),我们一般都可以用instanceof去判断。像很多基本变量以及它们的引用类型,我们就用typeof去判断。

9. in和for遍历

var a = [10,20,30];
var b = {name : 'cmx'}

//基本数组遍历
for(var i=0,length = a.length; i < length; i++){
    console.log(a[i]);
}

//of运算符每次循环直接得到数组值
for(var i of a){
    console.log(i);
}

//in运算符每次循环得到当前下标
for(var i in a){
    console.log(a[i]);
}

//in运算符遍历对象时,每次得到对象的key
for(var i in b){
    console.log(i);
    console.log(b[i]);
}

存在一些特殊情况,in遍历时,不仅会把数组(或对象)的每一个下标(key)遍历出来,一旦像下面这样定义了原型属性或方法

Array.prototype.what = function(){ }

in遍历还会把它们一起遍历出来,这里得到一个what。如果没有作必要的判断往往会导致如下:

for(var i in a){
    //打印a[what]出错
    console.log(a[i]);
}

要么不使用原型直接添加的方式,而使用Object.defineProperty并设置可枚举属性为false。要么就in遍历数组时,都判断当前i是否为number类型:

var a = [1, 2, 3];
Array.prototype.cmx = 5;

for(var i in a){
    //+i是因为i遍历出来的都是字符串形式。至于为什么不使用 i != NaN ,因为任意两个NaN不相等啊
    if(!Number.isNaN(+i)){
        console.log(i)
    }
}

因为原型扩展是不允许添加一个number类型的,所以上面的做法成立。

Array.prototype.1 = 1 //error

或者使用通用的方法(同时适合数组和对象)

for(var i in a){
    if(a.hasOwnProperty(i)){
        console.log(a[i])
    }
}

Object.prototype.hasOwnProperty()会忽略对象原型链上的属性和方法。

10. call和apply

我们先来看段代码:

function Chen(){
    this.name = '老陈';
    
    this.sayHello = function(hello){
        console.log('我是老陈的方法');
        console.log(hello + ', I am ' + this.name);
    }
}

function Huang(){
    this.name = '老黄';
    
    this.sayHello = function(hello){
        console.log('我是老黄的方法');
        console.log(hello + ', I am ' + this.name);
    }
}

var chen = new Chen();
chen.sayHello('你好');    //我是老陈的方法  你好, I am 老陈

var huang = new Huang();

chen.sayHello.call(huang,'Hello');  //我是老陈的方法  Hello, I am 老黄

chen.sayHello.apply(huang,['Hi'])  //我是老陈的方法  Hi, I am 老黄

callapply非常像,都是为一个函数指定上下文,并传入参数。唯一的区别在于参数参入的形式。

Function.prototype.call(thisObj, ...args)
Function.prototype.apply(thisObj[, argArray])

call()thisObj作为上下文带上若干个指定的参数值调用某个函数或方法,参数必须展开。

apply()thisObj作为上下文带上一个数组或累数组参数调用某个函数或方法,参数必须组合成一个数组。

由于只是形式不同,故以其中一个作为示例说明:

chen.sayHello.call(huang, 'Hello')

huang作为上下文,调用chensayHello方法,由于chen.sayHello()调用的this.name已经被更改为huang.name,所以得到上面的执行结果。

call或者apply来模拟一下继承:

function Boy(name, love){
    this.name = name;
    this.lova = love;
    
    this.say = function(){
        console.log('I am '+ name +',I like '+ love +',what about you?');
    }
}

function BadBoy(name, love){
    Boy.call(this, name, love);
    
    this.do = function(){
        console.log('I made a mistake!');
    }
}

var badBoy = new BadBoy('张三','打架');
badBoy.say();   //I am 张三,I like 打架,what about you?
badBoy.do();    //I made a mistake!

var boy = new Boy('李四','学习');
boy.say();  //I am 李四,I like 学习,what about you?
boy.do();   //boy.do is not a function

(转载请注明出处,简书-沐心chen)

    原文作者:blurooo
    原文地址: https://www.jianshu.com/p/47837c732ae0
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞