JavaScript开发问题搜集

# 概述

JavaScript基于原型的面向对象系统参考了Self语言和Smalltalk语言。原型模式不单是一种设计模式,也被称为一种编程泛型。基于原型链的委托机制就是原型继承的本质。

鸭子类型的通俗说法是:“如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。”鸭子类型指导我们只关注对象的行为,而不关注对象本身,也就是关注HAS-A,而不是IS-A。在动态类型语言的面向对象设计中,鸭子类型的概念至关重要。

JavaScript这种将函数作为一等对象的语言中,函数本身也是对象,函数用来封装行为并且能够被四处传递。JavaScript的函数既可以作为普通函数被调用,也可以作为构造器被调用。当使用new运算符来调用函数时,此时的函数就是一个构造器。函数的length属性就是一个只读的属性,表示形参的个数。在JavaScript中,如果函数没有return值,则默认return this。

JavaScript是一门完整的面向对象的编程语言,但这门语言同时也拥有许多函数式语言的特性。函数式语言有Lambda表达式闭包高阶函数等特性。闭包的形成与变量的作用域以及变量的生存周期密切相关。

IE浏览器中,由于BOMDOM中的对象是使用C++COM对象的方式实现的,而COM对象的垃圾收集机制采用的是引用计数策略。跟DOM节点相关的操作往往是非常消耗性能的。

