第一章:关于this

为什么要用this

  1. function identify(){
  2. return this.name.toUpperCase()
  3. }
  4. function speak(){
  5. var greeting = "hello " + identify.call(this)
  6. console.log(greeting)
  7. }
  8. var me = {
  9. name:"Kyle"
  10. }
  11. var you = {
  12. name:"Reader"
  13. }
  14. console.log(identify.call(me)) //KYLE
  15. console.log(identify.call(you)) //READER
  16. speak.call(me) //hello KYLE
  17. speak.call(you) //hello READER

隐式“传递”一个对象引用

误解

1.误解一:指向自身

问题:**

  1. function foo(num) {
  2. console.log("foo" + num)
  3. //记录foo被调用的次数
  4. this.count++
  5. }
  6. foo.count = 0
  7. var i;
  8. for (i = 0; i < 10; i++) {
  9. if (i > 5) {
  10. foo(i)
  11. }
  12. }
  13. console.log(foo.count) //0


解决 => 创建另一个带有count属性的对象**

  1. function foo(num){
  2. console.log("foo" + num)
  3. //记录foo被调用的次数
  4. // this.count++
  5. data.count++
  6. }
  7. // foo.count = 0
  8. var data = {
  9. count:0
  10. }
  11. var i;
  12. for(i=0;i<10;i++){
  13. if(i>5){
  14. foo(i)
  15. }
  16. }
  17. console.log(data.count) //4


使用foo标识符替代this来引用函数对象,是 依赖于变量的foo的词法作用域

  1. function foo(num) {
  2. console.log("foo" + num)
  3. //记录foo被调用的次数
  4. foo.count++
  5. }
  6. foo.count = 0
  7. var i;
  8. for (i = 0; i < 10; i++) {
  9. if (i > 5) {
  10. foo(i)
  11. }
  12. }
  13. console.log(foo.count) //4



=> 真正的解决问题(发现问题而不是换一种方法)

强制this指向foo函数对象**

(?疑问 那么那个this到底指向谁 0 是哪里来的)

  1. function foo(num) {
  2. console.log("foo" + num)
  3. //记录foo被调用的次数
  4. //注意 在当前的调用方式下,this确实指向foo
  5. this.count++
  6. }
  7. foo.count = 0
  8. var i;
  9. for (i = 0; i < 10; i++) {
  10. if (i > 5) {
  11. //使用call(..)可以确保this指向函数对象foo本身
  12. foo.call(foo,i)
  13. }
  14. }
  15. console.log(foo.count)



**
如果从函数对象内部引用它自身,那只使用this是不够的,一般来说你需要通过一个指向函数对象的词法标识符(变量)来引用它.

  1. 这是具名函数,在它的内部可以使用foo来引用自身
  2. function foo(){
  3. foo.count = 4 //foo 指向它自身
  4. }
  5. 传入setTimeout(...)回调函数没有名称标识符(这种函数被称为匿名函数),因此无法从函数内部引用自身
  6. setTimeout(function(){
  7. //匿名函数无法指向自身
  8. },10)


**
误解二:它的作用域**

明确:this在任何情况下都不指向函数的词法作用域

错误实例:this不可以隐式用用函数的词法作用域

  1. function foo(){
  2. var a = 2
  3. this.bar()
  4. }
  5. function bar(){
  6. console.log(this.a)
  7. }
  8. foo()

this到底是什么

this实际上是在函数被调用时发生的绑定,它指向完全取决于函数在哪里被调用。
this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

image.png

第二章 this全面解析

调用位置

调用位置:就是函数在代码中被调用的位置(而不是声明的位置)。
分析调用栈(就是为了到达当前执行位置所调用的所有函数)

image.png

绑定规则

1.默认绑定
函数调用类型:独立函数调用。
**
在代码中,foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定。因此this指向全局对象。

  1. function foo(){
  2. console.log(this.a)
  3. }
  4. var a =2;
  5. foo()

2.隐式绑定
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象
需要考虑调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。

调用位置会使用obj上下文来引用函数,因此可以说函数被调用时obj对象“拥有”或“包含”它

  1. function foo(){
  2. console.log(this.a)
  3. }
  4. var obj = {
  5. a:2,
  6. foo:foo
  7. }
  8. obj.foo() //2

