深入理解JavaScript
来源:互联网 发布:成都培训java多少人 编辑:程序博客网 时间:2024/06/04 17:58
1 变量
1.1 基本类型和引用类型的值
var num1 = 5;var num2 = num1;num1 ++;console.log(num2);//5
var obj1 = new Object();var obj2 = obj1;obj1.name = "Dendi";console.log(obj2.name);//Dendi
同样是复制,num1和num2的值完全独立,而obj1和obj2却是指向同一个对象。
1.2 传递参数
ECMScript中所有函数的参数都是按值传递的!把函数外部的值赋值给函数内部的参数(arguments),如同把一个变量复制到另一个变量一样。
function addTen(num){ return num + 10;}var num1 = 5;addTen(num1);console.log(num1);//5
function setName(obj){ obj.name = "Dendi";}var person= new Object();setName(person);console.log(person.name);//Dendi
上述代码中,num1作为参数传递给函数,在函数内部,参数num的值被加了10,但不会影响到外面num1变量,这个很好理解,也说明了基本类型的值是按值传递的。
但是对于第二点,为什么函数内部obj添加了name属性后,函数外面的person也会改变呢?很多人就认为这个obj参数是按引用传递了。
再看两段代码:
function setName(obj){ obj.name = "Dendi"; obj = new Object(); obj.name = "Miracle";}var person = new Object();setName(person);console.log(person.name);//Dendi
var person1 = new Object();var person2 = person1;person2.name = "Dendi";person2 = new Object();person2.name = "Miracle";console.log(person1.name);//Dendi
上面两段代码的实现效果是一样的。说明person->obj的传递方式和person1->person2的方式一致,否则结果就应该和下面的一样了。
var person = new Object();person.name = "Dendi";person = new Object();person.name = "Miracle";console.log(person.name);//Miracle
现在我知道了,函数的参数传递和变量传递一样,而且这个外面的person和里面的obj访问的是同一个对象就好了。
至于上面讨论的问题:参数按什么传递,Does it matter?
2 执行环境和变量对象
执行环境(execution context,简称context)是一个抽象的概念。
每个context里有一个与之关联的变量对象,context里面所有定义的变量和函数都放在这个对象里面。
ECMScript执行机制:每个函数都有一个自己的context,当代码执行到一个函数里时,函数的context会被推入到一个堆栈里面,堆栈底部永远都是全局上下文(global context),而当函数执行完时,堆栈将其context弹出。
JS在运行context
代码时,分为两个阶段
- 进入context
- 执行代码
如果这个context是函数,我们把活动对象作为变量对象,活动对象一开始只包含arguments对象。(全局上下文中的变量对象和函数上下文中的变量对象的区别!!)
进入context时,活动对象包括:
- 函数的形参
- 函数声明
#如果变量对象已经存在相同名称的属性,则完全替换这个属性 - 变量声明
#由名称和对应值(undefined)组成
#如果变量名称跟已经声明的形参或函数相同,则变量声明不会替换它
有了这个概念,下面的结果就很好理解了
alert(x); // functionvar x = 10;alert(x); // 10x = 20;function x() {};alert(x); // 20
//进入上下文阶段:变量声明没有影响同名的function的值VO= { x: <reference to FunctionDeclaration>};//执行代码时VO['x'] = 10;VO['x'] = 20;
3 作用域链
定义:
作用域链这个概念很好理解,其目的是为了保证在内部函数的执行环境中,能对变量对象进行有序的访问,也用于查找变量。
函数被调用时,会创一个执行环境以及相应的作用域链。
作用域链的顶端始终是当前执行的代码所在的环境的变量对象。它的下一个来自外部环境(父函数)的变量对象,再下一个来自更外部的环境的变量对象,一直延续到全局环境的变量对象。
函数的生命周期
- 函数创建
创建函数时,会预先创建一个包含父-变量对象函数的作用域链,并保存在[[scope]]属性中。而这个[[scope]]在函数创建时被存储,静态的(不变的)。 - 函数激活
函数调用时,执行环境被创建,然后复制函数的[[scope]]属性中的作用域链,同时有一个活动对象被创建,并推入作用域链中。
code:
var x = 10;function foo() { var y = 20; function bar() { var z = 30; alert(x + y + z); } bar();}foo(); // 60
对此,分析如下:
1.全局环境的变量对象:
globalContext.VO = { x:10, foo:<< function >>}
2.foo创建
foo[[scope]]={ globalContext.VO}
3.foo激活
//活动对象fooContext.AO = { y = 20; bar:<< function >>}//作用域fooContext.scope = fooContext.AO + foo[[scope]]);//作用域链fooContext.scopeChain = [ fooContext.AO, globalContext.VO]
4.bar被创建
bar[[scope]]=[ fooContext.AO globalContext.VO];
5.bar被激活
//活动对象barContext.AO = { z:30}//bar作用域barContext.scope = barContext.AO + bar[[scope]];//bar作用域链barContext.scopeChain = [ barContext.AO, fooContext.AO, globalContext.VO];
4 闭包
自动垃圾回收
ECMScript使用自动垃圾回收。这一规范没有定义细节,留给开发者整理,目前一些已知的实现里,垃圾回收的操作优先级很低。但一般来说,如果对象变得不可引用了(没有剩余的引用给执行代码访问),它就可以被垃圾回收并且在未来的某一时刻被摧毁,它所消耗的资源也会被释放还给系统。
在退出执行环境时,这一现象经常发生。作用域链、活动/变量对象以及任何在执行环境里创建的对象,包括函数对象,都会变得不可获得并且会被垃圾回收。
创建闭包
function exampleClosureForm(arg1, arg2){ var localVar = 8; function exampleReturned(innerArg){ return ((arg1 + arg2)/(innerArg + localVar)); } /* return a reference to the inner function defined as - exampleReturned -:- */ return exampleReturned;}var globalVar = exampleClosureForm(2, 4);globalVar (8);globalVar = null;//解除对exampleReturned函数的引用,释放内存。
上面一章说道,函数内部定义的函数,它的作用域链里会包含外部函数的活动对象。
因此,当exampleReturned被返回后,它的作用域链会包含exampleClosureForm的活动对象和全局变量。这样,exampleReturned内部可以访问exampleClosureForm中定义的所有变量。而且,exampleClosureForm函数结束后,其活动对象也不会销毁。因为exampleReturned的作用域仍在引用他们。
正是由于“闭包”会携带包含它的函数的作用域,因此它会比其他函数占用的内存过多。
一个[[scope]]
在ECMAScript中,同一个父上下文中创建的闭包是共用一个[[Scope]]属性的
任何一个闭包里面改变了scope的属性,也会影响到其他闭包函数的取值。
eg.
var data = [];for (var k = 0; k < 3; k++) { data[k] = function () { alert(k); };}data[0](); // 3, 而不是0data[1](); // 3, 而不是1data[2](); // 3, 而不是2
5 关于this对象
this取决于调用函数的方式!!
var name = "window"var foo = { name : "foo", bar:function(){ alert(this.name); alert(this === foo); }};foo.bar();//foo true(foo.bar = foo.bar)();//window false;
那调用函数的方式如何影响this值?我们需要引入一个概念:引用类型(reference type)
内部的引用类型并不是一个数据类型,一个引用类型其实是一个对对象属性的引用。它包含两个组件,base object和property name。
//两个抽象方法:referenceType:{ GetBase(V):返回base object GetPropertyName(V) :返回属性名}
引用类型的值有两种情况:
- 处理标示符
标识符是变量名,函数名,函数参数名、全局对象中未识别的属性名。
- 处理属性访问器
foo.bar();foo['bar']();
那么引用类型的值和this如何关联呢?这里有一个规则:
如果调用括号()的左边是引用类型的值,那么this将设为引用类型值的base对象(base object)
其他情况下(与引用类型不同的任何其它属性),这个值为null。
eg.引用类型
var bar = 0;function foo() { alert(this.bar);}var x = {bar: 10};var y = {bar: 20};x.test = foo;y.test = foo;foo();//0x.test(); //10y.test(); // 20/*fooReference = { base:global, propertyName:'test' };testReference1 = { base:x, propertyName:'test' };testReference2 = { base:y, propertyName:'test' };*/
当调用括号的左边不是引用类型而是其它类型,那么这个值会自动设置为null,结果就是全局对象了。
看几个例子:
var foo = { bar: function () { alert(this); }};foo.bar(); // Reference, OK => foo(foo.bar)(); // Reference, OK => foo(foo.bar = foo.bar)(); // global?(false || foo.bar)(); // global?(foo.bar, foo.bar)(); // global?
结合上面说的引用类型
来看,第一个例子很好理解,关键是后面4个例子,为了看懂这些东西,博主搜了好久的资料,整理如下:
comma operator 和 simple assignment,都依赖于调用内部的GetValue方法。如果GetValue的参数不是引用而被直接返回的话,就会导致base object找不到了。如下代码所示:
// pseudo-codeGetValue(V) : if (Type(V) != Reference) return V; baseObject = GetBase(V); // in your example foo if (baseObject === null) throw ReferenceError; return baseObject.[[Get]](GetPropertyName(V)); // equivalent to baseObject[v.PropertyName];
而理解(foo.bar = foo.bar)() 和 foo.bar()不同的关键在于Simple Assignment 操作:
Simple Assignment (`=`)The production `AssignmentExpression` : `LeftHandSideExpression` = `AssignmentExpression`is evaluated as follows:1. Evaluate LeftHandSideExpression.2. Evaluate AssignmentExpression.3.Call GetValue(Result(2)).4.Call PutValue(Result(1), Result(3)).5.Return Result(3).
基本上,第4步put操作对(foo.bar = foo.bar) 没有影响,关键在于第5步返回了第3步得到的值,而第3步得到的值,其实它并没有一个base object。
- 深入理解JavaScript函数
- 深入理解javascript
- 深入理解 javascript
- 深入理解JavaScript系列
- 深入理解JavaScript系列
- 深入理解JavaScript系列
- 深入理解JavaScript系列
- 深入理解JavaScript
- 深入理解JavaScript系列
- JavaScript this 深入理解
- 深入理解JavaScript系列
- 深入理解JavaScript系列
- 深入理解JavaScript系列
- 深入理解JavaScript系列
- 深入理解JavaScript-replace
- 深入理解JavaScript系列
- 深入理解JavaScript
- 深入理解javascript函数
- iOS代码技巧之判断设备及状态
- 关于系统登录那件事
- Failure to transfer org.codehaus.plexus:plexus-io:pom:1.0,Failure to transfer org.codehaus.plexus:pl
- yum安装redis详解
- 【Django】数据库操作
- 深入理解JavaScript
- etcd:从应用场景到实现原理的全方位解读
- 9. Palindrome Number
- hog特征原理详解及matlab代码学习笔记
- liblinphone注册状态的变化
- 解决Fiddler "creation of the root certificate was not successful”的问题
- ExtJs (3.2.0)文件目录介绍、文件删减、文件引用
- jeecg <t:formvalid>标签提交前验证 beforeSubmit
- 字典树