【JavaScript核心技术卷】创建实例对象

文章目录

  • 创建实例对象
    • 一、创建自定义构造函数的实例对象
      • (1)创建自定义构造函数的实例对象的图示过程
      • (2)创建自定义构造函数的实例对象的文字描述过程
      • (3)创建实例对象的三种情况的程序
      • (4)自定义函数对象Fn数据结构实现模型
    • 二、使用模式创建实例对象
      • (1)工厂模式
      • (2)构造函数模式

创建实例对象

JavaScript只有实例方法,所以JavaScript没有静态构造函数

一、创建自定义构造函数的实例对象

(1)创建自定义构造函数的实例对象的图示过程

JS本身只有构造函数对象具备类的概念,因此要创建一个对象,必须使用构造函数对象。构造函数对象内部有[[Construct]]方法和[[Call]]方法,[[Construct]]用于构造对象,[[Call]]用于函数调用,只有使用new操作符时才会触发[[Construct]]逻辑。

1、 对象创建表达式

var obj=new Object(); 是对象创建表达式,是使用内置的Object构造函数对象创建该类的实例化对象obj,[[Construc]]构造器充当的是一个第三方工厂的角色。

首先声明函数function Fn(){}(函数声明语句/函数字面量), 然后var myObj=new Fn();实对象创建表达式,是使用用户自定义的构造函数创建该类的实例化对象myObj。

2、 对象字面量/对象初始化表达式

var obj={};(对象字面量/对象初始化表达式)和var obj=[];(数组字面量/数组初始化表达式)这种代码将触发JS引擎构造Object和Array实例对象的过程,生成Objec、Array的实例,并不调用Object和Array的构造函数,生成效率高,JS引擎在此充当的是一个第三方工厂的角色。

【JavaScript核心技术卷】创建实例对象_第1张图片

(2)创建自定义构造函数的实例对象的文字描述过程

new Fn(args):使用new操作符时触发Fn 的[[Construct]] 方法处理逻辑,创建实例对象的步骤如下:

  1. 申请空间,分配内存。
  2. 创建一个具有内置对象数据结构(build-in object data structure)的实例对象obj。
  3. 在实例对象obj中添加内置对象数据结构的隐式属性。
  4. 设置obj的隐式属性[[Class]]的值为”Object”, 说明该实例的类名称是”Object”。
  5. 设置obj的隐式属性[[Extensible]] 的值为 true,说明该实例对象的属性可以增删。
  6. 设置obj的隐式属性[[Prototype]]:
    • 如果Fn.prototype的属性值是一个引用类型对象,则设置obj的隐式属性[[Prototype]]的值为Fn.prototype,说明实例对象obj是类Fn的实例;
    • 否则设置obj的隐式属性[[Prototype]]的值为Object.prototype,说明实例对象obj是类Object的实例;
  7. 调用Fn的隐式[[Call]]方法,添加内置对象数据结构的显式属性并进行初始化。(生成一种新的数据结构)将obj作为this,使用args参数调用Fn的隐式[[Call]]方法, 方法调用:obj.[[Call]](args):
    • 创建隐式[[Call]]方法的当前执行环境(当前执行环境对象压入执行环境栈):
      • 函数Fn的隐式[[Scope]]静态作用域链复制到[[Call]]的执行作用域链,在执行作用域链的前端添加构造函数Fn的活动对象
      • 当前执行环境的this引用实例对象obj。
    • 执行Fn的函数体:
      • 扫描函数体代码[[Call]],提升函数声明和变量声明。
      • 执行函数体代码。
    • 销毁[[Call]] 的当前执行环境(当前执行环境对象弹出执行环境栈)
    • 返回Fn函数体的返回值。 Fn函数体返回值的三种情况:
      • Fn的函数体没有return,返回undefined值类型;
      • Fn的函数体有return, 返回值类型;
      • Fn的函数体有return, 返回引用类型;
  8. 如果[[Call]]的返回值是一个引用类型对象,则返回这个对象;否则([[Call]]的返回值是基本值类型)返回obj。

注意步骤6中,prototype指Fn对象显示的prototype属性,而[[Prototype]]则代表对象内部隐式Prototype属性。