对象属性引用链只有最后一层会影响调用位置

  1. function foo(){
  2. console.log(this.a)
  3. }
  4. var obj2 = {
  5. a:42,
  6. foo:foo
  7. }
  8. var obj1 = {
  9. a:2,
  10. obj2:obj2
  11. }
  12. obj1.obj2.foo() //42

隐式丢失
被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式。

原理解析:这里他指向全局是因为引用类型的问题
虽然bar是obj.foo一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定

  1. function foo(){
  2. console.log(this.a)
  3. }
  4. var obj = {
  5. a:2,
  6. foo:foo
  7. }
  8. var bar = obj.foo //函数别名
  9. var a = "oops,global" //a是全局对象的属性
  10. bar() //oops,global
  • 另一种情况:传入回调函数时

参数传递其实就是一种隐式赋值。

  1. function foo(){
  2. console.log(this.a)
  3. }
  4. function doFoo(fn){
  5. //fn其实引用的是foo
  6. fn() //调用位置
  7. }
  8. var obj = {
  9. a:2,
  10. foo:foo
  11. }
  12. var a = "oops,global"
  13. doFoo(obj.foo) //oops,global
  • 如果把函数传入语言内置的函数而不是传入你自己声明的函数
  1. function foo(){
  2. console.log(this.a)
  3. }
  4. var obj = {
  5. a:2,
  6. foo:foo
  7. }
  8. var a = "oops,global"
  9. setTimeout(obj.foo,100)

延伸:setTimeout()函数实现伪代码

  1. function setTimeout(fn,delay){
  2. //等待 delay 毫秒
  3. fn() // 调用位置
  4. }

3.显示绑定
call(..) 和 apply(..),他们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调试函数时指定这个this,因为你可以直接指定this的绑定对象,因此我们称之为显示绑定

  1. function foo(){
  2. console.log(this.a)
  3. }
  4. var obj = {
  5. a:2
  6. }
  7. foo.call(obj) //2

1.硬绑定

我们创建了函数bar()并在它的内部手动调用了foo.call(obj),因此强制把foo的this绑定到了obj,无论之后如何调用函数bar,它总会手动在obj上调用foo,这种绑定是一种显示的强制绑定,因此我们称之为硬绑定。

  1. function foo(){
  2. console.log(this.a)
  3. }
  4. var obj = {
  5. a:2
  6. }
  7. var bar = function(){
  8. foo.call(obj)
  9. }
  10. bar() //2
  11. setTimeout(bar,100) //2
  12. //硬绑定的bar不可能再修改它的this
  13. bar.call(window) //2

应用场景一:创建一个包裹函数,传入所有的参数并返回接收到的所有值

  1. function foo(something){
  2. console.log(this.a,something)
  3. return this.a + something
  4. }
  5. var obj = {
  6. a:2
  7. }
  8. var bar = function(){
  9. return foo.apply(obj,arguments)
  10. }
  11. var b = bar(3) //2 3
  12. console.log(b) //5

应用场景二:创建一个i可以重复使用的辅助函数

  1. function foo(something){
  2. console.log(this.a,something)
  3. return this.a + something
  4. }
  5. //简单的辅助绑定函数
  6. function bind(fn,obj){
  7. return function(){
  8. return fn.apply(obj,arguments)
  9. }
  10. }
  11. var obj = {
  12. a:2
  13. }
  14. var bar = bind(foo,obj)
  15. var b = bar(3)
  16. console.log(b)

ES5中提供了内置的方法 Function.prototype.bind
bind(..)会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数

  1. function foo(something){
  2. console.log(this.a,something)
  3. return this.a + something
  4. }
  5. var obj = {
  6. a:2
  7. }
  8. var bar = foo.bind(obj)
  9. var b = bar(3)
  10. console.log(b)

2.API调用的“上下文”

上下文(context)其作用和bind(..)一样,确保你的回调函数使用指定的this

MDN参考

比如 forEach()

4.new绑定

  1. function foo(a){
  2. this.a = a
  3. }
  4. var bar = new foo(2)
  5. console.log(bar.a) //2

**
关于js中函数和对象的误解

在js中,构造函数只是一些使用new操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。
实际上它们甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已

举例:
image.png

所有的函数都可以用new来调用,这种函数调用被称为构造函数调用。
实际上并不存在所有的“构造函数”,只有对于函数的“构造调用”

使用new来调用函数:
1.创建(或者说构造)一个全新的对象
2.这个新对象会被执行[[原型]]连接
3.这个新对象会绑定到函数调用的this
4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

优先级
暂时没有理解全部 先不写

