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 类加载流程 )(代码片段