构成对象Prototype链的是内部隐式的[[Prototype]](原型链),而并非对象显示的prototype属性。显示的prototype只有在函数对象上才有意义,从上面的创建过程可以看到,函数的prototype被赋给实例对象隐式[[Prototype]]属性,这样根据Prototype规则,实例对象和函数的prototype对象之间才存在属性、方法的继承/共享关系,实例对象与类之间无继承关系,类充当的只是一个第三方工厂的角色。

(3)创建实例对象的三种情况的程序

第一种 :创建实例对象代码, 返回值是基本值类型

//由于[[Call]]的返回值是基本值类型,所以创建过程返回构造函数的实例对象
<!DOCTYPE html>
<html>
<head>
<title>Create an Instance Example1</title>
<script type="text/javascript">
//用于new的构造函数对象,遵循Pascal命名方式,第一个单词首字母大写function Fn(){this.success = true };
//the value of implicit [[Prototype]] property of those objects derived from Fn will be assigned to Fn.prototype
	
// Fn.prototype.constructor == Fn;  	//result: true
// Fn.prototype.constructor == Object;  //result: false

Fn.prototype={ attr1:"aaa", attr2:"bbb"};
var obj=new Fn ();
document.write(obj.attr1 + "
"
); //result: aaa document.write(obj.attr2 + "
"
); //result: bbb // Fn.prototype.constructor == Fn; //result: false // Fn.prototype.constructor == Object; //result: true // obj.[[Prototype]] == Fn.prototype; //result: true //因此,obj继承了Fn.prototype的constructor , // obj.constructor –-> obj.[[Prototype]] –-> Fn.prototype –-> // Fn.prototype.constructor –-> Fn.prototype.[[Prototype]] –-> // object.prototype –-> object.prototype.constructor –-> Object document.write((obj instanceof Fn) + "
"
); //result: true document.write((obj instanceof Object) + "
"
); //result: true document.write("
"
); //I change the prototype of Fn here, so by the algorithm of Prototype the obj is no longer the instance of Fn, //but this won't affect the obj and its [[Prototype]] property, and the obj still has attr1 and attr2 properties Fn.prototype={}; document.write(obj.attr1 + "
"
); //result: aaa document.write(obj.attr2 + "
"
); //result: bbb // Fn.prototype.constructor == Object; //result: true // obj.[[Prototype]] == Fn.prototype; //result: false document.write((obj instanceof Fn) + "
"
); //result: false document.write((obj instanceof Object) + "
"
); //result: true </script> </head> <body> </body> </html>

解析代码的步骤:

1.创建全局执行环境(由引擎自动创建) Global EC

2.扫描全局代码,提升函数声明、变量声明

上面例子的执行顺序实际为(解析后的伪码):

<!DOCTYPE html>
<html>
<head>
<title>Create an Instance Example1</title>
<script type="text/javascript">
//用于new的构造函数对象,遵循Pascal命名方式,第一个单词首字母大写function Fn(){this.success = true};
//the value of implicit [[Prototype]] property of those objects derived from Fn will be assigned to Fn.prototype
var obj;
	
// Fn.prototype.constructor == Fn;  	//result: true
// Fn.prototype.constructor == Object;  //result: false

Fn.prototype={ attr1:"aaa", attr2:"bbb"};
obj=new Fn ();
document.write(obj.attr1 + "
"
); //result: aaa document.write(obj.attr2 + "
"
); //result: bbb // Fn.prototype.constructor == Fn; //result: false // Fn.prototype.constructor == Object; //result: true // obj.[[Prototype]] == Fn.prototype; //result: true //因此,obj继承了Fn.prototype的constructor , // obj.constructor –-> obj.[[Prototype]] –-> Fn.prototype –-> // Fn.prototype.constructor –-> Fn.prototype.[[Prototype]] –-> // object.prototype –-> object.prototype.constructor –-> Object document.write((obj instanceof Fn) + "
"
); //result: true document.write((obj instanceof Object) + "
"
); //result: true document.write("
"
); //I change the prototype of Fn here, so by the algorithm of Prototype the obj is no longer the instance of Fn, //but this won't affect the obj and its [[Prototype]] property, and the obj still has attr1 and attr2 properties Fn.prototype={}; document.write(obj.attr1 + "
"
); //result: aaa document.write(obj.attr2 + "
"
); //result: bbb // Fn.prototype.constructor == Object; //result: true // obj.[[Prototype]] == Fn.prototype; //result: false document.write((obj instanceof Fn) + "
"
); //result: false document.write((obj instanceof Object) + "
"
); //result: true </script> </head> <body> </body> </html>

