JS高级程序读书笔记(3)

属性
  • 数据描述符:

    • configurable:表示对象的属性是否可以被删除,以及除writable特性外的其他特性是否可以被修改。
    • enumerable:为true时,才能出现在对象的枚举属性中,才能在for...inObject.keys()被枚举
    • writable:能否改写
    • value:属性的数据值

要修改默认属性的特性,需要用Object.defineProperty(obj, prop, descriptor)

1
2
3
4
5
var person = {}
Object.defineProperty(person, "name",{
writable:true,
value:Jack
})
  • 定义多个属性Object.defineProperties(obj,{prop1:descriptor1,prop2:descriptor2...})

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    var book = {}
    Object.defineProperties(book, {
    _year:{
    writable:true,
    value:2004
    },
    edition:{
    writable:true,
    value:1
    },
    year:{
    get:function(){
    return this._year
    },
    set:function(newValue){
    if(newValue > 2004){
    this._year = newValue
    this.edition = newValue - 2004
    }
    }
    }
    })
  • 存取描述符:get & set

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var book = {
    _year:2004, //下划线默认用于只能通过对象方法访问的属性
    edition:1
    }
    Object.defineProperty(book,"year",{ //这里的year属性和_year不是同一个
    get(){
    return this._year
    },
    set(newValue){
    if(newValue >2004){
    this._year = newValue
    this.edition = newValue - 2004
    }
    }
    })
    book.year = 2018
    book.edition //14
  • 读取属性Object.getOwnPropertyDescriptor(obj, prop)

    1
    2
    3
    var descriptor = Object.getOwnPropertyDescriptor(book,"_year")
    console.log(descriptor.value)
    console.log(descriptor.writable)
工厂模式(一个函数返回一个对象)

解决多个相似对象的问题,但是没有解决对象的识别问题

构造函数

构造函数也是函数,只要通过new操作符调用的,就是构造函数

用来创建特定类型的对象,与工厂模式的区别是

  • 没有显示地创建对象

  • 直接把属性和方法赋给this对象

  • 没有return语句

  • 创建新实例必须使用new操作符,新的实例person1和person2都有constructor(构造函数)属性,是一个指针,指向Person(prototype属性所在的函数)。构造函数的优势是可以将它的实例标识为一种特定的类型,可以用instanceof 来检测,工厂模式只能检测出来Object。

    • 创建一个新对象
    • 将构造函数的作用域赋给这个新对象(this指向这个新对象)
    • 执行代码,给新对象添加属性
    • 返回新对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function Person(name,age){
    this.name = name
    this.age = age
    this.sayName = function(){console.log(this.name)}
    }
    var person1 = new Person("jacK",18)
    var person2 = new Person("tom",20)
    -------------
    person1 instanceof Object //true
    person2 instanceof Person //true
    ------------------原型模式prototype
    # 以上定义在this上的方法,每创建一个新的实例,就要创建一个相同任务的方法,完全没有必要,可通过原型来解决。每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象。
    Person.prototype.sayName = function(){console.log(this.name)}
    var person1 = new Person("jacK",18)
    var person2 = new Person("tom",20)
    person1.sayName() //jacK 此时他们引用的是同一个函数
    person2.sayName() //tom
  • obj.hasOwnProperty(prop) :用来检测属性是否存在于实例中,只有属性存在于实例中才返回true,来自原型会返回false。

    in 操作符单独使用prop in obj:无论在实例中,还是在原型中,只要能访问到该属性,就返回true

    for-in 循环,返回包括原型和实例的可枚举属性(enumerable:true),如果要包含不可枚举属性,可以用Object.getOwnPropertyNames()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function Person(){
    }
    Person.prototype.name = "Nicholas";
    Person.prototype.age = 29;
    Person.prototype.job = "Software Engineer";
    Person.prototype.sayName = function(){ alert(this.name) };
    var person1 = new Person();
    var person2 = new Person();
    alert(person1.hasOwnProperty("name")); //false
    alert("name" in person1) //true
    -------------
    person1.name = "Greg";
    alert(person1.name); //"Greg"——来自实例
    alert(person1.hasOwnProperty("name")); //true
    alert(person2.name); //"Nicholas"——来自原型
    alert(person2.hasOwnProperty("name")); //false
    delete person1.name;
    alert(person1.name); //"Nicholas"——来自原型
    alert(person1.hasOwnProperty("name")); //false
  • 理解原型对象

    创建一个新函数,就会为该函数创建一个prototype属性,指向函数的原型对象。所有的原型对象都会自动获得一个constructor属性(构造函数),这个属性是一个指向prototype属性所在函数的指针。

    1
    2
    3
    4
    5
    function foo(a){
    console.log(a)
    }
    //console.dir(foo) 会有prototype属性指向函数原型对象,而原型对象有个constructor属性指向foo函数
    foo.prototype.constructor === foo //true

  • 两种原型语法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ##第一种
    function Person(){}
    Person.prototype.name = "Jack";
    Person.prototype.age = 29;
    Person.prototype.sayName = function(){ alert(this.name) };
    -----------
    ##第二种简写,重写了默认的prototype属性,此时constructor属性指向了Object,不再指向Person,所以需要添加constructor属性,来指定到Person
    function Person(){}
    Person.prototype={
    name = "Jack";
    age = 29;
    sayName = function(){ alert(this.name) };
    }
  • 构造函数、原型和实例的关系

    1
    每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 实现一个原型链,让A的原型对象 = B的实例,那么A重写了原型对象,继承了B。
    function A(){
    this.name = "heihei"
    }
    A.prototype.getName = function(){
    return this.name
    }
    function B(){
    this.age = 18
    }
    ----------继承了A
    B.prototype = new A()
    B.prototype.getAge = function(){
    return this.age
    }
    var person = new B()
    person.getName() //"heihei"
    这里的person实例的constructor指向的是A

#####作用域链和原型继承

在作用域链中查找变量的过程和原型继承(prototypal inheritance)有着非常相似之处。但是,非常不一样的地方在于,当你在原型链(prototype chain)中找不到一个属性的时候,并不会引发一个错误,而是会得到undefined。但是如果你试图访问一个作用域链中不存在的属性的话,你就会得到一个ReferenceError

  • 作用域链,作用域对象。作用域链本质上是一个指向作用域对象的指针列表,它只引用但不实际包含作用域对象。

    1
    js运行时,需要一些空间来存储本地变量,这些空间称作为作用域对象。作用域对象是可以有父作用域对象(parent scope object)的。当代码试图访问一个变量的时候,解释器将在当前的作用域对象中查找这个属性。如果这个属性不存在,那么解释器就会在父作用域对象中查找这个属性。就这样,一直向父作用域对象查找,直到找到该属性或者再也没有父作用域对象。我们将这个查找变量的过程中所经过的作用域对象称作作用域链