js的构造函数

Posted yeluo-blog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js的构造函数相关的知识,希望对你有一定的参考价值。

一、js函数

  首先何为函数,简单的说函数就是重复执行的代码块。函数是一段只定义一次但可能被执行或调用任意次的javascript 代码。

  函数的定义方式分为以下几种:

    1.函数声明: function  函数名 () { 函数体 };这种定义方式,会出现函数声明提升(在执行代码之前会先读取函数声明,也就是说,可以把函数声明放在调用它的语句后面)它会被提升到该函数所在作用域的最开头。

    2.函数表达式:let fun = function () { 函数体 };(匿名函数)此方式定义的函数,只能在该作用域中,在使用前必须先赋值,再通过fun()调用函数,否则,由于变量声明提升,fun === undefined。

    3.new Function 形式: var fun1 = new Function (arg1 , arg2 ,arg3 ,…, argN , body  );Function构造函数所有的参数都是字符串类型。除了最后一个参数, 其余的参数都作为生成函数的参数即形参。这里可以没有参数。最后一个参数, 表示的是要创建函数的函数体。

  总结:

    1 、第一种和第二种函数的定义的方式其实是第三种new Function 的语法糖,当我们定义函数的时候都会通过 new Function 来创建一个函数,只是前两种为我们进行了封装,我们看不见了而已,js 中任意函数都是Function 的实例。

    2、ECMAScript 定义的函数实际上是功能完整的对象。

    3、函数声明和函数表达式的区别:用函数声明创建的函数可以在函数解析后调用(解析时进行等逻辑处理);而用函数表达式创建的函数是在运行时进行赋值,且要等到表达式赋值完成后才能调用。这本质远原因体现在JavaScript function hoisting(函数提升)和运行时机(解析时/运行时)上的差异。函数声明在JS解析时进行函数提升,因此在同一个作用域内,不管函数声明在哪里定义,该函数都可以进行调用。而函数表达式的值是在JS运行时确定,并且在表达式赋值完成后,该函数才能调用。

二、构造函数

  定义:通过  new 函数名    来实例化对象的函数叫构造函数,构造函数首字母一般大写 。任何的函数都可以作为构造函数存在。之所以有构造函数与普通函数之分,主要是从功能上进行区分,构造函数的主要功能为 初始化对象,特点是和new 一起使用。new就是在创建对象,从无到有,构造函数就是在为初始化的对象添加属性和方法。

  对new理解:new 申请内存, 创建对象,当调用new时,后台会隐式执行new Object()创建对象。所以,通过new创建的字符串、数字是引用类型,而是非值类型。

  1、常用的构造函数: 

    1.    var arr = [];     其实是     var arr = new Array();       的语法糖。

    2.    var obj = {}     其实是      var obj = new Object();     的语法糖

    3. var  date = new Date();

    4.    function Foo (){ };    其实是     var Foo = new Function();

  2、构造函数的执行过程:

    先看个例子

1 function Pen(color) {
2   this.color = color;
3 }

    当一个函数创建好以后,虽然函数名为大写,我们也不能确定它是不是构造函数。只有当一个函数以 new 关键字来调用的时候,我们才能说它是一个构造函数。就像下面这样:

1 var dog = new Pen("blue");

    OK,继续,下面我们只讨论构造函数的执行过程,也就是以 new 关键字来调用的情况。

1 function Person(name, gender, hobby) {
2   this.name = name;
3   this.gender = gender;
4   this.hobby = hobby;
5   this.age = 6;
6 }
7 
8 var p1 = new Person(‘zs‘, ‘男‘, ‘basketball‘);

     此时,构造函数会有以下几个执行过程:

     1> 当以 new 关键字调用时,会创建一个新的内存空间,标记为 Person 的实例

      技术图片

      2> 函数体内部的 this 指向该内存

      技术图片

 

 

       通过以上两步,我们就可以得出这样的结论。

1 var p2 = new Person(‘ls‘, ‘女‘, ‘dancing‘);  // 创建一个新的内存 #f2
2 var p3 = new Person(‘ww‘, ‘女‘, ‘singing‘);  // 创建一个新的内存 #f3

      每当创建一个实例的时候,就会创建一个新的内存空间(#f2, #f3),创建 #f2 的时候,函数体内部的 this 指向 #f2, 创建 #f3 的时候,函数体内部的 this 指向 #f3。

      3> 执行函数体内的代码

      通过上面的讲解,你就可以知道,给 this 添加属性,就相当于给实例添加属性。

      4> 默认返回 this

      由于函数体内部的 this 指向新创建的内存空间,默认返回 this ,就相当于默认返回了该内存空间,也就是上图中的 #f1。此时,#f1的内存空间被变量 p1 所接受。也就是说 p1 这个变量,保存的内存地址就是 #f1,同时被标记为 Person 的实例。

      以上就是构造函数的整个执行过程。

  3、构造函数的返回值

  构造函数执行过程的最后一步是默认返回 this 。言外之意,构造函数的返回值还有其它情况。下面我们就来聊聊关于构造函数返回值的问题。

    (1)没有手动添加返回值,默认返回 this 。

1 function Foo(name, age) {
2   this.name = name;
3   this.age = age;
4   this.class = ‘class-1‘;
5   // return this  //默认有这一行,即便不写也会默认返回 this
6 }
7  var f = new Foo(‘hanri‘, 20);
    按照上面讲的,我们复习一遍。首先,当用 new 关键字调用时,产生一个新的内存空间 #f11,并标记为 Foo的实例;接着,函数体内部的 this 指向该内存空间 #f11;执行函数体内部的代码;由于函数体内部的 this 指向该内存空间,而该内存空间又被变量 f 所接收,所以 f 中就会有一个 name 属性,属性值为 ‘hanri‘。
1 f: {
2   name: "hanri";
3   age: 20;
4   class: "class-1";
5 }

    (2) 手动添加一个基本数据类型的返回值,最终还是返回 this

 1  function Foo(name, age) {
 2    this.name = name;
 3    this.age = age;
 4    this.class = ‘class-1‘;
 5  
 6    return 1;
 7  }
 8  var f = new Foo(‘hanri‘, 20);
 9  console.log(f.age);  // 构造函数  20 还是返回this
10  var f1 = Foo(‘hanri‘, 20);
11  console.log(f1);  // 1 一个普通函数的调用,那么返回值就是 1。
12           //如果没有显性声明 return 1 的话,他是没有默认返回值的  

 

1 f: {
2    name: "hanri";
3    age: 20;
4    class: "class-1";
5 }

    (3) 手动添加一个复杂数据类型(对象)的返回值,最终返回该对象

1 function Person1() {
2   this.height = ‘170‘;
3   return [‘a‘, ‘b‘, ‘c‘];
4 }
5 
6 var p1 = new Person1();
7 console.log(p1.height);  // undefined this对象丢失
8 console.log(p1.length);  // 3
9 console.log(p1[0]);      // ‘a‘

    tips:同上例,如果指定了返回对象,那么,this对象可能会丢失。(自己可以试一下)

 1   function Person(name){
 2     this.name = name;
 3     this.say = function(){
 4       return "I am " + this.name;
 5     }
 6     var that = {};
 7     that.name = "It is that!";
 8     return that;
 9   }
10  
11  var person1 = new Person(‘nicole‘);
12  person1.name; // "It is that!"

  4、用new和不要new调用构造函数的区别

    1)使用new操作符调用函数