3.执行全局代码 function Fn(){}; var obj;
【JavaScript核心技术卷】创建实例对象_第2张图片

4.执行全局代码 Fn.prototype={ attr1:“aaa”, attr2:“bbb”};

改写Fn的原型之后,obj不再是Fn的实例,但是这不影响obj的原型指向
Fn.prototype.constructor == Fn; //result: false
Fn.prototype.constructor == Object; //result: true
obj.[[Prototype]] == Fn.prototype; //result: true

【JavaScript核心技术卷】创建实例对象_第3张图片
5.执行全局代码 obj=new Fn ();

  1. 申请空间,分配内存。
  2. 创建一个具有内置对象数据结构(build-in object data structure)的实例对象ins。
  3. 在实例对象ins中添加内置对象数据结构的隐式属性。
  4. 设置ins的隐式属性[[Class]]的值为”Object”, 说明该实例的类名称是”Object”。
  5. 设置ins的隐式属性[[Extensible]] 的值为 true,说明该实例对象的属性可以增删。
  6. 设置ins的隐式属性[[Prototype]]:
    • 如果Fn.prototype的属性值是一个引用类型对象,则设置ins的隐式属性[[Prototype]]的值为Fn.prototype,说明实例对象ins是类Fn的实例;
    • 否则设置ins的隐式属性[[Prototype]]的值为Object.prototype,说明实例对象ins是类Object的实例;

【JavaScript核心技术卷】创建实例对象_第4张图片

  1. 调用Fn的隐式[[Call]]方法,添加内置对象数据结构的显式属性并进行初始化。(生成一种新的数据结构)将ins作为this,使用args参数调用Fn的隐式[[Call]]方法, 方法调用:ins.[[Call]](args):
    1. 创建隐式[[Call]]方法的当前执行环境(当前执行环境对象压入执行环境栈):
      • 函数Fn的隐式[[Scope]]静态作用域链复制到[[Call]]的执行作用域链,在执行作用域链的前端添加构造函数Fn的活动对象。
      • 当前执行环境的this引用实例对象ins。
    2. 执行Fn的函数体:
      • 扫描函数体代码[[Call]],提升函数声明和变量声明。
      • 执行函数体代码。

【JavaScript核心技术卷】创建实例对象_第5张图片

    1. 销毁[[Call]] 的当前执行环境(当前执行环境对象弹出执行环境栈)
    2. 返回Fn函数体的返回值。 Fn函数体返回值的三种情况:
      • Fn的函数体没有return,返回undefined值类型;
      • Fn的函数体有return, 返回值类型;
      • Fn的函数体有return, 返回引用类型;
  1. 如果[[Call]]的返回值是一个引用类型对象,则返回这个对象;否则([[Call]]的返回值是基本值类型)返回ins。将返回对象赋予obj。

【JavaScript核心技术卷】创建实例对象_第6张图片

6.执行全局代码 Fn.prototype={};

由于在创建实例obj后改写了Fn的原型,obj与Fn的继承关系断开
也即obj.[[Prototype]] == Fn.prototype; //result: false

【JavaScript核心技术卷】创建实例对象_第7张图片

第二种: 创建实例对象代码, 返回值是一个引用类型对象