鸭子类型举例

  var duck = {
      duckSinging: function(){
        console.log( ’嘎嘎嘎’ );
      };
  }

  var chicken = {
      duckSinging: function(){
        console.log( ’嘎嘎嘎’ );
      }
  };

  var choir = [];    //合唱团

  var joinChoir = function( animal ){
      if ( animal && typeof animal.duckSinging === 'function' ){
          choir.push( animal );
          console.log( ’恭喜加入合唱团’ );
          console.log( ’合唱团已有成员数量:' + choir.length );
      }
  };

  joinChoir( duck );    //恭喜加入合唱团
  joinChoir( chicken );    //恭喜加入合唱团
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 原型编程范型规则

原型编程范型至少包括以下基本规则:

❏ 所有的数据都是对象。

❏ 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。

❏ 对象会记住它的原型。

❏ 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。

# 所有数据都是对象

JavaScript中的根对象是Object.prototype对象。Object.prototype对象是一个的对象。

  var obj1 = new Object();
  var obj2 = {};

  console.log( Object.getPrototypeOf( obj1 ) === Object.prototype );    //输出:true
  console.log( Object.getPrototypeOf( obj2 ) === Object.prototype );    //输出:true
1
2
3
4
5

# 对象会记住它的原型

JavaScript的真正实现来说,其实并不能说对象有原型,而只能说对象的构造器有原型。对于“对象把请求委托给它自己的原型”这句话,更好的说法是对象把请求委托给它的构造器的原型。

JavaScript给对象提供了一个名为__proto__的隐藏属性,某个对象的__proto__属性默认会指向它的构造器的原型对象,即{Constructor}.prototype。实际上,__proto__就是对象跟“对象构造器的原型”联系起来的纽带。

对象的__proto__属性会指向构造器的原型

  var a = new Object();
  console.log ( a.__proto__=== Object.prototype );    // 输出:true
1
2

# 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型

这条规则即是原型继承的精髓所在。

# this

跟别的语言大相径庭的是,JavaScriptthis总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。

this的指向大致可以分为以下4种。

❏ 作为对象的方法调用。

❏ 作为普通函数调用。

❏ 构造器调用。

Function.prototype.callFunction.prototype.apply调用。

# 作为对象的方法调用

当函数作为对象的方法被调用时,this指向该对象

  var obj = {
      a: 1,
      getA: function(){
        alert ( this === obj );    // 输出:true
        alert ( this.a );    // 输出: 1
      }
  };

  obj.getA();
1
2
3
4
5
6
7
8
9

# 作为普通函数被调用

当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this总是指向全局对象。在浏览器的JavaScript里,这个全局对象是window对象。

指向window对象

  window.name = 'globalName';

  var getName = function(){
      return this.name;
  };

  console.log( getName() );    // 输出:globalName
1
2
3
4
5
6
7

or

  window.name = 'globalName';

  var myObject = {
      name: 'sven',
      getName: function(){
        return this.name;
      }
  };

  var getName = myObject.getName;
  console.log( getName() );    // globalName
1
2
3
4
5
6
7
8
9
10
11

指向window对象

  var obj = {
      myName: 'sven',
      getName: function(){
        return this.myName;
      }
  };

  console.log( obj.getName() );    // 输出:'sven'

  var getName2 = obj.getName;
  console.log( getName2() );    // 输出:undefined
1
2
3
4
5
6
7
8
9
10
11

指向window对象

  <html>
      <body>
        <div id="div1">我是一个div</div>
      </body>
      <script>

      var getId = document.getElementById;
      getId( 'div1' );

      </script>
  </html>
1
2
3
4
5
6
7
8
9
10
11

指向window对象

  <html>
      <body>
        <div id="div1">我是一个div</div>
      </body>
      <script>

      window.id = 'window';

      document.getElementById( 'div1' ).onclick = function(){
        alert ( this.id );        // 输出:'div1'
        var callback = function(){
            alert ( this.id );        // 输出:'window'
        }
        callback();
      };

      </script>
  </html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

指向div的办法

  document.getElementById( 'div1' ).onclick = function(){
      var that = this;    // 保存div的引用
      var callback = function(){
        alert ( that.id );    // 输出:'div1'
      }
      callback();
  };
1
2
3
4
5
6
7

# 构造器调用

除了宿主提供的一些内置函数,大部分JavaScript函数都可以当作构造器使用。构造器里的this就指向返回的这个对象

  var MyClass = function(){
      this.name = 'sven';
  };

  var obj = new MyClass();
  alert ( obj.name );     // 输出:sven
1
2
3
4
5
6

但用new调用构造器时,还要注意一个问题,如果构造器显式地返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的this。如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会造成上述问题。

  var MyClass = function(){
      this.name = 'sven';
      return {    // 显式地返回一个对象
        name: 'anne'
      }
  };

  var obj = new MyClass();
  alert ( obj.name );     // 输出:anne
1
2
3
4
5
6
7
8
9
  var MyClass = function(){
      this.name = 'sven'
      return 'anne';    // 返回string类型
  };

  var obj = new MyClass();
  alert ( obj.name );     // 输出:sven
1
2
3
4
5
6
7

# Function.prototype.call或Function.prototype.apply调用

跟普通的函数调用相比,用Function.prototype.callFunction.prototype.apply可以动态地改变传入函数的this。这两个方法的应用也非常广泛,能熟练运用这两个方法,是我们真正成为一名JavaScript程序员的重要一步。

  var obj1 = {
      name: 'sven',
      getName: function(){
        return this.name;
      }
  };

  var obj2 = {
      name: 'anne'
  };

  console.log( obj1.getName() );     // 输出: sven
  console.log( obj1.getName.call( obj2 ) );    // 输出:anne
1
2
3
4
5
6
7
8
9
10
11
12
13
  document.getElementById = (function( func ){
      return function(){
        return func.apply( document, arguments );
      }
  })( document.getElementById );

  var getId = document.getElementById;
  var div = getId( 'div1' );

  alert (div.id);    // 输出: div1
1
2
3
4
5
6
7
8
9
10

它们的作用一模一样,区别仅在于传入参数形式的不同。

apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数。

  var func = function( a, b, c ){
      alert ( [ a, b, c ] );    // 输出 [ 1, 2, 3 ]
  };

  func.apply( null, [ 1, 2, 3 ] );
1
2
3
4
5

call传入的参数数量不固定,跟apply相同的是,第一个参数也是代表函数体内的this指向,从第二个参数开始往后,每个参数被依次传入函数。

  var func = function( a, b, c ){
      alert ( [ a, b, c ] );    // 输出 [ 1, 2, 3 ]
  };

  func.call( null, 1, 2, 3 );
1
2
3
4
5

当使用call或者apply的时候,如果我们传入的第一个参数为null,函数体内的this会指向默认的宿主对象,在浏览器中则是window

  var func = function( a, b, c ){
      alert ( this === window );    // 输出true
  };

  func.apply( null, [ 1, 2, 3 ] );
1
2
3
4
5
  var func = function( a, b, c ){
      "use strict";
      alert ( this === null );     // 输出true
  }

  func.apply( null, [ 1, 2, 3 ] );
1
2
3
4
5
6

# call或apply详细用法

# 最常用法是改变this指向

  var obj1 = {
      name: 'sven'
  };

  var obj2 = {
      name: 'anne'
  };

  window.name = 'window';

  var getName = function(){
      alert ( this.name );
  };

  getName();    // 输出: window
  getName.call( obj1 );    // 输出: sven
  getName.call( obj2 );    // 输出: anne
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

使用call修正this

  document.getElementById( 'div1' ).onclick = function(){
      alert( this.id );        // 输出:div1
  };
1
2
3
  document.getElementById( 'div1' ).onclick = function(){
      alert( this.id );            // 输出:div1
      var func = function(){
        alert ( this.id );        // 输出:undefined
      }
      func();
  };
1
2
3
4
5
6
7
  document.getElementById( 'div1' ).onclick = function(){
      var func = function(){
        alert ( this.id );        // 输出:div1
      }
      func.call( this );
  };
1
2
3
4
5
6

再比如例子

  document.getElementById = (function( func ){
      return function(){
        return func.apply( document, arguments );
      }
  })( document.getElementById );

  var getId = document.getElementById;
  var div = getId( 'div1' );
  alert ( div.id );    // 输出: div1
1
2
3
4
5
6
7
8
9

# Function.prototype.bind

大部分高级浏览器都实现了内置的Function.prototype.bind,用来指定函数内部的this指向。

  Function.prototype.bind = function( context ){
      var self = this;        // 保存原函数
      return function(){        // 返回一个新的函数
        return self.apply( context, arguments );    // 执行新的函数的时候,会把之前传入的context
                                                // 当作新函数体内的this
      }
  };

  var obj = {
      name: 'sven'
  };

  var func = function(){
      alert ( this.name );    // 输出:sven
  }.bind( obj);

  func();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  Function.prototype.bind = function(){
      var self = this,    // 保存原函数
        context = [].shift.call( arguments ),    // 需要绑定的this上下文
        args = [].slice.call( arguments );    // 剩余的参数转成数组
      return function(){    // 返回一个新的函数
        return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );
            // 执行新的函数的时候,会把之前传入的context当作新函数体内的this
            // 并且组合两次分别传入的参数,作为新函数的参数
        }
      };

  var obj = {
      name: 'sven'
  };

  var func = function( a, b, c, d ){
      alert ( this.name );        // 输出:sven
      alert ( [ a, b, c, d ] )    // 输出:[ 1, 2, 3, 4 ]
  }.bind( obj, 1, 2 );

  func( 3, 4 );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 借用其他对象的方法