判断this

image.png

绑定例外

1.被忽略的this

如果你把null或者undefined作为this的绑定对象传入call apply 或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则、

  1. function foo(){
  2. console.log(this.a)
  3. }
  4. var a = 2;
  5. foo.call(null) //2

2.间接引用

在赋值时发生

  1. function foo() {
  2. console.log(this.a)
  3. }
  4. var a = 2;
  5. var o ={
  6. a:3,
  7. foo:foo
  8. };
  9. var p = {
  10. a:4
  11. };
  12. o.foo(); //3 这里必须写
  13. (p.foo=o.foo)() //2

赋值表达式p.foo=o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo(),所以这里会应用默认绑定。

注意:对于默认绑定来说,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式,如果函数体处于严格模式,this会被绑定到undefined,否则this会被绑定到全局对象。

3.软绑定

硬绑定这种方式可以把this强制绑定到指定的对象(除了使用new时)防止函数调用应该默认绑定规则,但是会降低函数的灵活性,使用硬绑定之后就无法使用隐式绑定或显示绑定来修改this

4.this词法

箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this

foo()内部创建的箭头函数会捕获调用时foo()的this,由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改(new也不行)

  1. function foo(){
  2. //返回一个箭头函数
  3. return (a)=>{
  4. //this继承来自 foo()
  5. console.log(this.a)
  6. }
  7. }
  8. var obj1 = {
  9. a:2
  10. }
  11. var obj2 = {
  12. a:3
  13. }
  14. var bar = foo.call(obj1)
  15. bar.call(obj2) //2

箭头函数最长用于回调函数中,例如事件处理器或定时器

  1. function foo(){
  2. setTimeout(()=>{
  3. //这里的this在词法上继承自foo()
  4. console.log(this.a)
  5. },100)
  6. }
  7. var obj = {
  8. a:2
  9. }
  10. foo.call(obj) //2
  1. function foo(){
  2. var self = this
  3. setTimeout(()=>{
  4. console.log(self.a)
  5. },100)
  6. }
  7. var obj = {
  8. a:2
  9. }
  10. foo.call(obj) //2

疑问解答:this使用 var self = this 的原因

  1. function foo(){
  2. var self = this
  3. setTimeout(function(){
  4. console.log(self.a)
  5. },100)
  6. }
  7. var obj = {
  8. a:2
  9. }
  10. foo.call(obj) //2

首先看一个例子
在一个对象里面定义了一个普通函数(不是该对象的属性函数),为了能够在该普通函数里面访问到对象的属性,可以先把对象的this赋值给一个变量self,然后在该普通函数里通过self来获取到对象的属性。

这里理解: 函数也是一个对象,写在里面的b函数不是这个对象的属性函数,所以他需要访问a函数里面的作用域,这时候就需要将a的作用域保存然后给他,因为此时的this指向是不一样的。
写这个var self = this 就是为了在其他函数中访问到对象属性,

  1. function a(){
  2. var self = this;
  3. this.name = "hello"
  4. function b(){ //b只是一个普通函数,不是a的属性方法
  5. console.log(self.name)
  6. }
  7. b()
  8. }
  9. new a() //hello

这里模拟一下 setTimeout 实现的伪代码

  1. function setTimeout(fn,delay){
  2. //等待 delay 毫秒
  3. fn() // 调用位置
  4. }


所以上面的代码就可以看成是对象中含有一个普通函数,不是foo的一个属性方法。

第三章 对象

语法

两种形式定义:声明和构造

  1. 推荐:文字语法
  2. var myObj = {
  3. key:value
  4. }
  5. 构造形式
  6. var myObj = new Object()
  7. myObj.key = value

类型

简单基本类型 : string number boolean null undefined object
复杂基本类型:函数 数组

注明:null本身是基本类型。原理:不同的对象在底层表示为二进制,在js中二进制前三位都为0的话会被判断为object类型,null的二进制表示是全0,自然前三位也是0,所以执行typeof时会返回”object”

内置对象
String Number Boolean Object Function Array Date RegExp Error

这些内置函数可以当作构造函数(由new产生的函数调用)来使用,从而可以构造一个对应子类型的新对象。

instanceof 用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上

  1. var strPrimitive = "i am a string"
  2. typeof strPrimitive //"string"
  3. strPrimitive instanceof String //false
  4. var strObject = new String("i am a string")
  5. typeof strObject //"object"
  6. strObject instanceof String //true