//由于[[Call]]的返回值是一个引用类型对象,所以创建过程返回其它实例对象
<!DOCTYPE html>
<html>
<head>
<title> Create an Instance Example2</title>
<script type="text/javascript">
//用于new的构造函数对象,遵循Pascal命名方式,第一个单词首字母大写
function Fn(m,n){
	//according to step 8 described above, 
	//the new Fn() operation will return the object { attr1: 111, attr2: 222 }, it's not an instance of Fn!
   		this.x = m;
		this.y = n;
	document.write(this.attr1 + "
"
); //result: aaa document.write(this.attr2 + "
"
); //result: bbb return { attr1: 111, attr2: 222 }; } Fn.prototype={ attr1:"aaa", attr2:"bbb"}; var obj=new Fn(); document.write(obj.attr1 + "
"
); //result: 111 document.write(obj.attr2 + "
"
); //result: 222 document.write(obj instanceof Fn); //result: false </script> </head> <body> </body> </html>

【JavaScript核心技术卷】创建实例对象_第8张图片

第三种:创建实例对象代码, 构造函数的prototype = null

//由于构造函数的prototype = null,所以创建过程返回Objec构造函数的实例对象
<!DOCTYPE html>
<html>
<head>
<title>Create an Instance Example3</title>
<script type="text/javascript">
//用于new的构造函数对象,遵循Pascal命名方式,第一个单词首字母大写
function Fn(){};

Fn.prototype = null;

//according to step 6 described above, 
//the new Fn() operation will return an instance of object, it's not an instance of Fn!

var obj = new Fn();

document.write((obj.constructor == Object) + "
"
); //true document.write((obj.constructor == Fn) + "
"
); //false document.write((obj instanceof Object) + "
"
); //true //由于Fn.prototype = null,所以instanceof产生异常 // Function has non-object prototype 'null' in instanceof check try{ document.write((obj instanceof Fn) + "
"
); } catch(error) { document.write((error.message) + "
"
); } </script> </head> <body> </body> </html>

由于Fn.prototype=null,obj的原型不再指向Fn.prototype而是指向object,obj不再是Fn的实例
obj.constructor == Object //true
obj.constructor == Fn //false
obj instanceof Object //true

【JavaScript核心技术卷】创建实例对象_第9张图片

(4)自定义函数对象Fn数据结构实现模型

以下列出了自定义函数对象的几种方式

【JavaScript核心技术卷】创建实例对象_第10张图片

二、使用模式创建实例对象

模式(Pattern):就是解决某一类问题的方法论。把解决某类问题的方法总结归纳到理论高度,那就是模式。模式是一种指导,在一个良好的指导下,有助于你完成任务,有助于你作出一个优良的设计方案,达到事半功倍的结果,而且会得到解决问题的最佳方案。模式在某类环境下(应用领域),要解决的问题。

(1)工厂模式

问题:

解决创建多个相似对象的问题。

优点:

可以减少大量重复的操作代码。这种模式抽象了创建具体对象的过程。前面通过 new Object()或对象字面量,每次都需要给对象添加属性并赋值,会产生大量重复的操作代码。

缺点:

  1. 返回的实例对象不知道实例对象的类(存在对象识别问题)。
  2. 每个实例对象都有功能相同的方法。
//使用工厂模式创建实例对象

<!DOCTYPE html>

<html>
<head>
    <title>Factory Pattern Example1</title>
</head>
<body>

   <script type="text/javascript">
 		//只用于调用的函数对象,遵循camel命名方式,第一个单词首字母小写
        function createPerson(name, age, job){
            var o = new Object();
            o.name = name;
            o.age = age;
            o.job = job;
            o.sayName = function(){
                alert(this.name);
                //alert(name);
            };    
            return o;
        }
        
		//函数调用
        var person1 = createPerson("Nicholas", 29, "Software Engineer");
        //var person2 = createPerson("Greg", 27, "Doctor");
        
		//方法调用
        person1.sayName();   //"Nicholas"
        //person2.sayName();   //"Greg"

       
	//alert(createPerson.hasOwnProperty("caller"));   	//true
	//alert(person1 instanceof createPerson);			//false
	//alert(person1 instanceof Object);					//true

	//personX = new createPerson("Nicholas", 29, "Software Engineer");
	//alert(person1 instanceof createPerson);		//false
	//alert(person1 instanceof Object);				//true


    </script>