借用方法的第一种场景是“借用构造函数”,通过这种技术,可以实现一些类似继承的效果。

  var A = function( name ){
      this.name = name;
  };

  var B = function(){
      A.apply( this, arguments );
  };

  B.prototype.getName = function(){
      return this.name;
  };

  var b = new B( 'sven' );
  console.log( b.getName() );  // 输出: 'sven'
1
2
3
4
5
6
7
8
9
10
11
12
13
14

借用方法的第二种运用场景跟我们的关系更加密切。在操作arguments的时候,我们经常非常频繁地找Array.prototype对象借用方法。

往类数组对象arguments身上添加新元素

  (function(){
      Array.prototype.push.call( arguments, 3 );
      console.log ( arguments );    // 输出[1,2,3]
  })( 1, 2 );
1
2
3
4

我们可以把“任意”对象传入Array.prototype.push。如果这个对象满足下面条件 :❏ 对象本身要可以存取属性;❏ 对象的length属性可读写。

  var a = {};
  Array.prototype.push.call( a, 'first' );

  alert ( a.length );    // 输出:1
  alert ( a[ 0 ] );    // first
1
2
3
4
5
  var a = 1;
  Array.prototype.push.call( a, 'first' );
  alert ( a.length );      // 输出:undefined
  alert ( a[ 0 ] );    // 输出:undefined
1
2
3
4
  var func = function(){};
  Array.prototype.push.call( func, 'first' );

  alert ( func.length );
  // 报错:cannot assign to read only property ‘length' of function(){}
1
2
3
4
5

# 闭包

闭包的形成与变量的作用域以及变量的生存周期密切相关。

# 变量作用域

变量的作用域,就是指变量的有效范围。当在函数中声明一个变量的时候,如果该变量前面没有带上关键字var,这个变量就会成为全局变量。反之则是局部变量。变量的搜索是从内到外而非从外到内的。

  var func = function(){
      var a = 1;
      alert ( a );     // 输出: 1
  };
  func();
  alert ( a );     // 输出:Uncaught ReferenceError: a is not defined
1
2
3
4
5
6

# 变量的生存周期

全局变量的生存周期当然是永久的,除非我们主动销毁这个全局变量。在函数内用var关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,它们都会随着函数调用的结束而被销毁。

请特别注意这个例子,局部变量a并未销毁

  var func = function(){
      var a = 1;
      return function(){
        a++;
        alert ( a );
      }
  };

  var f = func();

  f();    // 输出:2
  f();    // 输出:3
  f();    // 输出:4
  f();    // 输出:5
1
2
3
4
5
6
7
8
9
10
11
12
13
14

用途

  for ( var i = 0, len = nodes.length; i < len; i++ ){
      (function( i ){
        nodes[ i ].onclick = function(){
            console.log(i);    //按顺序输出0,1,2,3,4,5...
        }
      })( i )
  };
1
2
3
4
5
6
7

# 高阶函数

高阶函数是指至少满足下列条件之一的函数。

❏ 函数可以作为参数被传递;

❏ 函数可以作为返回值输出。

# 函数作为参数传递

