Javascript 技法100第一篇

作者: blurooo | 来源:发表于2017-01-11 16:00 被阅读1086次

    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)

    相关文章

      网友评论

      本文标题:Javascript 技法100第一篇

      本文链接:https://www.haomeiwen.com/subject/kujrbttx.html