下面的两种方法我们都可以直接在字符串字面量上访问属性或方法,之所以可以这样做,是因为引擎自动把字面量转换成String对象,所以可以访问属性和方法

  1. var strPrimitive = "i am a string"
  2. console.log(strPrimitive.length) //13
  3. console.log(strPrimitive.charAt(3)) //m

理解:直接在字符串上面访问属性或者方法,其实是经过了一层转换,转换为其对应的构造形式。其他类似数值,布尔字面量也是如此。

对于Object Array Function RegExp 来说,无论使用文字形式还是构造形式,它们都是对象,不是字面量。

内容

对象属性的两种访问方式

  1. .a 属性访问
  2. [a] 键访问

区别所在:

  • . 只可以访问规范的标识符
  • [] 任意字符串

访问对象属性的那个值(属性名)一定是字符串。哪怕不是也会进行转换。

可计算属性名
ES6增加了可计算属性名,可以在文字形式中使用 [] 包裹一个表达式来当作属性名

  1. var prefix = "foo"
  2. var myObject = {
  3. [prefix + "bar"]:"hello",
  4. [prefix + "baz"]:"world"
  5. }
  6. myObject["foobar"] //hello

属性与方法

对象中的函数究竟是属性访问还是方法访问?

有些函数具有this引用,有时候这些this确实会指向调用位置的对象引用,但是这种用法从本质上并没有把一个函数变成一个方法,因为this始在运行时根据调用位置动态绑定的,所以函数和对象的关系只能说是间接关系。

即便在对象的文字形式中声明一个函数表达式,这个函数也不会属于这个对象,它们只是对于相同函数对象的多个引用。

  1. var myObject = {
  2. foo:function(){
  3. console.log("foo")
  4. }
  5. }
  6. var someFoo = myObject.foo
  7. someFoo; //function foo(){...}
  8. myObject.foo; //function foo(){...}

数组
数组期望的是数值下标,也就是说值存储的位置,通常被称为索引。

数组也是一个对象。所以就可以给数组添加属性。

注:虽然添加了命名属性,但是数组的length并没有发生变化

  1. var myArray = ["foo",42,"bar"]
  2. myArray.baz = "baz"
  3. myArray.length //3
  4. myArray.baz //baz

向一个数组添加一个属性,但是属性名“看起来”像一个数字,那么它会变成一个数值下标。=>(因此会修改数组的内容而不是添加一个属性)

  1. var myArray = ["foo",42,"bar"]
  2. myArray["3"] = "baz"
  3. myArray.length //4
  4. myArray[3] //baz

复制对象

  • 浅拷贝
    • ES6 Object.assign()

定义:Object.assign() 方法第一个参数是目标对象,之后还可以跟一个或多个源对象。
它会遍历一个或多个源对象的所有可枚举(enumerable)的自有键(owendkey)并把它们复制(使用=操作符赋值)到目标对象,最后返回目标对象。

  • 深拷贝

只适用部分情况

  1. var newObj = JSON.parse(JSON.stringify(someObj))

属性描述符
**

  1. var myObject = {
  2. a:2
  3. }
  4. Object.getOwnPropertyDescriptor(myObject,"a")
  5. //configurable: true
  6. //enumerable: true
  7. //value: 2
  8. //writable: true

分析:这个普通的对象属性对应的属性描述符不仅仅是一个2
writeable(可写) enumerable(可枚举) configurable(可配置)

在创建普通属性时属性描述符会使用默认值,我们也可以使用 Object.defineProperty()来添加一个新属性或者修改一个已有属性(如果他是configurable)并对特性进行设置。

  1. var myObject = {}
  2. Object.defineProperty(myObject,"a",{
  3. value:2,
  4. writable:true,
  5. configurable:true,
  6. enumerable:true
  7. })
  8. myObject.a //2
  9. 如果单单只设置一个value,其他的值默认会是false

1.writable
writable 决定是否可以修改属性的值

  1. var myObject = {}
  2. Object.defineProperty(myObject,"a",{
  3. value:2,
  4. writable:false,
  5. configurable:true,
  6. enumerable:true
  7. })
  8. myObject.a = 3
  9. myObject.a //2

image.png

2.Configurable

只要属性是可配置的,就可以使用defineProperty() 方法来修改属性描述符
把configurable 修改成false是单向操作,无法撤销。

除了无法修改,configurable:false 还会禁止删除这个属性

