JavaScript中的new 操作符(转)总结

JavaScript是一门基于原型的语言,但它却拥有一个 new 操作符使得其看起来象一门经典的面对对象语言。那样也迷惑了程序员们,导致一些有问题的编程模式。

 

1、其实你永远不需要在JavaScript使用 new Object()。用字面量的形式{}去取代吧。

2、同理,不要使用 new Array() ,而代之以字面量[]。JavaScript中的数组并不象Java中的数组那样工作的,使用类似Java的语法只会让你糊涂。

3、同理不用使用 new Number, new String, 或者 new Boolean。这些的用法只会产生无用的类型封装对象。就直接使用简单的字面量吧。

4、不要使用 new Function 去创建函数对象。用函数表达式更好。比如:

frames[0].onfocus = new Function(”document.bgColor='antiquewhite'”)

更好的写法是:

frames[0].onfocus = function () {document.bgColor = ‘antiquewhite';};

第二种形式让脚本编译器更快的看到函数主体,于是其中的语法错误也会更快被检测出来。有时候程序员使用 new Function 是因为他们没有理解内部函数是如何工作的。

 

selObj.onchange = new Function(”dynamicOptionListObjects[”+dol.index+”].change(this)”);

如果我们让用字符串做函数体,编译器不能看到它们。如果我们用字符串表达式做函数体,我们同样也看不到它们。

更好的方式就是不要盲目编程。通过制造一个返回值为函数的函数调用,我们可以明确的按值传递我们想要绑定的值。这允许我们在循环中初始化一系列 selObj 对象。

selObj.onchange = function (i) {
        return function () {dynamicOptionListObjects[i].change(this);};
                               }(dol.index);

 

直接对一个函数使用new永远不是一个好主意。比如, new function 对构造新对象没有提供什么优势。

myObj = new function () {this.type = ‘core';};

更好的方式是使用对象字面量,它更轻巧,更快捷。

myObj = {type: ‘core'};

 

假如我们需要创建的对象包含的方法需要访问私有变量或者函数,更好的方式仍然是避免使用new.

var foo = new function() {
   function processMessages(message) {alert(”Message: ” + message.content);}
   this.init = function() {subscribe(”/mytopic”, this, processMessages);}
                           }

 

 

通过使用 new 去调用函数,对象会持有一个无意义的原型对象。这只会浪费内存而不会带来任何好处。如果我们不使用new,我们就不用在对象链维护一个无用的prototype对象。所以我们可以用()来正确的调用工厂函数。

var foo = function () {
function processMessages(message) {alert(”Message: ” + message.content);}
return {init: function () {
                      subscribe(”/mytopic”, this, processMessages);
                          }
};
}();

所以原则很简单:唯一应该要用到new操作符的地方就是调用一个古老的构造器函数的时候。当调用一个构造器函数的时候,是强制要求使用new的。有时候可以来new一下, 有的时候还是不要了吧。

 

引用:详细出处参考:http://www.jb51.net/article/9998.htm

 

this、new、call和apply的相关问题

  讲解this指针的原理是个很复杂的问题,如果我们从javascript里this的实现机制来说明this,很多朋友可能会越来越糊涂,因此本篇打算换一个思路从应用的角度来讲解this指针,从这个角度理解this指针更加有现实意义。

  下面我们看看在java语言里是如何使用this指针的,代码如下:

复制代码
public class Person {
    
    private String name;
    private String sex;
    private int age;
    private String job;

    public Person(String name, String sex, int age, String job) {
        super();
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.job = job;
    }

    private void showPerson(){
        System.out.println("姓名:" + this.name);
        System.out.println("性别:" + this.sex);
        System.out.println("年龄:" + this.age);
        System.out.println("工作:" + this.job);
    }

    public void printInfo(){
        this.showPerson();
    }
    
    public static void main(String[] args) {
        Person person = new Person("马云", "男", 46, "董事长");
        person.printInfo();
    }

}

//姓名:马云
//性别:男
//年龄:46
//工作:董事长
复制代码

  

  上面的代码执行后没有任何问题,下面我修改下这个代码,加一个静态的方法,静态方法里使用this指针调用类里的属性,如下图所示:

 

  我们发现IDE会报出语法错误“Cannot use this in a static context”,this指针在java语言里是不能使用在静态的上下文里的。

  在面向对象编程里有两个重要的概念:一个是类,一个是实例化的对象,类是一个抽象的概念,用个形象的比喻表述的话,类就像一个模具,而实例化对象就是通过这个模具制造出来的产品,实例化对象才是我们需要的实实在在的东西,类和实例化对象有着很密切的关系,但是在使用上类的功能是绝对不能取代实例化对象,就像模具和模具制造的产品的关系,二者的用途是不相同的。

  有上面代码我们可以看到,this指针在java语言里只能在实例化对象里使用,this指针等于这个被实例化好的对象,而this后面加上点操作符,点操作符后面的东西就是this所拥有的东西,例如:姓名,工作,手,脚等等。

  其实javascript里的this指针逻辑上的概念也是实例化对象,这一点和java语言里的this指针是一致的,但是javascript里的this指针却比java里的this难以理解的多,究其根本原因我个人觉得有三个原因:

  原因一:javascript是一个函数编程语言,怪就怪在它也有this指针,说明这个函数编程语言也是面向对象的语言,说的具体点,javascript里的函数是一个高阶函数,编程语言里的高阶函数是可以作为对象传递的,同时javascript里的函数还有可以作为构造函数,这个构造函数可以创建实例化对象,结果导致方法执行时候this指针的指向会不断发生变化,很难控制。

  原因二:javascript里的全局作用域对this指针有很大的影响,由上面java的例子我们看到,this指针只有在使用new操作符后才会生效,但是javascript里的this在没有进行new操作也会生效,这时候this往往会指向全局对象window。

  原因三:javascript里call和apply操作符可以随意改变this指向,这看起来很灵活,但是这种不合常理的做法破坏了我们理解this指针的本意,同时也让写代码时候很难理解this的真正指向

  上面的三个原因都违反了传统this指针使用的方法,它们都拥有有别于传统this原理的理解思路,而在实际开发里三个原因又往往会交织在一起,这就更加让人迷惑不解了,今天我要为大家理清这个思路,其实javascript里的this指针有一套固有的逻辑,我们理解好这套逻辑就能准确的掌握好this指针的使用。

  我们先看看下面的代码:

复制代码
<script type="text/javascript">
    this.a = "aaa";
    console.log(a);//aaa
    console.log(this.a);//aaa
    console.log(window.a);//aaa
    console.log(this);// window
    console.log(window);// window
    console.log(this == window);// true
    console.log(this === window);// true
</script>
复制代码

  在script标签里我们可以直接使用this指针,this指针就是window对象,我们看到即使使用三等号它们也是相等的。全局作用域常常会干扰我们很好的理解javascript语言的特性,这种干扰的本质就是:

  在javascript语言里全局作用域可以理解为window对象,记住window是对象而不是类,也就是说window是被实例化的对象,这个实例化的过程是在页面加载时候由javascript引擎完成的,整个页面里的要素都被浓缩到这个window对象,因为程序员无法通过编程语言来控制和操作这个实例化过程,所以开发时候我们就没有构建这个this指针的感觉,常常会忽视它,这就是干扰我们在代码里理解this指针指向window的情形。

  干扰的本质还和function的使用有关,我们看看下面的代码:

复制代码
<script type="text/javascript">
    function ftn01(){
       console.log("I am ftn01!");
    }
    var ftn02 = function(){
        console.log("I am ftn02!");
    }
</script>    
复制代码

 

  上面是我们经常使用的两种定义函数的方式,第一种定义函数的方式在javascript语言称作声明函数,第二种定义函数的方式叫做函数表达式,这两种方式我们通常认为是等价的,但是它们其实是有区别的,而这个区别常常会让我们混淆this指针的使用,我们再看看下面的代码:

复制代码
<script type="text/javascript">
    console.log(ftn01);//ftn01()  注意:在firebug下这个打印结果是可以点击,点击后会显示函数的定义
    console.log(ftn02);// undefined
    function ftn01(){
       console.log("I am ftn01!");
    }
    var ftn02 = function(){
        console.log("I am ftn02!");
    }
</script>    
复制代码

 

  这又是一段没有按顺序执行的代码,先看看ftn02,打印结果是undefined,undefined我在前文里讲到了,在内存的栈区已经有了变量的名称,但是没有栈区的变量值,同时堆区是没有具体的对象,这是javascript引擎在预处理(群里东方说预处理比预加载更准确,我同意他的说法,以后文章里我都写为预处理)扫描变量定义所致,但是ftn01的打印结果很令人意外,既然打印出完成的函数定义了,而且代码并没有按顺序执行,这只能说明一个问题:

  在javascript语言通过声明函数方式定义函数,javascript引擎在预处理过程里就把函数定义和赋值操作都完成了,在这里我补充下javascript里预处理的特性,其实预处理是和执行环境相关,在上篇文章里我讲到执行环境有两大类:全局执行环境和局部执行环境,执行环境是通过上下文变量体现的,其实这个过程都是在函数执行前完成,预处理就是构造执行环境的另一个说法,总而言之预处理和构造执行环境的主要目的就是明确变量定义,分清变量的边界,但是在全局作用域构造或者说全局变量预处理时候对于声明函数有些不同,声明函数会将变量定义和赋值操作同时完成,因此我们看到上面代码的运行结果。由于声明函数都会在全局作用域构造时候完成,因此声明函数都是window对象的属性,这就说明为什么我们不管在哪里声明函数,声明函数最终都是属于window对象的原因了

  关于函数表达式的写法还有秘密可以探寻,我们看下面的代码:

复制代码
<script type="text/javascript">
    function ftn03(){
        var ftn04 = function(){
            console.log(this);// window
        };
        ftn04();
    }
    ftn03();
</script>
复制代码

 

  运行结果我们发现ftn04虽然在ftn03作用域下,但是执行它里面的this指针也是指向window,其实函数表达式的写法我们大多数更喜欢在函数内部写,因为声明函数里的this指向window这已经不是秘密,但是函数表达式的this指针指向window却是常常被我们所忽视,特别是当它被写在另一个函数内部时候更加如此。

  其实在javascript语言里任何匿名函数都是属于window对象,它们也都是在全局作用域构造时候完成定义和赋值,但是匿名函数是没有名字的函数变量,但是在定义匿名函数时候它会返回自己的内存地址,如果此时有个变量接收了这个内存地址,那么匿名函数就能在程序里被使用了,因为匿名函数也是在全局执行环境构造时候定义和赋值,所以匿名函数的this指向也是window对象,所以上面代码执行时候ftn04的this也是指向window,因为javascript变量名称不管在那个作用域有效,堆区的存储的函数都是在全局执行环境时候就被固定下来了,变量的名字只是一个指代而已。

  这下子坏了,this都指向window,那我们到底怎么才能改变它了?

  在本文开头我说出了this的秘密,this都是指向实例化对象,前面讲到那么多情况this都指向window,就是因为这些时候只做了一次实例化操作,而这个实例化都是在实例化window对象,所以this都是指向window。我们要把this从window变成别的对象,就得要让function被实例化,那如何让javascript的function实例化呢?答案就是使用new操作符。我们看看下面的代码:

复制代码
<script type="text/javascript">
    var obj = {
        name:"sharpxiajun",
        job:"Software",
        show:function(){
            console.log("Name:" + this.name + ";Job:" + this.job);
            console.log(this);// Object { name="sharpxiajun", job="Software", show=function()}
        }
    };
    var otherObj = new Object();
    otherObj.name = "xtq";
    otherObj.job = "good";
    otherObj.show = function(){
        console.log("Name:" + this.name + ";Job:" + this.job);
        console.log(this);// Object { name="xtq", job="good", show=function()}
    };
    obj.show();//Name:sharpxiajun;Job:Software
    otherObj.show();//Name:xtq;Job:good
</script>    
复制代码

 

   这是我上篇讲到的关于this使用的一个例子,写法一是我们大伙都爱写的一种写法,里面的this指针不是指向window的,而是指向Object的实例,firebug的显示让很多人疑惑,其实Object就是面向对象的类,大括号里就是实例对象了,即obj和otherObj。Javascript 里通过字面量方式定义对象的方式是new Object的简写,二者是等价的,目的是为了减少代码的书写量,可见即使不用new操作字面量定义法本质也是new操作符,所以通过new改变this 指针的确是不过攻破的真理。

  下面我使用javascript来重写本篇开头用java定义的类,代码如下:

复制代码
<script type="text/javascript">
    function Person(name,sex,age,job){
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.job = job;
        this.showPerson = function(){
            console.log("姓名:" + this.name);
            console.log("性别:" + this.sex);
            console.log("年龄:" + this.age);
            console.log("工作:" + this.job);
            console.log(this);// Person { name="马云", sex="男", age=46, 更多...}
        }
    }
    var person = new Person("马云", "男", 46, "董事长");
    person.showPerson();
</script>
复制代码

  看this指针的打印,类变成了Person,这表明function Person就是相当于在定义一个类,在javascript里function的意义实在太多,function既是函数又可以表示对象,function是函数时候还能当做构造函数,javascript的构造函数我常认为是把类和构造函数合二为一,当然在javascript语言规范里是没有类的概念,但是我这种理解可以作为构造函数和普通函数的一个区别,这样理解起来会更加容易些

  下面我贴出在《javascript高级编程》里对new操作符的解释:

  new操作符会让构造函数产生如下变化:

  1.       创建一个新对象;

  2.       将构造函数的作用域赋给新对象(因此this就指向了这个新对象);

  3.       执行构造函数中的代码(为这个新对象添加属性);

  4.       返回新对象

  关于第二点其实很容易让人迷惑,例如前面例子里的obj和otherObj,obj.show(),里面this指向obj,我以前文章讲到一个简单识别this 方式就是看方法调用前的对象是哪个this就指向哪个,其实这个过程还可以这么理解,在全局执行环境里window就是上下文对象,那么在obj里局部作用域通过obj来代表了,这个window的理解是一致的。

  第四点也要着重讲下,记住构造函数被new操作,要让new正常作用最好不能在构造函数里写return,没有return的构造函数都是按上面四点执行,有了return情况就复杂了,这个知识我会在讲prototype时候讲到。

   Javascript还有一种方式可以改变this指针,这就是call方法和apply方法,call和apply方法的作用相同,就是参数不同,call和apply的第一个参数都是一样的,但是后面参数不同,apply第二个参数是个数组,call从第二个参数开始后面有许多参数。Call 和apply的作用是什么,这个很重要,重点描述如下:

  Call和apply是改变函数的作用域(有些书里叫做改变函数的上下文)

  这个说明我们参见上面new操作符第二条:

  将构造函数的作用域赋给新对象(因此this就指向了这个新对象);

  Call和apply是将this指针指向方法的第一个参数。

  我们看看下面的代码:

复制代码
<script type="text/javascript">
    var name = "sharpxiajun";
    function ftn(name){
        console.log(name);
        console.log(this.name);
        console.log(this);
    }
    ftn("101");
    var obj = {
      name:"xtq"
    };
    ftn.call(obj,"102");
    /*
    * 结果如下所示:
    *101
     T002.html (第 73 行)
     sharpxiajun
     T002.html (第 74 行)
     Window T002.html
     T002.html (第 75 行)
     102
     T002.html (第 73 行)
     xtq
     T002.html (第 74 行)
     Object { name="xtq"}
    * */
</script>
复制代码

  我们看到apply和call改变的是this的指向,这点在开发里很重要,开发里我们常常被this所迷惑,迷惑的根本原因我在上文讲到了,这里我讲讲表面的原因:

  表面原因就是我们定义对象使用对象的字面表示法,字面表示法在简单的表示里我们很容易知道this指向对象本身,但是这个对象会有方法,方法的参数可能会是函数,而这个函数的定义里也可能会使用this指针,如果传入的函数没有被实例化过和被实例化过,this的指向是不同,有时我们还想在传入函数里通过this指向外部函数或者指向被定义对象本身,这些乱七八糟的情况使用交织在一起导致this变得很复杂,结果就变得糊里糊涂。

  其实理清上面情况也是有迹可循的,就以定义对象里的方法里传入函数为例:

  情形一:传入的参数是函数的别名,那么函数的this就是指向window;

  情形二:传入的参数是被new过的构造函数,那么this就是指向实例化的对象本身;

  情形三:如果我们想把被传入的函数对象里this的指针指向外部字面量定义的对象,那么我们就是用apply和call

  我们可以通过代码看出我的结论,代码如下:

复制代码
<script type="text/javascript">
var name = "I am window";
var obj = {
    name:"sharpxiajun",
    job:"Software",
    ftn01:function(obj){
        obj.show();
    },
    ftn02:function(ftn){
        ftn();
    },
    ftn03:function(ftn){
        ftn.call(this);
    }
};
function Person(name){
    this.name = name;
    this.show = function(){
        console.log("姓名:" + this.name);
        console.log(this);
    }
}
var p = new Person("Person");
obj.ftn01(p);
obj.ftn02(function(){
   console.log(this.name);
   console.log(this);
});
obj.ftn03(function(){
    console.log(this.name);
    console.log(this);
});
</script>
复制代码

  结果如下:

 

  最后再总结一下:

  如果在javascript语言里没有通过new(包括对象字面量定义)、call和apply改变函数的this指针,函数的this指针都是指向window的

 

 

javascript的new只是对class的一种模拟,这个也是业界说javascript的类根本不是类的原因——因为它是模拟的。

 

 

其实javascript的new关键字只不过做了五件事情。

1.创建Object
2.查找class的prototype上的所有方法、属性,复制一份给创建的Object(注意,如果prototype上有属性是function或者数组或者Object,那么只复制指针)
3.将构造函数classA内部的this指向创建的Object

4.创建的Object的__proto__指向class的prototype
5.执行构造函数class

 

请看示例:

 

[javascript] view plain copy
  1. // 定义类 类名字是 classA  
  2. function classA(){  
  3.     this.b=1;  
  4. }  
  5. classA.prototype.b=44;  
  6. classA.prototype.show = function(){  
  7.     alert(this.b);  
  8. };  
  9. // 用new实例化  
  10. var b = new classA();  
  11. b.show();  
  12. // 用函数实例化  
  13. function newClass(cls,args){  
  14.     var obj = {};  
  15.     for(var p in cls.prototype)  
  16.         obj[p] = cls.prototype[p];  
  17.     obj.__proto__ = cls.prototype;  
  18.     cls.apply(obj,args||[]);  
  19.     return obj;  
  20. };  
  21. var k = newClass(classA);  
  22. k.show();  

原型对象概念

无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。而通过这个构造函数,可以继续为原型对象添加其他属性和方法。创建了自定义的构造函数后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都从 Object 继承而来。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262第5版管这个指针叫 [[Prototype]] 。脚本中没有标准的方式访问 [[Prototype]],但Firefox、Safari和Chrome在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于示例和构造函数的原型对象之间,而不是存在于实例和构造函数之间。

这段话基本概述了构造函数、原型、示例之间的关系,下图表示更清晰

通过new创建对象经历4个步骤

1、创建一个新对象;[var o = new Object();]

2、将构造函数的作用域赋给新对象(因此this指向了这个新对象);[Person.apply(o)]  [Person原来的this指向的是window]

3、执行构造函数中的代码(为这个新对象添加属性);

4、返回新对象。

通过代码还原new的步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function Person(name, age) {
 
     this .name = name;
 
     this .age = age;
 
     this .sayName = function () {
 
         alert( this .name);
 
     }
 
}
 
function createPerson(P) {
 
     var o = new Object();
 
     var args = Array.prototype.slice.call(arguments, 1);
 
     o.__proto__ = P.prototype;
 
     P.prototype.constructor = P;
 
     P.apply(o, args);
 
     return o;
 
}

new()过程中的原型链维护

new总是因为建立原型继承树而存在的,如果没有new过程参与,则当

obj = new MyObjecEx()时,我们无法通过instanceof运算:obj instanceof MyObject 来了解obj在继承树上的关系。但是事实上这一过程并不需要MyObject的参与。因为instanceof只检查prototype链,并不检查函数本身。

new新对象的创建,就是不断地为this赋值而已,只不过new会为产生的对象维护<obj>.constructor属性。

 

new关键字做了什么

在JavaScript中,使用new关键字后,意味着做了如下四件事情:

  • 创建一个新的对象,这个对象的类型是object
  • 设置这个新的对象的内部、可访问性和[[prototype]]属性为构造函数(指prototype.construtor所指向的构造函数)中设置的;
  • 执行构造函数,当this关键字被提及的时候,使用新创建的对象的属性;
  • 返回新创建的对象(除非构造方法中返回的是‘无原型’)。

在创建新对象成功之后,如果调用一个新对象没有的属性的时候,JavaScript会延原型链向止逐层查找对应的内容。这类似于传统的‘类继承’。

注意:在第二点中所说的有关[[prototype]]属性,只有在一个对象被创建的时候起作用,比如使用new关键字、使用 Object.create、基于字面意义的(函数默认为Function.prototype,数字默认为Number.prototype等)。它只能被Object.getPrototypeOf(someObject)所读取。没有其他任何方式来设置或读取这个值。

样例说明

ObjMaker = function() {this.a = 'first';};

ObjMaker只是一个用于作为构造器的方法,没有其他意义。

ObjMaker.prototype.b = 'second';

与其他函数类似, ObjMaker拥有一个可被我们修改的prototype属性. 我们添加一个属性b给它。与所有对象一样,ObjMaker也拥有一个不可访问的[[prototype]]属性,我们无法对其进行改变。

obj1 = new ObjMaker();

这里发生了三件事情:

  • 一个叫obj1的空对象被创建,首先obj1与{}一致;
  • obj1的[[prototype]]属性被设置为ObjMaker的原型属性的拷贝;
  • ObjMaker方法被执行,所以obj1.a被设置为‘first‘。

    obj1.a;

返回'first'。

obj1.b;

obj1没有'b'属性,所以JavaScript在它的[[prototype]]中查找。它的[[prototype]]与 ObjMaker.prototype属性一致。而ObjMaker.prototype属性有一个叫'b'的属性,其值为'second',所以返回 'second'。

模仿继承

你可以使用如下的方式实例化ObjMaker类的子类:

SubObjMaker = function () {};
SubObjMaker.prototype = new ObjMaker(); 

由于这里使用了new关键字,所以SubObjMaker的[[prototype]]属性被设置为ObjMaker.prototype的一个拷贝。

SubObjMaker.prototype.c = 'third';  
obj2 = new SubObjMaker();

obj2的[[prototype]]属性被设置为SubObjMaker的prototype属性的一个拷贝。

obj2.c;

返回'third'。来自SubObjMaker.prototype

obj2.b;

返回‘second’。来自ObjMaker.prototype

obj2.a;

返回‘first’。来自SubObjMaker.prototype,这是因为SubObjMaker是使用ObjMaker的构造方法创建的,这个构造方法赋值给a。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值