把函数当作参数传递,这代表我们可以抽离出一部分容易变化的业务逻辑,把这部分业务逻辑放在函数参数中,这样一来可以分离业务代码中变化与不变的部分。其中一个重要应用场景就是常见的回调函数。

回调函数

  var getUserInfo = function( userId, callback ){
      $.ajax( 'http://xxx.com/getUserInfo? ' + userId, function( data ){
        if ( typeof callback === 'function' ){
            callback( data );
        }
      });
  }

  getUserInfo( 13157, function( data ){
      alert ( data.userName );
  });
1
2
3
4
5
6
7
8
9
10
11
  var appendDiv = function( callback ){
    for ( var i = 0; i < 100; i++ ){
        var div = document.createElement( 'div' );
        div.innerHTML = i;
        document.body.appendChild( div );
        if ( typeof callback === 'function' ){
            callback( div );
        }
    }
  };

  appendDiv(function( node ){
    node.style.display = 'none';
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 函数作为返回值输出

函数柯里化,先将数值保存,直到最后才一并计算

  var monthlyCost = 0;

  var cost = function( money ){
      monthlyCost += money;
  };

  cost( 100 );    // 第1天开销
  cost( 200 );    // 第2天开销
  cost( 300 );    // 第3天开销
  //cost( 700 );    // 第30天开销

  alert ( monthlyCost );      // 输出:600
1
2
3
4
5
6
7
8
9
10
11
12
  var cost = (function(){
      var args = [];

      return function(){
        if ( arguments.length === 0 ){
            var money = 0;
            for ( var i = 0, l = args.length; i < l; i++ ){
                money += args[ i ];
            }
            return money;
        }else{
            [].push.apply( args, arguments );
        }
      }

  })();

  cost( 100 );    // 未真正求值
  cost( 200 );    // 未真正求值
  cost( 300 );    // 未真正求值

  console.log( cost() );       // 求值并输出:600
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# Undefined类型

  • 使用只声明而未初始化的变量时,会返回“undefined”
  var a;
  console.log(a);  // undefined
1
2
  • 获取一个对象的某个不存在的属性(自身属性和原型链继承属性)时,会返回“undefined”
  var obj = {
    name: 'kingx'
  };
  console.log(obj.address);  // undefined
1
2
3
4
  • 函数没有明确的返回值时,却在其他地方使用了返回值,会返回“undefined”
  function foo() {}
  console.log(foo()); // undefined
1
2
  • 函数定义时使用了多个形式参数(后文简称为形参),而在调用时传递的参数的数量少于形参数量,那么未匹配上的参数就为“undefined”
  function foo(param1, param2, param3) {
    console.log(param3);
  }
  foo(1, 2);  // undefined
1
2
3
4

nullJavaScript中的关键字,而undefinedJavaScript中的一个全局变量,即挂载在window对象上的一个变量,并不是关键字。

# Number

JavaScript中,整数和浮点数都属于Number类型,它们都统一采用64位浮点数进行存储。

# parseInt()与Array.map()将字符串转成整型

  var arr = ['1', '2', '3', '4'];

  var result = arr.map(function (val) {  
      return parseInt(val, 10);
  });

  console.log(result);  // [1, 2, 3, 4]
1
2
3
4
5
6
7

# Number.isNaN()与isNaN()

isNaN()函数在判断是否为NaN时,需要先进行数据类型转换,只有在无法转换为数字时才会返回“true”

Number.isNaN()函数在判断是否为NaN时,只需要判断传入的值是否为NaN,并不会进行数据类型转换。

# 逗号运算符

不使用中间变量交换变量的值

  var a = 'a';
  var b = 'b';
  // 方案1
  a = [b, b = a][0];
  // 方案2
  a = [b][b = a, 0];
1
2
3
4
5
6

# instanceof

数组不仅是Array类型的实例,也是Object类型的实例。因此判断类型的时候我们先判断Array类型。

  var a =  [1, 2, 3];
  console.log(a instanceof Array);  // true
  console.log(a instanceof Object); // true

  var b = {name: 'kingx'};
  console.log(b instanceof Array);  // false
  console.log(b instanceof Object); // true
1
2
3
4
5
6
7

# 比较扩展

  [] == 0; // true
  [1] == 1; // true
  [2] == 2; // true
1
2
3

# !x == true的所有情况

  • 变量为null。
  • 变量为undefined。
  • 变量为空字符串' '。
  • 变量为数字0,包括+0、-0。
  • 变量为NaN。

# 构造函数中new操作符和this的区别

如果函数没有return值,则默认return this

上次更新: 2025/02/10, 20:20:37
最近更新
01
Git问题集合
01-29
02
安装 Nginx 服务器
01-25
03
安装 Docker 容器
01-25
更多文章>
×
×