1 function Person(name){
2   this.name = name;
3   this.say = function(){
4     return "I am " + this.name;
5   }
6 }
7 
8 var person1 = new Person(‘nicole‘);
9 person1.say(); // "I am nicole"

    用new调用构造函数,函数内部会发生如下变化:

    创建一个this变量,该变量指向一个空对象。并且该对象继承函数的原型;

    属性和方法被加入到this引用的对象中;

    隐式返回this对象(如果没有显性返回其他对象)

    用伪程序来展示上述变化:

 1 function Person(name){
 2   // 创建this变量,指向空对象
 3   var this = {}; 
 4   // 属性和方法被加入到this引用的对象中
 5   this.name = name;
 6   this.say = function(){
 7     return "I am " + this.name;
 8   }
 9   // 返回this对象
10   return this;
11 }

     可以看出,用new调用构造函数,最大特点为,this对象指向构造函数生成的对象,所以,person1.say()会返回字符串: “I am nicole”。

    2)直接调用函数

    如果直接调用函数,那么,this对象指向的是window,而且不会默认返回任何对象(除非显性声明返回值)。在上面得例子也有提到过。

    还是拿Person函数为例,直接调用Person函数:

1 var person1 = Person(‘nicole‘);
2 person1; // undefined
3 window.name; // nicole

    可见,直接调用构造函数的结果,并不是我们想要的。

            3)技术提升(强行调用new关键字)

               为了防止因为忘记使用new关键字而调用构造函数,可以加一些判断条件强行调用new关键字,代码如下:

 1 function Person(name){
 2   if (!(this instanceof Person)) {
 3     return new Person(name);
 4   }
 5   this.name = name;
 6   this.say = function(){
 7     return "I am " + this.name;
 8   }
 9 }
10 
11 var person1 = Person(‘nicole‘);
12 console.log(person1.say()); // I am nicole
13 var person2 = new Person(‘lisa‘);
14 console.log(person2.say()); // I am lisa

三、ES6 中 class 与构造函数的关系

  class 为  构造函数的语法糖,即 class 的本质是 构造函数。class的继承 extends  本质 为构造函数的原型链的继承。

  例如:

  类的写法

 1   class Person{  //定义一个名字为Person的类
 2 
 3     constructor(name,age){ //constructor是一个构造方法,用来接收参数
 4 
 5       this.name = name;  //this代表实例对象
 6 
 7       this.age = age;
 8 
 9     } 
10 
11     say(){  //这是一个类的方法,注意千万不要加上function
12 
13       return   this.name + this.age
14 
15     }
16 
17   }
18 
19   var obj = new Person(‘老铁‘,18);
20 
21   console.log(obj.say());

  构造函数的写法

 1     function Person(name,age){   //构造函数和实例化构造名相同且大写(非强制,但这么写有助于区分构造函数和普通函数)
 2 
 3       if(!(this instanceof Person)){ //避免使用者不小心讲Person当作普通函数执行
 4 
 5          throw new Error(‘‘请使用 new Person"); //仿ES6 class 中的写法
 6 
 7       }
 8 
 9       this.name = name;
10 
11       this.age = age;
12 
13     }
14 
15     Person.prototype.say = function(){
16 
17       return   this.name + this.age
18 
19     }
20 
21     
22 
23   var obj = new Person(‘老铁‘,18);   //通过构造函数创建对象,必须使用new运算符
24 
25   console.log(obj.say());

  总结:通过class定义的类 和通过构造函数定义的类 二者本质相同。并且在js执行时,会将第一种转会为第二种执行。所以 ES6 class的写法实质就是构造函数。

以上是关于js的构造函数的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段

防止 Proguard 删除片段的空构造函数

无法解析片段中的 ViewModelProvider 构造?

为啥要避免片段中的非默认构造函数?

片段真的需要一个空的构造函数吗?

这个嵌套类构造函数片段可以应用于泛型类吗?