注意:delete方法:delete只用来直接删除对象的(可删除)属性,如果对象的某个属性是某个对象/函数的最后一个引用者,对这个属性执行delete操作之后,这个未引用的对象/函数就可以被垃圾回收。但是他不是释放内存的工具,只是一个删除对象属性的操作而已。

3.Enumerable
这个描述符控制的是属性是否会出现在对象的属性枚举中,比如说for..in循环,如果把enumerable设置成false,这个属性就不会出现在枚举中,虽然仍然可以正常访问。相对的,设置成true就会让他出现在枚举中

不变性
希望属性或者对象是不可变的。

1.对象常量
结合writable:false 和 configurable:false 就可以创建一个真正的常量属性(不可修改)

  1. var myObject = {}
  2. Object.defineProperty(myObject,"favorite",{
  3. value:42,
  4. writable:false,
  5. configurable:false
  6. })

2.禁止拓展
如果你想禁止一个对象添加新属性并且保留已有属性,可以使用Object.preventExtensions

  1. var myObject = {
  2. a:2
  3. }
  4. Object.preventExtensions(myObject)
  5. myObject.b = 3
  6. myObject.b //undefiend

image.png


(重要)[[Get]]

  1. var myObject = {
  2. a:2
  3. }
  4. myObject.a //2

