Skip to content

Instantly share code, notes, and snippets.

@xuanyuanaosheng
Created August 11, 2014 08:08
Show Gist options
  • Save xuanyuanaosheng/76acbeadffee6a514c23 to your computer and use it in GitHub Desktop.
Save xuanyuanaosheng/76acbeadffee6a514c23 to your computer and use it in GitHub Desktop.

#JavaScript第五次记录整理

###关于上次函数的复习

对象的属性也可以是一个函数,比如 var a = {b: 9, c: function () {} };

这里 a.c 就是一个函数,对象的函数称为方法。其实没什么特殊的,只不过是一个属性而已.

不过呢,JS语言规定,在以 a.c() 这种形式调用函数时,在函数c调用过程中设置 this 变量为 a.

形式就是[obj].[prop]()的调用,会设置 this[obj] 其他以外和一般的函数没有任何区别.

利用这个特性,函数就可以知道调用自己在哪个对象上调用.

例子: var a = {b: 9, c: function (d) { return this.b + d; } }; 这样的话 a.c(3) 返回值就是 12

这里 this 被设置为 a.

如果有很多很多对象,都有相同的函数,要怎么设置呢?

这些函数除了 this 不同,其他都相同,也就是说函数本身都是一样的.

一种方法当然是把函数放到一个变量里,然后再分别用在每个对象上.

比如 var sayHello = function () { console.log("Hello from " + this.name) };

然后就可以 var alice = {name: "Alice", sayHello: sayHello}; var bob = {name: "Bob", sayHello: sayHello};

不过这样写起来太繁琐了。假如有一类对象都需要 sayHello 函数,那要写好久好久的.

###prototype

先来讲 prototype.JS 有一个叫做 prototype 的概念.

在查找某个属性的时候,如果这个对象上没有这个属性,则会到原型上继续查找.

比如 alice.sayHello 没有的情况下,就会取 alice 的原型上的 sayHello.

这个所谓原型,是一个隐藏属性,不能直接通过取属性来得到.

但是,原型的存在却使得原本应该取不到任何值的属性,变成去原型上取属性.

好吧,那么我们如何使用原型呢?

先来介绍 Object.create

这个函数的作用是以某个对象为原型,创建一个新的对象.比如 var b = Object.create(a);,那么返回的对象 b 的原型就是 a.

有了这个以后,我们可以这样做: var person = {sayHello: function () { return this.name; }}; var alice = Object.create(person); alice.name = "Alice"

这样 alice.sayHello 就会从原型 person 上取得 sayHello 属性了.于是 alice.sayHello() 就能够成立.

要注意的是,该对象上取某个属性的时候,该属性不存在,才会找原型上的属性.如果 alice.sayHello 存在,那么就不会继续找下去了.

对象的属性可以覆盖原型的属性.特别注意一下原型查找的条件只对取属性成立,存属性(赋值)并不成立.

因此,原型并不能允许共享可修改的属性,而一般只用来共享一些函数什么的.

原型也可以有自己的原型,这样依次查找上去,形成原型链.

对象的预设属性有些可以修改,有些不能.原型也是一个对象,所以上面可以放很多属性.

如果不是由 Object.create 创建的对象,比如 {} ,它的原型是 Object.prototype, 这个对象.

而 Object.prototype 没有原型(或者说原型是 null)原型链就是这样形成的.

刚才的例子: var person = {sayHello: function () { return this.name; }}; var alice = Object.create(person);,

这种情况下, 取 alice.sayHello 需要查找原型链 大致顺序就是 alice -> person -> Object.prototype -> null

其中查找到 person 就找到了,所以不再向上查找了.

原型和传统面向对象的最大的区别是,对象继承对象,而不是对象的类继承另一个类.

原型是一种联系关系(隐藏属性),而不是简单的复制所有属性过来.

person 也可以进行修改或者增加新属性,可以反映到 alice 上.

Object.getPrototypeOf(a); 可以获取 a 的原型.

这个例子中 Object.getPrototypeOf(alice) 会是 person.

有了这些以后,我们可以创建这样一个原型结构:鸡是一种鸟,而鸟是一种动物

可以是 var animal= { ... }; var bird = Object.create(animal); var chicken = Object.create(bird);

通过修改 Object.prototype ,可以给所有对象增加属性,包括那些直接写 {}