<!--  
  // 注释
  <script type="text/javascript">

	//解释器执行过程:
	//程序源代码--〉词法分析--〉单词流--〉语法分析--〉抽象语法树--〉指令流(中间代码)(可选)--〉解释器--〉单步解释执行

	//编译器目标机器代码生成过程:
	//程序源代码--〉词法分析--〉单词流--〉语法分析--〉抽象语法树--〉指令流(中间代码)(可选)--〉生成器(JIT编译器)--〉目标机器代码 --〉整体执行
                                                                	//对script代码块整体进行分析,如果有语法错误,则编译检查未通过。通过,则生成抽象语法树(源代码树)。var、function语句提升。
	//?如果未有语法错误,则编译全局代码,将javascript全局源代码生成JS的指令流,
	//?将生成的指令流交由解释器解释执行,可能产生运行时错误。

	//函数内部的[[Call]]所引用的JS源代码是无语法错误的源代码
	//
	//运行函数内部的源代码只需生成指令流,
	//将生成的指令流交由解释器解释执行,可能产生运行时错误。
    

	//alert("test");
	//aler("test");

   </script>

    <script type="text/javascript">

	// 测试function语句定义的空函数执行效率  
	alert("test");
	var a = new Date();  
	var x = a.getTime();  
 
	for(var i=0;i<100000;i++){ 
		//使用function语句定义的空函数 
    		function f(){           
		        var x; 
                        function f1(){}; 
			alert(f);
	    	}  
	}  
	f();
	
	var b = new Date();  
	var y = b.getTime();  
	alert(y-x);             //1,不同环境和浏览器会存在差异
  

	// 测试function表达式定义的空函数执行效率  
	var a = new Date();  
	var x = a.getTime();  
 
	for(var i=0;i<100000;i++){  
		//使用function表达式定义的空函数    
  		var f = function(){         
		        ;  
	    	}  
	}  


	var b = new Date();  
	var y = b.getTime();  
	alert(y-x);             //6,不同环境和浏览器会存在差异
  

	// 测试Function构造函数定义的空函数执行效率  
	var a = new Date();  
	var x = a.getTime();  

	for(var i=0;i<100000;i++){  
		//使用Function构造函数定义的空函数
    		new Function();       
	}  

	var b = new Date();  
	var y = b.getTime();  
	alert(y-x);             //70 

   </script>
   // 注释
-->


</body>
</html>

对象模型-执行模型

【JavaScript核心技术卷】创建实例对象_第11张图片

(2)构造函数模式

问题:

解决创建特定类型的多个相似对象的问题。

优点:

可以减少大量重复的操作代码。这种模式抽象了创建具体对象的过程。前面通过 new Object()或对象字面量,每次都需要给对象添加属性并赋值,会产生大量重复的操作代码。

缺点:

将共享的函数属性放在了所有的实例对象中。

//使用构造模式创建实例对象

<!DOCTYPE html>
<html>
<head>
    <title>Constructor Pattern Example 1</title>
    <script type="text/javascript">
    
        function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
       		//将Person的执行作用域链复制到sayName方法的[[Scope]]静态作用域
			this.sayName = function(){
			document.write(this.name + "
"
); //实例的属性 //document.write(name + "
"); //变量
}; } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.sayName(); //"Nicholas" person2.sayName(); //"Greg" document.write((person1 instanceof Object) + "
"
); //true document.write((person1 instanceof Person) + "
"
); //true document.write((person2 instanceof Object) + "
"
); //true document.write((person2 instanceof Person) + "
"
); //true document.write((person1.constructor == Person) + "
"
); //true document.write((person1.constructor == Person) + "
"
); //true document.write((person1.sayName == person2.sayName) + "
"
); //false </script> </head> <body> </body> </html>

【JavaScript核心技术卷】创建实例对象_第12张图片

//使用构造模式创建实例对象
<!DOCTYPE html>
<html>
<head>
    <title>Constructor Pattern Example 2</title>
    <script type="text/javascript">
    
        function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = function(){
                alert(this.name);
                //alert(name);
            };
        }

        var person = new Person("Nicholas", 29, "Software Engineer");
		//方法调用
        person.sayName();   //"Nicholas"
        
		//函数调用
        Person("Greg", 27, "Doctor");  //adds to window

		//方法调用
        window.sayName();   //"Greg"
        
        //使用call方法,可以改变方法调用中的this所引用的对象,尽管该对象的原型链中并不存在该函数对象。
		//相当于o.Person ("Kristen", 25, "Nurse")  
        var o = new Object();
		Person.call(o, "Kristen", 25, "Nurse");
        o.sayName();    //"Kristen"
        
    </script>
</head>
<body>

</body>
</html>

对象模型-执行模型

var person = new Person(“Nicholas”, 29, “Software Engineer”);
对应的执行模型如下
此种方法创建的对象作用域链中的0指向的是person变量对象,1指向person活动对象

【JavaScript核心技术卷】创建实例对象_第13张图片

//函数调用
Person(“Greg”, 27, “Doctor”); //adds to window
执行模型如下所示
用此种方法创建的对象直接被添加到window对象中,作用域链中0指向widnow对象,1指向活动对象

【JavaScript核心技术卷】创建实例对象_第14张图片

//使用call方法,可以改变方法调用中的this所引用的对象,尽管该对象的原型链中并不存在该函数对象。
//相当于o.Person(“Kristen”, 25, “Nurse”)
var o = new Object();
Person.call(o, “Kristen”, 25, “Nurse”);
o.sayName(); //“Kristen”
此种构建对象的方法对应的执行模型如下
此种方法创建的对象执行模型与new Person()方式创建的执行模型类似,后台会自动给它创建一个变量对象,新对象的作用域链中0指向其变量对象,1指向其活动对象

【JavaScript核心技术卷】创建实例对象_第15张图片

//使用构造模式创建实例对象

//如果一个函数内部未使用全局变量,则这个函数可以作为一块代码来看待,不受外界参数变化的影响。否则,类的封装性遭到了破坏。
//JS的良好类封装性:
//1、实例方法的封装:
//		类中的方法成员(的代码)只能隶属于类本身(只有找到类,才能找到方法成员),
//	只能通过方法调用,而不应是全局函数。
//2、实例字段的封装:
//		因为需要合法性校验(完整性的约束一致性),
//	类中的实例成员只应通过类中的方法成员来访问(get和set访问器)。
//	如果通过引用直接访问实例字段,
//	我们就说类的封装性遭到了破坏(无法实现完整性的约束一致性)。
<!DOCTYPE html>
<html>
<head>
    <title>Constructor Pattern Example 3</title>
    <script type="text/javascript">
    
		//由于Person构造函数内部使用了全局变量,
		//则这个Person构造函数就不可以作为一块代码来看待,受外界参数变化的影响。//类的封装性遭到了破坏。
        function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
            //类中的方法不应被全局变量所引用,
			this.sayName = sayName;
        }
        
        function sayName(){
            alert(this.name);
        }
        
        var person1 = new Person("Nicholas", 29, "Software Engineer");
        var person2 = new Person("Greg", 27, "Doctor");
        
        person1.sayName();   //"Nicholas"
        person2.sayName();   //"Greg"
        
        alert(person1 instanceof Object);  //true
        alert(person1 instanceof Person);  //true
        alert(person2 instanceof Object);  //true
        alert(person2 instanceof Person);  //true
        
        alert(person1.constructor == Person);  //true
        alert(person2.constructor == Person);  //true
        
        alert(person1.sayName == person2.sayName);  //true        
                
    </script>
</head>
<body>

</body>
</html>

首先对person1,person2进行变量提升,初始化为undefined,如下所示

【JavaScript核心技术卷】创建实例对象_第16张图片

对person1进行赋值,创建person1的变量对象、活动对象以及作用域链
如下所示

【JavaScript核心技术卷】创建实例对象_第17张图片

对person2进行赋值,创建变量对象,活动对象以及作用域链,如下所示

【JavaScript核心技术卷】创建实例对象_第18张图片

执行到person1.sayName时,将sayName函数入栈,创建活动对象,构建作用域链,如下所示

【JavaScript核心技术卷】创建实例对象_第19张图片

执行到person2.sayName时,再次创建一个活动对象并构建其对应的作用域链,如下所示

【JavaScript核心技术卷】创建实例对象_第20张图片

你可能感兴趣的:(JavaScript核心技术)