传值类型
把传值类型放在第一部分讲 是因为 这个问题其实 困扰了我很久。在看了这篇文章 算有了一点了解。我们通常了解的传值类型有2种 按值传递(call by value) 和指针传递(call by reference)。我上学学c和java 的时候。 老师总是强调。java是按值传递 c是指针传递。当我使用java的时候,总对按值传递报以怀疑的态度。 因为传入对象的时候,改变这个对象的内部属性时候是可以改变外面的对象的内部属性的。 当时 解释是 按值传递传的是引用。当时也强迫自己接受了。
现在我有了更深刻的理解。 python 引入了 call by sharing 或者 call by object 的概念。传递的是这个对象指针的备份。写个例子区别一下 3种传递的区别。
- var aro1={x:1};
- var aro2={x:2};
- function test(aro1,aro2){
- aro1.x=100;
- aro2={x:200};
- }
- test(aro1,aro2);
- console.log(aro1);
- console.log(aro2);
java,js,ruby,python等语言都是用的第三种 (call by object)的方法传值的。
对于上面的结果
按值传递结果:
{x:1}
{x:2}
指针传递结果
{x:100}
{x:200}
call by object:
{x:100}
{x:2}
相信看到例子就能够分辨出来区别。
按值传递是完全的备份传递。 函数里面不会改变外面的对象。
引用传递完全相反。既然是引用 那么改变了自然就都改变了。
call by object 是传递的引用的备份。 如果改变的是这个对象的属性。 那么引用本身没有该变。 和外面的仍然指向一个地方。所以都改变了。 但是 如果在函数里面 将指针指向另外的地方。只是备份的指针指向了别的地方。本身并没有改变。 所以 不会影响外部的对象
数据类型
对于数据类型。 可能是很对对js了解不够深入的人最大的误解了。 很多人包括很多书籍都有这么一句话“js里面 一切皆对象”。如果你想深入了解js 。就待放弃这个观点。
js应该有9种类型,其中6种是我们开发层次接触的,分别是:
undefined ,Null,
Boolean, String ,Number ,Object
另外还有3种在底层实现
Reference List Completion
为什么我们会得到js里面一切都是对象。 并且很多时候都感觉很正确的。这是因为js里面当你对其他的类型属性读取它的属性的时候,会先通过 toObject 方法变成 object 然后 再移除这个对象。这里就出现了很多错觉。比如:
- var x=1;
- alert(x.toString());
- alert(x.toString===Object(x).toString);
看看这个 。x 很明显 是 number 类型的。但是缺有toString 方法。 但是这是js 处处皆对象的证据嘛? 不是的。
当你调用x.tostring的时候。js 会先将x 转化换成object。 这时候 就有了 toString 方法。 在之后 立刻remove'掉转化出来的对象
- var x=1;
- x.xx=1;
- alert(x.xx);
- x={};
- x.xx=1;
- alert(x.xx);
对于我们常用的六种类型就不讨论了。需要注意的是 typeof 是可以检测出到底是上面类型。 除了 NULL。 typeof null 在以前被认为是一个bug 跟规范不同。但是 到 es5 规范的时候,规范改成了 typeof null 为 object 但是。它不是object类型。
接下来 了解一下 Reference List Completion
List 类型 是用在函数传参数的时候使用。
Completion 主要是在 break continue return throw的时候使用
我需要关心的 其实主要是 引用类型:reference type
引用类型里面 有2个内容。 可以简单的表示为 base (拥有属性的那个对象) 和 propertyName (属性名);
引用类型 在只在2种情况下使用到:
1 处理一个标示符(we deal with an identifier)(var x)
2 访问一个属性(with a property accessor)(xx.xx xx['xx'])
对于访问引用类型 会通过自己的实现去获取所需要的值。(可以简单理解为 base['propertyName ']这个去获取)
上下文
上下文主要由变量对象,this等组成:
变量对象(vo)
在一些js 引擎的实现中 vo是用__parent__ 属性来表示的。我们甚至能够 访问到它 并且改变它。
变量对象(vo):变量对象是上下文里面的一个隐藏对象。保存着我们定义的数据,
变量对象包括3个内容:
1.声明的变量
2.声明的方法
3.函数的形参
只有在全局上下文中 我们才可以通过vo直接访问变量。因为这时候vo就是 global 本身。而在其他上下文我们是访问不到vo的。vo只是内部实现。
我们定义的变量、方法都是vo的属性。
在全局上下文中 ,vo===this===global
在函数上下文中,我们在进入函数上下文的时候创建vo,这时候称呼他为ao(
activation object)。在刚进入这个上下文的时候 ao 就有了属性。就是 arguments。然后 会扫描 变量声明 和函数声明。
扫描优先级为(主要指同名的时候 覆盖问题):函数>参数>变量。
- function test(x){
- alert(x);
- function x(){
- }
- }
- test(1);
- function test(x,y,z){
- console.log(arguments)
- }
- test(1,2);
arguments 主要有下面几个属性组成。
callee 调用的函数本身
length 参数长度。(这里需要注意 这个长度 是参数的真实长度 看上面的例子 为2.)
properties-indexes 就是参数 数组。 里面的个数 是和上面的length 一致的。 并且和传递进来的参数指向同一地址。这里需要注意的是 比如上面的例子 arguments[0]和x是共享的。arguments[1]和y是共享的。但是 arguments [2]和 y却不是共享的。
this
this 可以说是和函数无关的一个东西。 这是很多人无法理解的。 在一个函数里面,this 可能会在不同的情况下是不同的值。this其实是跟上下文相关的。只有进入一个上下文才会出现this。其实 最简单的(可能不是特别准确)的理解:
this,就是 调用这个方法的那个对象。 比如: a.xx() 调用xx方法产生的上下文里面 this 就是指向a这个对象。
- var foo = {x: 10};
- var bar = {
- x: 20,
- test: function () {
- alert(this === bar); // true
- alert(this.x); // 20
- }
- };
- bar.test(); // true, 20
- foo.test = bar.test;
- foo.test(); // false, 10
。
上面的例子
- var foo ={
- bar:function(){
- console.log(this)
- }
- }
- foo.bar();
- alert(foo.bar === (foo.bar||foo.bar));
- (foo.bar||foo.bar)();
上面的例子 foo.bar 是引用类型 所以 this 就是 foo。(foo.bar||foo.bar) 经过运算后 已经不是 引用类型了。是一个函数对象。 所以 base 是null 就是 全局对钱
作用域链
js里面 作用域链 是通过 函数的一个隐藏属性 [[scope]]来实现的。这个属性是在函数定义的时候静态创建的(不变)。依赖于这个函数存在的上下文。 [[scope]]是跟函数定义相关(跟执行这个函数进入的上下文 无关) 而作用域链却是跟上下文相关的。原因是作用域链 = [[scope]]+ao
而这个作用域链 又用于 在这个上下文中定义的函数的 [[scope]]
这么理解 可以递归的发现 其实 作用域链 最后就是 一堆 上层 上下文 的 ao 的集合。
- var x = 10;
- function foo() {
- var y = 20;
- function bar() {
- var z = 30;
- alert(x + y + z);
- }
- bar();
- }
- foo(); // 60
- /*
- globalContext.VO === Global = {
- x: 10
- foo: <reference to function>
- };
- foo.[[Scope]] = [
- globalContext.VO
- ];
- fooContext.AO = {
- y: 20,
- bar: <reference to function>
- };
- fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.:
- fooContext.Scope = [
- fooContext.AO,
- globalContext.VO
- ];
- bar.[[Scope]] = [
- fooContext.AO,
- globalContext.VO
- ];
- barContext.AO = {
- z: 30
- };
- barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:
- barContext.Scope = [
- barContext.AO,
- fooContext.AO,
- globalContext.VO
- ];
- - "x"
- -- barContext.AO // not found
- -- fooContext.AO // not found
- -- globalContext.VO // found - 10
- - "y"
- -- barContext.AO // not found
- -- fooContext.AO // found - 20
- - "z"
- -- barContext.AO // found - 30
- *
- */
闭包与作用域链
闭包是个很重要的东西。 它又是跟作用域链紧密相连的。所以就放在这里了
在js里面 严格的来说 每个函数都是一个闭包。 因为它都可以通过作用域链访问外部的变量。
而 闭包神奇的地方是因为,函数的 [[scope]]属性是静态的。跟函数定义的位置相关。 而上下文 却不是跟函数定义的位置相关。 这就导致了 有的函数在执行的时候。 定义他的上下文已经消失了。 却可以通过作用域链来访问在消失的上下文中的变量。
- var x = 10;
- function foo() {
- alert(x);
- }
- (function () {
- var x = 20;
- foo(); // 10, but not 20
- })();
- function foo() {
- var x = 10;
- var y = 20;
- return function () {
- alert([x, y]);
- };
- }
- var x = 30;
- var bar = foo(); // anonymous function is returned
- bar(); // [10, 20]
这就是一个闭包了。
函数
函数 最需要搞清楚的 就是 函数声明 和函数表达式。
函数声明
函数声明的特点:
1 必须有名字
2 代码要么在程序级别 要么直接在在另外一个函数体里(意思是 不可以在类似代码快中。)
3 在进入上下文的社会就创建
4影响变量对象 vo
5格式必须是
function xxx (){
}
对于我们程序员来说 函数声明和函数表达式 最重要的一点事 对 变量对象的影响。函数声明 是保存在vo中的 。而vo 我们知道 在进入上下文的时候 就会被 变量,函数,形参 所填充。 所以 在函数声明的前面 就可以访问这个函数了。
1 只能定义在表达式的位置。
2 可以有名字但是不必须。
3不影响vo
4在代码执行阶段才创建
在这里 需要说明的是
只要不符合函数声明的特点的都是函数表达式
即使像这样
// in parentheses (grouping operator) can be only an expression (function foo() {}); // in the array initialiser – also only expressions [function bar() {}]; // comma also operates with expressions 1, function baz() {};
对于函数表达式 可以在表达式后面加上 ()来自执行。 需要注意的是 只要是函数表达式 加上()就自执行。
我们最常用的是:
- (function(){
- alert(1)
- })()
- (function(){
- alert(1)
- }())
其实 只要让编译器辨认出事函数表达式 就可以了 即使这样:
- 1,function(){
- alert(1)
- }()
- var xx={
- bar :function(){
- alert(1)
- }()
- }!
- function xx(){
- <span style="white-space:pre"> </span>alert(1)
- }()
这里 有个 es5 规范的特性方法 我觉的很有用。 是bind方法
函数 可以有bind 方法 来绑定这个方法的 this 值。 即使通过call 来调用 也改变不了this值。
原型与原型链
js 的面向对象性质是基于原型实现的。(区别于 基于类--包括静态类和动态类)
基于原型 其实 跟基于动态类的实现(python)很像.我们更需要注意的是 跟 静态类的实现(java ,c)的区别
基于类的实现:
1穿件一个对象必须已经定义了它的类。
2对象会按照本身的 结构和行为创建。
3 通过一个 严格的 直接的 不可改变的 继承链来解析方法
4 子孙类中包含所有继承链中的属性。即使这些属性可能用不到。
5 类在创建后不能改变它的实例的任何特性。(无论方法和属性)。
6 实例不能添加于类结构不同的行为或属性。
基于原型的
1
基本概念是对象(而不用先定义它的类);
2对象是完全动态和可变的(并且,理论上可以完全从一种类型变成另一种类型);
3对象没有严格的类来描述它们的结构和行为,对象不需要类;
4然而,虽然没有类,但是对象可以有原型(prototypes),以便于当它们自身不能应答消息时委托原型;
5对象的原型可以在运行时的任何时刻改变;
6在基于委托(delegation based)的模式中,改变原型的特性将会影响到和这个原型相关的所有对象;
7
在串联式原型(concatenative prototyping)模式中,原型是对象克隆的原始拷贝,因此变得完全独立,改变原型的特性不会影响到根据它克隆出的对象;
8如果不能响应一个消息,可以发信号给调用者,以便于采取额外的措施(比如改变调度);
9对象的识别可以不通过它们的层级位置或者它们属于哪个具体的类型,而是通过对当前拥有的特性。
每个对象都有原型。 在js 里面 我们需要区分一下 一个 显示 的 prototype 和 一个 隐式的 [[prototype]](在一些js 引擎中 为通过
__proto__这个属性来实现的。 甚至可以调用到它)
对于一个函数 他有显示的 prototype 。我们可以设置他的值。而对于其他的对象,我们通过 隐式的 [[prototype]]来 取值。 不能改变它。 当你 new 一个对象的时候。 我们 将这个对象的隐式[[prototype]]指向 构造函数的 prototype(注意 这里 在js传递参数都是 call by object 所以这里只是复制了一个 prototype的指针 付给了 [[prototype]] 。 这也导致了 改变了 prototype 以后 new的 对象的prototype 都会改变)
在这里 有个比较不错的 继承的实现方法:
- function inherit(child, parent) {
- var F = function() {};
- F.prototype = parent.prototype;
- child.prototype = new F();
- child.prototype.constructor = child;
- child.superproto = parent.prototype;
- return child;
- }