参考《JavaScript高级程序设计3版》
javaScript预编译过程。
javaScript运行三个步骤:
- 语法分析
- 预编译 //在内存中开辟一些空间,存放一些变量与函数。
- 解释执行 /执行代码
JS预编译什么时候会发生?
- script中的代码执行前
- 函数执行前
<script type=“text/javascript”>
var a = 1;
//console.log(a); //1
function test(a) {
console.log(a); //ƒ a() {}
var a = 123;
console.log(a); //123
function a() {}
console.log(a); //123
var b = function() {}
console.log(b); //ƒ () {}
function d() {}
}
var c = function (){
console.log("I at C function");
}
console.log(c);
test(2);
</script>
分析:
- 页面产生便创建了GO对象(Glabal Object) (也就是window对象);
- 第一个脚步文件加载
- 脚本加载完毕后,分析语法是否合法;
- 开始预编译
查找变量声明,作为GO属性,值赋予undefined
查找函数声明,作为GO 属性,值赋予函数体;
//预编译:
//抽象描述
GO/window = {
a: undefined,
c: undefined,
test: function(a) {
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
function d() {}
}
}
//解释执行代码(直到test(2),不包含执行test(2))
//抽象描述
GO/window = {
a: 1,
c: function (){
console.log("I at C function");
},
test: function(a) {
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
function d() {}
}
}
//执行函数test()之前,发生预编译
//1. 创建AO活动对象(Active Object);
//2. 查找形参和变量声明,值赋予undefined;
//3. 实参赋给形参;
//4. 查找函数声明,值赋予函数体;
//预编译之前面1、2两小步如下:
//抽象描述
AO = {
a:undefined,
b:undefined,
}
//预编译之第3步如下:
//抽象描述
AO = {
a:2,
b:undefined,
}
//预编译之第4步如下:
//抽象描述
AO = {
a:function a() {},
b:undefined,
d:function d() {}
}
//执行test()函数时如下过程变化:
//抽象描述
AO = {
a:function a() {},
b:undefined,
d:function d() {}
}
//--->
AO = {
a:123,
b:undefined,
d:function d() {}
}
//--->
AO = {
a:123,
b:function() {},
d:function d() {}
}
注意:匿名函数不参与预编译;
函数执行前预编译
- 创建AO对象(Activie Object)
- 查找函数形参及函数内变量声明,形参名及变量名作为AO对象的属性,值为undefined
- 实参形参相统一,实参赋给形参
- 查找函数声明,值赋予函数体。
脚本代码块script执行前预编译
- 查找全局变量声明(包括隐式全局变量声明,省略var 声明),变量名作为全局对象的属性,值为undefined
- 查找函数声明,函数名作为全局对象的属性,值为函数体/函数引用。
预编译特点,或表象。
1: 函数声明提升。这意味着可以把函数声明放在调用它的语句后面。
2: 变量声明提升
预编译例子
var a=1;
if(true){ //没有块级作用域
var b=1;
}
console.log(b);
var a = 1;
var b = 2;
function doit(){
console.log(b);
var b = 3;
console.log(b);
}
doit();
console.log(b);
作用域
var a=1;
if(true){
var b=1;
}
console.log(b); //1
var a = 1;
var b = 2;
function doit(){
console.log(b); //undefined; 预编译的时候b被提升了值为undefined;
var b = 3;
console.log(b); //3
return function () {
//console.log(b); //打开,作用域链包含doit的scopes
console.log('inner');
}
}
console.dir(doit); //ƒ doit()
var libian = doit();
console.log(b); //2
console.dir(libian);//注意scopes 从它的外围 AO开始直到GO
libian(); //inner
function kong() {
}
console.dir(kong); //scopes 对应GO
doit 在<script>内代码预编译时函数被声明后; doit 就有了[[Scopes]];
libian 在doit()执行后,libian就被声明了,libian就有了[[Scopes]];到底需不需要doit对应的scopes 或说活动对象,js引擎能够判断。
定义函数的两种方式:
1. 函数声明, 2 函数表达式
//2函数表达式,也叫匿名函数
sayHi(); //报错
var sayHi = function(){
alert('Hi!');
}
//可以这样做
var condition = true;
if(condition){
sayHi = function(){
alert('Hi');
}
}else{
sayHi = function(){
alert('Yo!');
}
}
sayHi(); //fire:hi;360:hi;google:hi
函数递归
常见的好的写法如下:
//递归
function factorial(num){
if(num<=1){
return 1;
}else{
return num*arguments.callee(num-1);
}
}
alert(factorial(4)); //24
//arguments.callee 是一个指向正在执行的函数的指针。比使用函数名更保险。在严格模式下不能使用
也可以使用
//可以使用命名函数表达式
var factorial = function f(num){ //这里的f根本不是变量;可以在最外边加上小括号
if(num<=1){
return 1;
}else{
return num*f(num-1);
}
};
//console.log(f(4)); //f未定义
//console.log(f); //f未定义; 这个也不是function后面的f
f=null;
console.log(factorial(4));
console.dir(factorial);
console.log(factorial);
分析为什么f=null 没有用,因为 function f的f根本不是变量;它只是函数的名字而已,最多算内部变量供引擎使用。
闭包
指它有权访问另一个函数作用域中的变量,它是函数。
创建常见方式: 在函数内部创建另一个函数。
当某个函数被调用时,会创建:
一个活动对象(activation object) 或说AO;
一个执行环境(execution context)或说作用域链;包含了AO;或说执行环境里边有this, 有arguments, 有作用域链,有AO;注意this引用的和AO不是一个东西。
//作用域链本质上是一个指向变量对象的指针列表。
难道说执行环境里不仅仅只包含作用域链,还包括别的?
//闭包,是指有权访问另一个函数作用域中的变量的函数。创建闭包的方式,就是在一个函数内部创建另一个函数,
function createComparisonFunction(propertyName){
var a = 1;
console.log(arguments);
var that = this;
return function(object1,object2){
//console.log(this);
console.log(this.arguments);
console.log(object1);
console.log(arguments);
that;
a=2;
a = null;
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if(value1<value2){
return -1;
}else if(value1>value2){
return 1;
}else{
return 0;
}
}
}
//当某个函数被调用时,会创建一个执行环境,相应的作用域链,活动对象。在函数的执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。
//全局变量的对象始终存在。一般来讲当函数执行完毕,局部活动对象就会被销毁。但闭包不同
console.dir(createComparisonFunction);
var compareName = createComparisonFunction('name');
console.dir(compareName);
var result = compareName({name:'lhao'},{name:'liang'});
console.log(result);
compareNames = null;
//由于闭包会携带包含他的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。
说明函数执行完成后会销毁arguments caller等这些
闭包与变量
副作用:闭包只能取得包含函数(外围函数)中任何变量的最后一个值。
//闭包与变量
function createFunctions(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = function(){
return i;
}
}
return result;
}
var funs = createFunctions();
var val1 = funs[0]();
alert(val1); //10;
alert(funs[1]()); //10;
function createFunctions(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
var funs = createFunctions();
alert(funs[0]());
alert(funs[1]());
alert(funs[2]());
关于this对象/this对象
this对象是在【运行】时基于 函数的 【执行环境】绑定的。
在全局环境中 this 等于window.
闭包环境的执行环境通常具有全局性, 因此this对象通常指向window。(但要注意一些有关this赋值的情况)
当然通过call()或apply()改变函数执行环境的情况下,this就会指向其他对象。
//每个函数在被调用的时都会自动取得两个特殊变量:this和arguments.内部函数在搜索这两个变量时,只会搜到其活动对象为止。
var name = 'the window';
var object = {
name:'My object',
getNameFunc:function(){
var that = this;
return function(){
return that.name;
};
}
}
alert(object.getNameFunc()());//my object
//this和arguments也存在同样的问题。
var name = 'the window';
var object = {
name:'my object',
getName:function(){
return this.name;
}
}
alert(object.getName());//my object
alert((object.getName)());//my object
alert((object.getName = object.getName)());//the window
//有可能意外得改变this的值。
怎么理解最后一个代码含义:
a= b 表达式返回b,所以 :相当于
function(){
return this.name
}();
//执行环境是全局
内存泄漏
闭包的问题:容易引起内存泄漏,外部的活动对象得不到消除。
//内存泄露
//IE9之前
function assignHandler(){
var element = document.getElementById('someElement');
element.onclick = function(){
alert(element.id);
}
}
function assignHandler(){
var element = document.getElementById('someElement');
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
模仿块级作用域
写一个函数表达式(或匿名函数)立即调用;在全局环境中也可以这么写;写在函数中也可以。
(function(){
// 这里是块级作用域
})();
这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。适用在大型项目多人协作,或者引入了多个插件。
私有变量
严格来说js中没有私有成员的概念; 所有对象的属性都是公有的。可以o.属性;
访问器属性使用 get set 设置访问有点像私有属性的行为但不是。但是有私有变量的东西,可以利用这个特点 来模仿对象的私有属性,做一些事情。
//p205私有变量
//javascript中没有私有成员的概念;所有对象属性都是公有的。在函数中定义的变量(包括函数的参数,局部变量,在内部定义的其他函数),都可以认为是私有变量,
function MyObject(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function(){
privateVariable++;
//return privateFunction();
}
return function () {
privateVariable++;
}
}
var person = new MyObject();
console.log(person); //如果有return 则是return后的东西;不再是一个对象
// console.dir(person.publicMethod); //privateVariable: 11
// var pri = person.publicMethod();
// //console.log(pri);
// console.dir(person.publicMethod); //privateVariable: 11
// var person1 = new MyObject();
// console.dir(person1.publicMethod); //privateVariable: 10;
var perFun = MyObject();
console.dir(perFun); //privateVariable : 11;
perFun();
console.dir(publicMethod); //privateVariable : 11;
var perFun2 = MyObject();
console.dir(perFun2); //privateVariable : 10;
术语: 有权访问私有变量和私有函数的公有方法称为特权方法(private method)
在构造函数中定义特权访问方法优缺点: 每个实例都有会创建同样一组方法。不好!
静态私有变量:
在私有作用域中;定义私有变量或函数;创建特权方法。 将特权方法(匿名函数/函数表达式)定义为全局;或者将特权方法定义为全局里边对象的方法。
//p206 静态私有变量
(function(){
//私有变量和私有函数
var privateVariable = 10; // 属于MyObject类型的 静态私有变量
function privateFunction(){
return false;
}
MyObject = function(){
};//初始化未声明的变量,总是会创建一个全局变量。
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
}
})();
var person1 = new MyObject();
console.dir(person1.publicMethod); //scopes privateVariable : 12
person1.publicMethod();
var person2 = new MyObject();
console.dir(person2.publicMethod);
person2.publicMethod(); //privateVariable : 12
例子2:
(function(){
var name = '';
Person = function(value){
name = value;
};
Person.prototype.getName = function(){
return name;
}
Person.prototype.setName = function(value){
name = value;
}
})();
var person1 = new Person('zhao');
console.log(person1.getName());//zhao
person1.setName('liang');
console.log(person1.getName());//liang
var person2 = new Person('liang2');
console.log(person2.getName());//liang2
console.log(person1.getName());//liang2
//结:会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量。
模块模式:module pattern
为单例创建私有变量和特权方法。
name : 'xiaoming',
method : function () {
//这是方法的代码
}
}
var singleton = function () {
//私有变量和私有函数
var privateVariable = 10;
function privateFunction() {
return false;
}
//特权方法/ 公有方法和 属性
return {
publicProperty : true,
publicMethod : function () {
privateVariable++;
return privateFunction();
}
}
}();
console.dir(singleton.publicMethod);
singleton.publicMethod();
这种模式需要对单例进行某些初始化,同时又需要维护其私有变量时。
//p207模块模式
//前面的模式是用于为自定义类型创建私有变量的和特权方法的。
//模块模式则是为单例创建私有变量和特权方法。单例就是只有一个实例的对象。
var application = function(){
//私有变量和函数
var components = new Array();
//初始化
//components.push(new BaseComponent());
//公共
return {
getComponentCount:function(){
return components.length;
},
registerComponent:function(){
if(typeof component == 'object'){
components.push(component);
}
}
};
}();
//在web应用程序中,经常需要使用一个单例来管理应用程序及的信息。
console.dir(application.getComponentCount);
分析: 每个单例都是Object的实例,单例通常作为全局对象存在的,我们不会将它传递给一个函数。因此没必要使用instanceof操作符
增强的模块模式
适用于:必须是某种类型的实例,同时还必须添加某些属性和方法对其增强。
var singleton = function () {
//私有变量和私有函数
var privateVariable = 10;
function privateFunction() {
return false;
}
//创建对象
var object = new CustomType();
//添加特权/ 公有属性和方法
object.publicProperty = true;
object.publicMethod = function () {
privateVariable++;
return privateFunction();
}
//返回这个对象
return object;
}();
//增强的模块模式
var application = function(){
//私有变量和函数
var components = new Array();
//初始化
components.push(new BaseComponent());
var app = new BaseComponent();
app.getComponentCount = function(){
return components.length;
}
app.registerComponent = function(){
if(typeof component == 'object'){
components.push(component);
}
}
return app;
}();