但是那个是不推荐的 因为会有太多东西受影响了 而且 Object.prototype 只有一个,所以可能会影响到不只是自己的代码,还有使用的库的代码,等等

###小练习 下面是一个小练习,通过 Object.create 创建10个不同的人,有不同的 firstName 和 lastName 。而原型上需要有 getFullName 方法(函数),用来获取全名(连在一起),需要在循环语句中使用 Object.create

###构造函数

JS提供了另一种很方便地创建对象的方法,叫构造函数.

写法是 var Person = function () { this.sayHello = ...; this.name = "Amy"; }.

可以看到构造函数首先是一个函数,而且构造函数中可以用 this 给正在创建的对象增加属性等.

调用方法是 var amy = new Person();

与一般函数调用类似,只是多一个关键字 new 。这个关键字 new 改变了构造函数中 this 的含义.

构造函数调用时,创建一个空对象,并且在构造函数调用过程中 this 变成那个对象。之后返回新创建的对象.

构造函数的一个好处就是可以批量给对象赋值.

任何用 new Person() 创建出的对象都经历过 Person 构造函数中的初始化.

var amy = {}; Person(amy); 之类的要方便.

构造函数还有一个作用,就是自动设置 prototype.

一个对象只能用一个构造函数创建.

从语法就可以看出 new Person() 不允许多个构造函数的名字.

如果是重载的话, JS 和很多动态语言一样,没有重载的.

不过可以自己判断参数来实现不同功能.

每个构造函数自带有一个 prototype ,如 Person.prototype.

然后呢,创建 new Person 的过程中,新创建对象的原型自动设置为 Person.prototype

于是,我们就可以 Person.prototype.sayHello = function () { ... };

用这样的方法,所有 new Person 创建的对象都可以从 Person.prototype 上获取到 sayHello 当然原理还是之前说过的原型链,这里就不多赘述了.

这种创建对象的方式(构造函数方式),常常和其他语言的类来相比.

这里 Person 可以看作是类.

new 后面接受一个函数(指向函数的变量)

我们通常使用这样的命名习惯,只要一个函数首字母是大写,就认为这个函数应该使用 new 来使用。如果第一个字母是小写则认为是不用作构造函数.

这只是众多风格之一,但是这里想要推荐这种风格.

因为一个构造函数不用 new 调用,就会变成一般函数,产生奇怪的后果

一个函数是否是构造函数,区别仅仅在于调用时是否增加了 new

用构造函数怎么实现继承呢?很简单,把 Person.prototype 设置成一个其他对象就好

比如, Person.prototype = Object.create(animal)

为什么不直接 Person.prototype = animal ?因为那样就没办法给 Person.prototype 单独增加属性了.

往往来说,一旦我们开始用构造函数,那么我们就会到处用构造函数。

可以想像 AnimalPerson 都是构造函数.

那么 Person.prototype = new Animal()

原型是一个对象,原型不是构造函数

不过这样的话, Animal 的构造函数必须允许构造一个空的,或者说非法的 new Animal() 对象,所以写构造函数的时候可能还没办法检查参数是否为空.

###instanceof

这个操作符,用法是 alice instanceof Person

作用就是,检查某个对象是否是通过某个构造函数创建的.

不过有一个特殊之处,为了达到类似继承的效果,这个操作符也会去翻 Person.prototype 的构造函数,之类的一直找下去.

对于 alice 继承关系上的每一个构造函数 Calice instanceof C 都返回 true ,其他为 false

因为 Person.prototype 默认是一个对象,而对象的构造函数是 Object ,所以 任何对象 instaceof Object 返回都是 true

alice instanceof Person; // --> true alice instanceof Object; // --> true

但少部分对象,比如 Object.prototype 并不是 instaceof Object ,否则就死循环了……

这也是另一个构造函数可以与“类”类比的地方

创建一个对象使用的构造函数可以使用 obj.constructor 属性来获取.不过注意的是,这个属性仅仅是反映了 obj 的构造函数而已,并没有什么特殊的地方

obj.constructor 这个属性可以修改的,不过修改这个属性并不会真的修改 obj 的构造函数

构造函数,像原型一样,也是一个隐藏属性,只不过 .constructor 的初始值碰巧和那个隐藏属性一样

