JavaScript只有实例方法,所以JavaScript没有静态构造函数
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引擎在此充当的是一个第三方工厂的角色。
new Fn(args):使用new操作符时触发Fn 的[[Construct]] 方法处理逻辑,创建实例对象的步骤如下:
obj.[[Call]](args):
注意步骤6中,prototype指Fn对象显示的prototype属性,而[[Prototype]]则代表对象内部隐式Prototype属性。
构成对象Prototype链的是内部隐式的[[Prototype]](原型链),而并非对象显示的prototype属性。显示的prototype只有在函数对象上才有意义,从上面的创建过程可以看到,函数的prototype被赋给实例对象隐式[[Prototype]]属性,这样根据Prototype规则,实例对象和函数的prototype对象之间才存在属性、方法的继承/共享关系,实例对象与类之间无继承关系,类充当的只是一个第三方工厂的角色。
第一种 :创建实例对象代码, 返回值是基本值类型
//由于[[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;
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
ins.[[Call]](args):
6.执行全局代码 Fn.prototype={};
由于在创建实例obj后改写了Fn的原型,obj与Fn的继承关系断开
也即obj.[[Prototype]] == Fn.prototype; //result: false
第二种: 创建实例对象代码, 返回值是一个引用类型对象
//由于[[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>
第三种:创建实例对象代码, 构造函数的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
以下列出了自定义函数对象的几种方式
模式(Pattern):就是解决某一类问题的方法论。把解决某类问题的方法总结归纳到理论高度,那就是模式。模式是一种指导,在一个良好的指导下,有助于你完成任务,有助于你作出一个优良的设计方案,达到事半功倍的结果,而且会得到解决问题的最佳方案。模式在某类环境下(应用领域),要解决的问题。
问题:
解决创建多个相似对象的问题。
优点:
可以减少大量重复的操作代码。这种模式抽象了创建具体对象的过程。前面通过 new Object()或对象字面量,每次都需要给对象添加属性并赋值,会产生大量重复的操作代码。
缺点:
//使用工厂模式创建实例对象
<!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>
对象模型-执行模型
问题:
解决创建特定类型的多个相似对象的问题。
优点:
可以减少大量重复的操作代码。这种模式抽象了创建具体对象的过程。前面通过 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>
//使用构造模式创建实例对象
<!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活动对象
//函数调用
Person(“Greg”, 27, “Doctor”); //adds to window
执行模型如下所示
用此种方法创建的对象直接被添加到window对象中,作用域链中0指向widnow对象,1指向活动对象
//使用call方法,可以改变方法调用中的this所引用的对象,尽管该对象的原型链中并不存在该函数对象。
//相当于o.Person(“Kristen”, 25, “Nurse”)
var o = new Object();
Person.call(o, “Kristen”, 25, “Nurse”);
o.sayName(); //“Kristen”
此种构建对象的方法对应的执行模型如下
此种方法创建的对象执行模型与new Person()方式创建的执行模型类似,后台会自动给它创建一个变量对象,新对象的作用域链中0指向其变量对象,1指向其活动对象
//使用构造模式创建实例对象
//如果一个函数内部未使用全局变量,则这个函数可以作为一块代码来看待,不受外界参数变化的影响。否则,类的封装性遭到了破坏。
//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,如下所示
对person1进行赋值,创建person1的变量对象、活动对象以及作用域链
如下所示
对person2进行赋值,创建变量对象,活动对象以及作用域链,如下所示
执行到person1.sayName时,将sayName函数入栈,创建活动对象,构建作用域链,如下所示
执行到person2.sayName时,再次创建一个活动对象并构建其对应的作用域链,如下所示