**
分析:在语言规范中,myObject.a在myObject上实际上是实现了[[Get]]操作,(有点像函数调用:[Get]
对象默认的内置[[Get]]操作首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值。
如果没有找到名称相同的属性,按照[[Get]]算法的定义会执行另外一种非常重要的行为,就是遍历可能存在的
[[Prototype]]链,也就是原型链。
如果无论如何都没有找到名称相同的属性,那么[[Get]]操作会返回值undefined

  1. var myObject = {
  2. a:2
  3. }
  4. myObject.b //2

/?不是很理解
注意:这种方法和访问变量时时不一样的,如果你引用了一个当前词法作用域中不存在的变量,并不会像对象属性一样返回undefined,而是会抛出一个ReferenceError异常

  1. var myObject = {
  2. a:undefined
  3. }
  4. myObject.a //undefined
  5. myObject.b //undefined

从返回值的角度来说,这两个引用没有区别,然而实际上底层的[[Get]]操作对myObject.b进行了更复杂的处理

由于仅根据返回值无法判断出到底变量的值为undefined还是变量不存在,所以[[Get]]操作返回了undefined.

[[Put]]

image.png


Getter 和 Setter
只能应用在单个属性上,无法应用在整个对象上,getter是一个隐藏函数,会在获取属性值时调用,setter也是一个隐藏函数,会在设置属性值时调用。
**
当你给一个属性定义getter setter或者两者都有时,这个属性会被定义为“访问描述属性”,对于访问属性来说,js会忽略它们的value和writable特性,取而代之的是关心set和get特性

  1. var myObject = {
  2. get a() {
  3. return 2
  4. }
  5. }
  6. Object.defineProperty(
  7. myObject, "b", {
  8. get: function () {
  9. return this.a * 2
  10. },
  11. //确保b会出现在对象的属性列表中
  12. enumerable: true
  13. }
  14. )
  15. myObject.a //2
  16. myObject.b //4

上述代码:不管是对象语法中的get a(){},还是defineProperty()中的显示定义,二者都会在对象中创建一个不包含值的属性,对于这个属性的访问会自动调用一个隐藏函数,它的返回值会被当做属性访问的返回值。


  1. var myObject ={
  2. get a(){
  3. return 2
  4. }
  5. }
  6. myObject.a = 3
  7. myObject.a //2

由于我们只定义了a的getter,所以对a的值进行设置时set操作会忽略赋值操作,不会抛出错误。
即便有合理的setter,由于我们定义的getter只会返回2,所以set操作是没有意义的。
理解:这儿的意思是返回的是一个固定值。所以你设置了也没用,下面的return的是一个变量

为了让属性更合理,还应当定义setter,setter会覆盖单个属性默认的[Put]操作。

  1. var myObject = {
  2. get a(){
  3. return this._a_
  4. },
  5. set a(val){
  6. this._a_ = val*2
  7. }
  8. }
  9. myObject.a = 2
  10. myObject.a //4

存在性

  1. var myObject = {
  2. a:2
  3. }
  4. // ("a" in myObject) //true
  5. // ("b" in myObject) //false
  6. myObject.hasOwnProperty("a") //true
  7. myObject.hasOwnProperty("b") //false



image.png

Object.prototype.hasOwnProperty.call(myObject,”a”)

借用基础的 hasOwnProperty(…)方法并把它显示绑定在myObject上

注意:
实际上in 检查的是某个属性名是否存在,对于数组来说这个区别十分重要。

  1. console.log(4 in [2,4,6]) //false

1.枚举

  1. var myObject = {}
  2. Object.defineProperty(
  3. myObject,
  4. "a",
  5. //让a像普通属性一样可以枚举
  6. {enumerable:true,value:2}
  7. )
  8. Object.defineProperty(
  9. myObject,
  10. "b",
  11. //让b不可枚举
  12. {enumerable:false,value:3}
  13. )
  14. console.log(myObject.b) //3
  15. console.log("b" in myObject) //true
  16. myObject.hasOwnProperty("b") //true
  17. for(var k in myObject){
  18. console.log(k,myObject[k])
  19. }
  20. // "a" 2


“可枚举”就相当于”可以出现在对象属性的遍历中”
提示:最好在对象上应用for…in 循环。

另一种方法
image.png

遍历

for…in 循环可以用来遍历对象的可枚举属性列表(包括[[Prototype]]链)

for…in 遍历对象是无法直接获取属性值的,因为他实际上遍历的是对象中的所有可枚举属性,你需要手动获取属性值。

for…of 可以直接遍历值而不是数组下标(或者对象属性),如果对象本身定义了迭代器的话也可以遍历对象。

for…of 循环首先会向被访问对象请求一个迭代对象,然后通过调用迭代器对象的next()方法来遍历所有值。

image.png

第四章:混合对象“类”

类理论

类、继承描述了一种代码的组织结构。

类继承和实例化 。比如说汽车工厂就是一个类,可以生产汽车,所以在定义car时,只要声明它继承了汽车这个类。
而实例就是生产出来的汽车,每台汽车都有自己的编号,这是不一样的。

类理论强烈建议父类和子类使用相同的方法名来表示特定的行为,从而让子类重写父类。

“类”设计模式

javascript中的类

js中有一些近似类的语法元素(new,instanceof)不过在后来的ES6中新增了一些元素,比如class关键字、
但这不是类

类的机制

类的继承

第五章:原型

prototype

prototype 引用有什么用?
当试图引用对象的属性时会触发[[Get]]操作,比如myObject.a。对于默认的[[Get]]操作来说,

  • 第一步检查对象本身是否有这个属性,如果有的话就使用它。
  • 如果a不在myObject中,就需要使用对象的[[prototype]]链
  1. var anotherObject = {a:2}
  2. //创建一个关联到 anotherObject 的对象
  3. var myObject = Object.create(anotherObject)
  4. //它会创建一个对象并把这个对象的[[prototype]]关联到指定的对象

Object.prototype

所有普通的[[prototype]]链最终都会指向内置的Object.prototype
比如 .toString() .valueOf()

  1. Object.prototype.valueOf() valueOf() 方法返回指定对象的原始值。
  2. var arr = [1,2,3]
  3. arr.valueOf() === arr //true 注意返回的是原始值

属性的设置和屏蔽

  1. myObject.foo = 'bar'

1.对象中含有名为foo的属性:只会修改已有的属性值
2.不是直接存在于对象中,[[prototype]]链会被遍历,类似[[Get]]操作,如果原型链上找不到foo,就会被直接添加到myObject上。
3.如果属性名既存在myObject中也出现在myObject的[[prototype]]链上层,那么就会发生屏蔽,屏蔽的是原型链的属性。

如果foo不直接存在于myObject中而是存在于原型链上层时,会出现的三种情况:
image.png

“类”

javascript 和面向类的语言不同,它并没有类来作为对象的抽象模式,javascript中只有对象。

“类函数”

“类似类”:所有的函数默认都会拥有一个名为prototype的公有并且不可枚举的属性,它会指向另一个对象

  1. function Foo(){
  2. //...
  3. }
  4. Foo.prototype; //{}

这个对象通常被称为Foo的原型,因为我们通过名为Foo.prototype的属性引用来访问它

解释:这个对象是在调用new Foo()时创建的,最后会被关联到这个“Foo.prototype”对象上

  1. function Foo(){
  2. }
  3. var a = new Foo()
  4. Object.getPrototypeOf(a) === Foo.prototype; //true