总结一下。某个对象的原型是一个隐藏属性,决定了不存在的属性的下一步查找顺序,也决定了 Object.getPrototypeOf(a) 的返回值

某个对象的构造函数是一个隐藏属性,决定了 .constructor 属性的初始值,决定 instanceof 的返回结果(truefalse)。不用构造函数创建的对象,默认 constructorObject

用构造函数创建的对象,其原型隐藏属性被设置为构造函数的 .prototype 属性

这些说法是语法上正确规范的

那么关于构造函数和原型就介绍到这里吧

知道了构造函数以后,我们就可以去试试看一些基本对象的构造函数是什么了

"abc".constructorString ([]).constructorArray 9..constructor或者 (9).constructorNumber

9.constructor语法错误,因为据说……解释器会把那个点当做小数点吃掉,然后就不吐出来了

这些都是构造函数,至于怎么用,有哪些附加功能,大家可以自己去看相关资料

大家要明白对象的本质是“身份”,也就是不相等性。 像 {} !== {} ,但 9 === 9 ,所以数不具有身份

另外, var b = 9; 然后 b.prop = "Hello"; 是不能添加属性的

只是能执行而已,之后 b.prop 返回还是 undefined

这个就是数、字符串与对象的本质区别了

###this

this 在JS中,是一个特殊的变量,和调用有关,而和语法结构无关

this不像一般的变量一样。this 不受闭包影响

每次函数调用的时候, this 的值可能更改也可能不更改。调用完毕后, this 的值还原

首先我们解释一下,在全局范围内, this 的初始值是全局对象,比如 window

普通的函数调用,如 a(); 不改变 this 的值,也就是说 a 执行范围内的 this 和外部相同.

形式是 xxx.yyy() 的调用,在调用期间 this 设置为 xxx ,调用完毕后还原.

也就是说,在 yyy 函数执行的时候 thisxxx

new F() 期间, this 设置成新创建的对象(Object.create(F.prototype)),调用完毕还原

可以看到, this 的值依赖于调用的嵌套关系(或者说堆栈),并且依赖于调用的形式(普通调用、方法调用、构造函数调用),与定义的位置无关

环境提供的代码执行期间,允许任意更改 this 的值,只要返回的时候还原就可以

一个典型的例子就是 DOM 的事件处理函数,如 element.onclick = function () {};

element.onclick 函数调用期间, onclick 被设置成了此事件的当前元素,一般来说是 element

原因就是环境

所以说,在嵌套函数的时候,千万不要假设里面的 this 还是外面的 this

为了保持 this 的值,通常选择使用一个变量 var that = this; 然后在里面的函数里使用 that

也有用 self 等名字的。这名字只是一个变量而已

接下去要讲和 this 相关的几个函数, bind, callapply

首先 var func = function () {} ,然后 var boundFunc = func.bind(alice);

那么 boundFunc 是一个新的函数,这个函数调用 func ,但是调用期间 this 被固定设置成 alice 这个对象

也就是说, var func = (function () { .... }).bind(this) 就可以确保 ... 执行的时候 this 不变

callapply 提供的则是函数的另一种调用方法

a.call(alice, bob, cindy); 相当于 a(bob, cindy),唯一的区别就是 this 设置成 alice

如果在一个构造函数里需要调用另一个构造函数(比如完成“基类”的初始化),那么可以用 .call 来保证 this 不变

一个构造函数里调用另一个构造函数,显然再用 new 不行,因为那样就不是同一个对象了

a.apply 是一个接受数组作为参数的 call 变种。 a.apply(alice, [bob, cindy]) 相当于 a.call(alice, bob, cindy)

注意到第二个参数是一个数组,然后每一个数组的元素成为一个参数

这个可以用来做这样的事情: b.apply(this, arguments);

这样的话,就可以把一个函数的所有参数原封不动地用来调用另一个函数 有了这些以后,我们可以自己实现 bind : function (original, self) { return function () { return original.apply(self, arguments); } }

这个也可以用作 IEpolyfill

JS 没有提供改变 this 的任何办法,除了调用另一个函数以外

this = 9; 是不行的

然后,调用结束是自动复原的,请放心

##现在要布置一个最重要的作业了!

###JS 课程快要结束了……大家应该开始考虑下一步的发展方向了

之后应该可以有浏览器方向,NodeJS方向

请大家到环聊讨论啦~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment