首页 Javascript 正文
412

Javascript中一些容易忽视的知识点(持续更新中...)

  • yiqingpeng
  • 2015-12-11
  • 0
  •  
1、switch语句使用全等比较。
var val = '123';
switch(val){
     case 123:
           //此处代码不会执行;
           break;
     case '123':
           //执行;
           break;
}
2、数组的length属性的值是最大索引加1,什么意思?看例子:
var arr=['a', 'b', 'c'];
arr[6] = 'd';
console.log(arr.length);//输出7,因为数组最大索引是6
3、parseInt("08")的结果:在IE8中是0,在chrome中是8。这是因为ie8将“08”看成是八进制,为了让IE8能正确转化“08”,可以通过指定parseInt的第二个参数(此参数表示使用哪种进制):parseInt("08", 10)。或者使用单目运算符(+):+"08"。

4、parseInt函数与正号运算符转化数值的差异(正号运算符的结果与Number(input)实例化的结果是一致的.):
console.log(parseInt("abc"));//NaN
console.log(parseInt(""));//NaN
console.log(parseInt(false));//NaN
console.log(parseInt(undefined));//NaN
console.log(parseInt(null));//NaN
console.log(parseInt("12abc"));//12
console.log(parseInt([]));//NaN
console.log(parseInt({}));//NaN

console.log(+"abcd");//NaN, 以下结果同样适用于Number(xxx);
console.log(+"");//0
console.log(+false);//0
console.log(+undefined);//NaN
console.log(+null);//0
console.log(+"12abcd");//NaN
console.log(+[]);//0
console.log(+{});//NaN
5、函数的length属性值保存的是函数形参的个数。
function test(a){
}
test(1, 2, 3);//不管调用的时候传入多少个参数,test.length都只会记录形参的个数。
//可以用arguments.length得到实参的个数。
console.log(test.length); //1
//其它一些例子
eval. length; //1
RegExp. length; //2
"".toString. length; //0
"abcd".length; //4
2..toString. length; //1,数值2后面加两个点是为了避免解析成小数,其实还有其它表示法:2['toString'],(2).toString, 2 .toString(2后面有一个空格)。
6、不要以为数组很单纯。看个例子
var arr = [1, 'a'];
arr.myattr = "hello";
console.log(arr.myattr); //hello, arr实为:[1, 'a', myattr:'hello'],该说它是数组呢,还是对象?其实本质来说数组就是一个特殊的对象,它的属性名都是整数型的。
7、变量声明提升与函数声明提升
var x=1;
function x(){}
console.log(x);//输出1
第一行var x=1是变量x声明并初始化,第二行是函数x声明。那么为什么函数声明没有覆盖变量的声明呢?那是因为变量声明与函数声明都会提升到当前作用域的最前面,然后才给x赋值。所以上面代码相当于这样:
var x;//变量声明提前
function x(){};//函数声明提前(声明提升的时候,函数声明总是在变量声明之后)
x=1; //所有声明提前之后,再进行赋值
console.log(x);
所以我们经常发现这个问题,就是函数声明即使写在代码的最后面,也是可以在声明之前调用函数的。而如果函数是用表达式方式定义的,则不可以在定义之前使用函数,如下
func1();//可以正确执行
func2();//发生"func2 is not a function"之类的错误,此时func2值是undefined
function func1(){};
var func2 = function(){};
再来看段代码
if(false){
   var x = 1;
}
console.log('x' in window);//true
上面的x虽然是在条件为false的代码块中声明的,但是它还是被提升到了前面,而且x的值并不是1,而是undefined。

8、构造函数返回值的问题
如果返回值是基本类型,那么会忽略它,并返回实例化对象;如果是引用类型,那么直接返回该引用值。
function Fac(){
  this.a=1;
  return {a:2}; //返回的是引用类型
}
var obj = new Fac();
console.log(obj.a);//2

function Fac2(){
  this.a=1;
  return 'a'; //返回的是基本类型
}
var obj2 = new Fac2();
console.log(obj2.a);//1

function Fac3(){
   this.a = 1;
   return function(){console.log(2);}; //返回的是函数,因为函数也是对象,所以并不会实例化Fac3
}
var obj3 = new Fac3();
console.log(typeof obj3); //"function"
new 操作符会影响 function 调用中 return 语句的行为。当 function 调用的时候有 new 作为前缀,如果 return 的结果不是一个对象/函数,那么新创建的对象将会被返回。

9、基本类型的对象化包装
var a='123', b='123', c= new String('123');
console.log(a==b);// true
console.log(a==c);//true
console.log(a===b);//true
console.log(a===c);//false
console.log(typeof a);//string
console.log(typeof c);//object
再来看一个例子:
var str ='test string';
str.func = function(){console.log('here');}
str.func(); //报错,str.func is not a function
str变量本来是一个基本类型,而代码第二行应用了对象方法操作,所以会将str包装成一个临时的string对象,并将func属性添加到这个临时对象上(相当(new String(str)).func = function(){...})。第三行同样也是创建了一个包装str的临时对象(相当(new String(str)).func()),这个对象与第二行创建的临时对象完全是两个不同的对象,所以它没有func属性。

10、对象的内置get方法应用
Number.prototype = Object.defineProperty(
  Number.prototype, "double", {
    get: function (){return (this + this)} 
  }
);//自定义double属性

Number.prototype =  Object.defineProperty(
  Number.prototype, "square", {
    get: function (){return (this * this)} 
  }
);//自定义square属性
(3).double.square;//36
ES5规定,每个对象的属性都有一个取值方法get,用来自定义该属性的读取操作。

11、很简单的清空数组方法
var a=[1,2];
a.length = 0;
console.log(a);//[]
12、函数的三种定义方式 在 JavaScript 中,function 对象的创建方式有三种:
function 声明、function 表达式和使用 Function 构造器。
通过这三种方法创建出来的 function 对象的 [[scope]]属性的值会有所不同,从而影响 function 执行过程中的作用域链。下面具体讨论这三种情况。
一、function 声明 function 声明的格式是 function funcName() {}。使用 function 声明的 function 对象是在进入执行上下文时的变量初始化过程中创建的。该对象的 [[scope]]属性的值是它被创建时的执行上下文对应的作用域链。
二、function 表达式 function 表达式的格式是 var funcName = function() {}。使用 function 表达式的 function 对象是在该表达式被执行的时候创建的。该对象的 [[scope]]属性的值与使用 function 声明创建的对象一样。
三、Function 构造器 对于 Function 构造器,大家可能比较陌生。声明一个 function 时,通常使用前两种方式。该方式的格式是 var funcName = new Function(p1, p2,..., pn, body),其中 p1、p2 到 pn 表示的是该 function 的形式参数,body 是 function 的内容。使用该方式的 function 对象是在构造器被调用的时候创建的。该对象的 [[scope]]属性的值总是一个只包含全局对象的作用域链。
function 对象的 length 属性可以用来获取声明 function 时候指定的形式参数的个数。 参考JavaScript 技巧与高级特性

13、在全局环境中,用var关键字声明的变量与未声明的变量有啥区别?如下:
var a=1;
b=2;
var关键字对于a与b来说有什么区别?答案是a才是真正意义上的变量,而b不是,尽管它从表现上来看很像变量(有些书上直接将其视为全局变量也是不正规的说法),那么b是什么呢?它就是全局对象的一个属性,这很重要。通过var声明的变量是会被提前到作用域的顶端的,而没有var声明的“变量”是不会有这种待遇的。下面代码运行的结果可以说明这个问题:
console.log(a);//undefined
console.log(b);// b is not defined
var a=1;
b=2;
delete b;//true
delete a;//false
可以看到,我们可以在变量a声明之前访问到变量a,但是b却不能访问到。再看一下对两个“变量”进行delete操作时,b可以被删除,说明b是全局对象的一个属性,但是变量a不可删除,因为var声明的变量都带有{dontDelete}特性,它是不能被删除的。但是这里有一点要注意的是eval函数中的var定义的变量是可以被delete操作的,所以在firebug中运行上面例子发现a也是可以被删除的,因为firebug是用的eval函数执行js代码。

14、arguments对象
先看一个例子:
function b(x, y, a) { 
    arguments[2] = 10; 
    alert(a); //弹出10.

b(1, 2, 3);
修改arguments元素的值之后,形参a的值也跟着改了。这个arguments是个很奇怪的东西,它是个伪数组,其实应该称之为对象但是在用for in遍历arguments对象的时候,每种浏览器又有不一样的实现方式,所以最好用for循环。关于arguments有下面三点特性:
a)函数中所有参数都放在arguments对象中,并且可以用下标像数组一样访问到每个参数,利用length属性可以得到实参的个数。
b)函数的命名参数(形参)只是为了方便,其实有没有都无所谓,但是命名参数的顺序决定了命名参数与arguments中实参的对应关系。
c)arguments的值永远与对应命名参数的值同步,但它们的内存空间是独立的。没有赋值的命名参数被自动赋于undefined值。
关于arguments的另一个例子:
var baz = 'a', foo = {
    bar: function() { return this.baz; },
    baz: 1
  };
  (function(){
    arguments.baz = function(){};
    return typeof arguments[0]();
  })(foo.bar); //output "function"; 注意this的指向是arguments对象。

15、arguments.callee使用时要注意一些地方:
首先,如果Strict Mode开启的话,arguments.callee是禁用的。
其次,在匿名函数中尽量不使用arguments.callee进行递归,否则会造成性能问题,原因是解析器不能将这类函数当做内联函数处理了,并且还会影响闭包。

16、有名函数表达式的一点浏览器兼容问题。
var a=1,  b = function a(x){
         x && a(--x);
     };
b(1);
以上代码在IE浏览器中将会报错,在IE中会将a函数视为函数声明,而其它浏览器则视为函数表达式。函数声明会覆盖变量声明,但不会覆盖变量赋值,调用a(--x)时,此时a的值是1。而其它浏览器仍可以在b函数中正确引用到a函数。

17、有名函数表达式中函数名的作用域问题。
(function f(f){
    var f='a';
    console.log(f); //output 'a'; 优先搜索局部变量。
})(1);
另一个类似的例子:
(function f(){
    function f(){return 1;};
    return f();
    function f(){return 2;};
})();//结果是2,首先函数声明会提升,所以return语句并不会中断其后面的函数声明语句; 其次,局部作用域优先级高于有名函数的函数名。
一个函数就是一个作用域,在其作用域内查找变量的顺序是:
1. 局部变量(var声明的)
2. 函数参数
3. 此函数名
有名函数表达式的函数名只能在此函数体内引用,外部不能使用这个函数名(但是IE8并不如此。)

18、使用new构造对象和使用工厂模式(也称模块模式)构造对象。
new 模式:
function Func(){
    this.abc = 'abc';
    if (typeof this.say !== 'function') { //避免在Func类外部定义类方法
        Func.prototype.say = function (){}
    }
}
var obj = new Func();
工厂模式:
function Factory(){
     var obj = {}; // 或者 obj = new Object();
     obj.func = function(){};
     return obj;
}

new模式可以很方便地使用原型链进行继承,而工厂模式每次都是重新生成一个完整的对象,这样就无法复用相同的方法,造成内存浪费。
有一种改进型的工厂模式可以避免重复定义方法:
function Factory(){
    var obj = {};
    obj.func = func; //func为全局函数
    return obj;
}
function func(){
    this.abc = 'abc';
}
改进之后,虽然达到了方法重用的目的,但是却污染了全局作用域,着实也好不到哪里去。

19、事件委托(将事件处理程序绑定在目标元素的父结点上)在某些情况下可以有效提升性能。
比如常见的表格单元格事件,如果每个单元格单独绑定事件,会因为表格数据量大而造成绑定事件耗费内存多。改进的方法是将事件绑定在table元素上,使用jquery可以很方便实现事件委托,例如:$('table').on('click', 'td', function(){});

20、finally语句块可以在return之后被执行。
function a(){
    try{
        //todo
        return;
    }catch(e){
        //todo
    }finally{
        console.log('finally');//output 'finally'
    }
}

20、谨慎使用for ... in 语句对数组进行遍历。
for ... in 是用来遍历对象的属性的,然而因为Array是一种特殊的对象,所以for...in也可作用于数组,但是for...in遍历属性的顺序是未知的,在对数组遍历的时候可能并不能按索引顺序遍历。

22、fn(1) == 1; fn(1)(2) == 3; fn(1)(0)(0)(0)(3) == 4; fn(1)(2)(3)(4) == 10;满足以上条件的fn如何编写:
function fn(){
    var buff = Array.prototype.slice.call(arguments);
    function f(){
        Array.prototype.push.apply(buff,Array.prototype.slice.call(arguments));
        return f;
    }
    f.toString = function(){
        return buff.reduce((acc, cur) => acc + cur);
    };
    return f;
}
//可能还有更好的实现方法,此法仅供参考。
23、如何在同一个js文件中实现webWorker线程?
通常实现一个worker线程,必须单独给worker写一个js脚本,类似 var worker = new Worker('mywork.js');
如果不想单独创建一个js文件,同时又想使用worker线程的话,就需要一些小技巧。
要达到此目的,必须借助两个特性:function.toString与URL.createObjectURL, 示例如下(来源于网络):
function workerFactory(fn) {
    var worker = new Worker(
        URL.createObjectURL(new Blob([
           'self.onmessage = ' + fn.toString()], {
           type: 'application/javascript'
        })
    ));
    return worker;
}

var worker = workerFactory(function(e){
    //result = handle(e);
    return self.postMessage(result);
});
24、箭头函数中的this不一样。
var obj1={
     p:'a', 
     f:()=>{
           console.log(this.p)
     }
};
obj1.f(); // output 'undefined';
var obj2={
     p:'b', 
     f: function(){
           console.log(this.p)
     }
};
obj2.f(); // output 'b';
这么看来,箭头函数中的this指向的对象是定义此函数时的作用域。那么apply或call能改变此行为吗?答案是否定的。 比如调用obj1.f.call(obj1)依然得到是undefined. 所以,有些时候传入回调函数就有点讲究了。比如使用jQuery.each的这个例子:
jQuery('ul>li').each((i,e)=>{
    console.log(this); //你会发现,此this为全局对象Window
    //而如果不是箭头函数,可以正常指向每一个li元素。
});
25、addEventListener方法中的第三个参数中的passive参数有什么用?MDN的解释有点晦涩,举个例子吧,比如我们监听了页面touchstart事件,并将passive设置为false,并且在此监听函数里面有个很耗时的ajax请求,那么在要滑动页面的时候,它必须等到ajax请求完成才能让页面执行滑动行为,给人感觉就会显得很卡顿。产生这样的情况,是因为浏览器无法事先知道监听函数里什么时候会调用preventDefault,所以它必须等代码完成执行才能决定是否执行默认行为。为了解决这个问题,所以才引入了passive参数,由开发者来告之浏览器此监听函数有没有调用preventDefault, 当passive=true时,表示不用理会监听函数中是否有preventDefault, 此时浏览器就可以不用等待函数立刻执行默认行为了。对于不支持passive参数的浏览器,需要确保传入的参数是正确的,那么可以这样写一个检测方法:
var supportsPassive = false;
try {
  var opts = Object.defineProperty({}, 'passive', {
    get: function() {
      supportsPassive = true;
    }
  });
  window.addEventListener("test", null, opts);
} catch (e) {}

// Use our detect's results. 
// passive applied if supported, capture will be false either way.
elem.addEventListener(
  'touchstart',
  fn,
  supportsPassive ? { passive: true } : false
); 
这段代码很巧妙地检测是否支持passive参数,值得借鉴在其它地方。

正在加载评论...