学习笔记(JavaScript面向对象精要)

原始类型和引用类型

  • 原始类型(5 种):boolean, number, string, null, undefined
    • 鉴别原始类型:typeof
    • 原始方法

  • 引用类型(对象)
    • 创建对象
      (1)new 操作符和构造函数
      var object = new object();
    • 随时添加和删除属性

  • 内建类型实例化:Array, Date, Error, Function, Object, RegExp
    通过 new 来实例化每一个内建类型引用
    • 字面形式(不需要使用 new 操作符和构造函数显式创建对象的情况下生成引用值)
    • 对象和数组字面形式
      // 对象
      var book = {
      name: “JS”,
      year: 2014
      }
      //等价于
      var book = new Object();
      book.name = “JS”;
      book.year = 2014;
      // 数组
      var colors = [ “red”,”blue”,”green”];
      //等价于
      var colors = new Array(“red”,”blue”,”green”);
    • 函数字面形式
      function reflect(value){
      return value;
      }
      // 等价于
      var reflect = new Function(“value”,”return value;”)
    • 正则表达式字面形式
      var numbers = /\d+/g;
      // 等价于
      var numbers = new RegExp(“\d+”,”g”);

  • 鉴别引用类型(typeof , instanceof)
  • 原始封装类型(String, Number, Boolean)

函数

  • 声明&表达式
    // 函数声明
    function add(num1,num2){
    return num1 + num2;
    }
    // 函数表达式
    var add = function(num1,num2){
    return num1 + num2;
    };
    函数声明会被提升至上下文的顶部
  • 函数就是值
    可以像使用对象一样使用函数,也可以将她们赋给变量,在对象中添加它们,将它们当成参数传递给别的函数,或从别的函数中返回。
    var numbers = [ 1,4,5,5,2,6,7,2 ];
    numbers.sort(function(first,second){
    return first - second;
    })
  • 参数
    可以给函数传递任意数量的参数却不造成错误。函数参数实际上被保存在一个被称为 arguments 的类似数组的对象中。arguments 可以自由增长来包含任意个数的值。arguments.length 可以获取参数个数。
  • 重载
    JS 函数没有签名,因此不存在重载
    function sayMessage(message){
    console.log(message);
    }
    function sayMessage(){
    console.log(“default message”);
    }
    sayMessage(“Hello”);
    // 输出”default message”
  • 对象方法
    可以给对象添加或删除属性,如果属性的值是函数,则该属性被称为方法。
    var person = {
    name : “tang”,
    sayName : function(){
    console.log(person.name);
    }
    }
    person.sayName();
  • this 对象
    javascript 所有的函数作用域内都有一个 this 对象代表调用该函数的对象。在全局作用于中,this 代表全局对象(浏览器里的 window)。当一个函数作为对象的方法被调用时,默认 this 的值等于那个对象。
    var person = {
    name : “tang”,
    sayName : function(){
    console.log(this.name);
    }
    };
    person.sayName();
  • 改变 this
    1. call()方法
      call()的第一个参数指定了函数执行时的 this 的值,其后的所有参数都是需要被传入函数的参数。
      function sayNameForAll(label){
      console.log(label + “:” + this.name);
      }
      var person1 = {
      name : “Nicholas”
      };
      var person2 = {
      name : “Greg”
      };
      var name = “Tom”;
      sayNameForAll.call(this,”global”);
      sayNameForAll.call(person1,”person1”);
      sayNameForAll.call(person2,”person2”);
      //此时的函数名被作为对象访问,所以后面没有小括号
    2. apply()方法
      apply()的工作方式和 call()完全一样,但它只接受两个参数:this 的值和一个数组或类似数组的对象,内含需要被传入函数的参数。不需要像使用 call()那样一个个指定参数,而是可以轻松的传递整个数组给 apply()。
      function sayNameForAll(label){
      console.log(label + “:” + this.name);
      }
      var person1 = {
      name : “Nicholas”
      };
      var person2 = {
      name : “Greg”
      };
      var name = “Tom”;
      sayNameForAll.call(this,[“global”]);
      sayNameForAll.call(person1,[“person1”]);
      sayNameForAll.call(person2,[“person2”]);
    3. bind()方法
      bind()的第一个参数是要传给新函数的 this 的值。其他所有参数代表需要被永久设置在新函数中的命名参数。
      function sayNameForAll(label){
      console.log(label + “:” + this.name);
      }
      var person1 = {
      name : “Nicholas”
      };
      var person2 = {
      name : “Greg”
      };
      var sayNameForPerson1 = sayNameForAll.bind(person1);
      sayNameForPerson1(“person1”);
      //输出 “person1:Nicholas”
      var sayNameForPerson2 = sayNameForAll.bind(person2,”person2”);
      sayNameForPerson2();
      //输出 “person2:Greg”
      person2.sayName = sayNameForPerson1;
      person2.sayName(“person2”);
      //输出 “person2:Nicholas”

理解对象

  • 定义属性
    两种方式:使用 Object 构造函数或使用对象的字面形式
    var person1 = {
    name : “Tom”
    };
    var person2 = new Object();
    person2.name = “Tom”;
    person1.age = 10;
    person2.age = 20;
    person1.name = “Nicholas”;
    person2.name = “Greg”;
  • 属性探测
    in 操作符在给定对象中查找一个给定名称的属性
    console.log(“name” in person1);
    // true
    console.log(“title” in person1);
    // false
    in 操作符会检查自有属性和原型属性。如果希望仅当一个属性是自由属性时才检查其是否存在,考虑所有对象都拥有的 hasOwnProperty()方法。该方法在给定的属性存在且为自由属性时返回 true。
    var person1 = {
    name : “Tom”;
    sayName : function(){
    console.log(this.name);
    }
    };
    console.log(“toString” in person1); // true
    console.log(person1.hasOwnProperty(“toString”)); // false
  • 删除属性(delete person1.name)
  • 属性枚举
    var property;
    for(property in object){ //遍历所有的可枚举属性
    console.log(“Name” + property);
    console.log(“Value” + object[property])
    }
    // Object.keys()可以获取可枚举属性的名字的数组
    可以用 propertyIsEnumerable()方法检查一个属性是否为可枚举的。
  • 属性类型(数据属性和访问器属性)
    数据属性包含一个值,例如之前的 name 属性。
    访问器属性不包含值而是定义了一个当属性被读取时调用的函数(getter)和一个当属性被写入时调用的函数(setter)。
    var person = {
    _name : “Tom”,
    getName(){
    console.log(“Reading name”);
    return this._name;
    }
    setName(value){
    console.log(“Setting name to “+ value);
    this._name = value;
    }
    };
    console.log(person.name);
    person.name = “Greg”;
    console.log(person.name);
  • 属性特征
    • 通用特征
      [[Enumerable]] 决定是否可以遍历该属性
      [[Configurable]] 决定该属性是否可以配置
      自己声明的所有属性默认都是可枚举、可配置的
      可以通过 Object.defineProperty()来改变属性特征,接受三个参数:拥有该属性的对象、属性名和包含需要设置的特征的属性描述对象。
      var person = {
      name : “Tom”
      };
      Object.defineProperty(person, “name”, {
      enumerable: false
      })
      console.log(“name” in person); // true
      var properties = Object.keys(person1);
      console.log(properties.length); // 0
      Object.defineProperty(person, “name”, {
      configurable: false
      });
      delete person.name;
      console.log(“name” in person); // true
      Object.defineProperty(person, “name”, { // error!
      configurable: true
      });
    • 数据属性特征
      [[Value]] 包含属性的值
      [[Writable]] 指定该属性是否可以写入(默认可写)
    • 访问器属性特征
      [[Get]]
      [[Set]]
      内含 getter 和 setter 函数
      var person = {
      _name : “Tom”,
      };
      Object.defineProperty(person1,”name”,{
      get: function(){
      return this._name;
      },
      set: function(value){
      this._name = value;
      },
      enumerable: true,
      configurable: true
      })
      // 只定义 getter 表示该属性的值只能被读取,不能被改变
    • 定义多重属性( Object.defineProperties() )
    • 获取属性特征(Object.getOwnPropertyDescriptor() )
      var person = {
      name : “Tom”
      };
      var descriptor = Object.getOwnPropertyDescriptor(person,”name”);
      console.log(descriptor.value);
      console.log(descriptor.writable);
      console.log(descriptor.enumerable);
      console.log(descriptor.configurable);
  • 禁止修改对象
    对象的[[Extensible]]指明该对象本身是否可以被修改
    • 禁止拓展(Object.preventExtensions() )
      不能给该对象添加新的属性
    • 对象封印(Object.seal() )
      一个被封印的对象是不可拓展的且其所有属性都不可配置
    • 对象冻结(Object.freeze() )
      不能在该对象上添加或删除属性,不能改变属性类型,也不能写入任何数据属性,且无法解冻。
      被冻结对象是一个数据类型都为只读的被封印对象

构造函数和原型对象

  • 构造函数
    好处:所有用同一个构造函数创建的对象都具有同样的属性和方法。
    function Person(){ //构造函数首字母要大写
    }
    var person1 = new Person();
    console.log(person1 instanceof Person); // true
    console.log(person1.constructor === Person) // true
    使用构造函数的目的是为了轻松创建许多拥有相同属性和方法的对象。
    function Person(name){
    this.name = name;
    this.sayName = function(){
    console.log(this.name);
    };
    }
    当调用构造函数时,new 会自动创建 this 对象
  • 原型对象
    可以把原型对象看作是对象的基类。几乎所有的函数都有一个 propotype 的属性,该属性是一个原型对象用来创建新的对象实例,所有创建的对象实例共享该原型对象,且这些对象实例可以访问原型对象的属性。
    var book = {
    title : “JS”
    };
    console.log(“title” in book); //true
    console.log(book.hasOwnProperty(“title”)); //true
    console.log(“hasOwnProperty” in book); //true
    console.log(book.hasOwnProperty(“hasOwnProperty”)); //false
    console.log(Object.prototype.hasOwnProperty(“hasOwnProperty”)); //true
    如果某个属性 in 一个对象,但 hasOwnProperty()返回 false,那么这个属性就是一个原型属性

    • [[prototype]]属性
      一个对象实例通过内部属性[[prototype]]跟踪其原型对象,当用 new 创建一个新的对象时,构造函数的原型对象会被赋给该对象的[[prototype]]属性。
      // 可以调用 Object.getPrototypeOf()方法来读取[[prototype]]属性的值
      var object = {};
      var prototype = Object.getPrototypeOf(object);
      console.log(prototype === Object.prototype); //true
      // 也可以用 isPrototypeOf()方法来检查某个对象是否是另一个对象的原型对象
      var object = {};
      console.log(Object.prototype.isPrototypeOf(object)) //true
      当读取一个对象的属性时,javascript 引擎首先在该对象的自有属性中查找属性名字。如果自有属性中不包含该名字,则 javascript 会搜索[[prototype]]中的对象。
      无法给一个对象的原型属性复制
    • 在构造函数中使用原型对象
      原型对象的共享机制使得它们成为一次性为所有对象定义方法的理想手段。将方法放在原型对象中并用 this 访问当前实例是更有效的做法。
      function Person(name){
      this.name = name;
      }
      Person.prototype.sayName = function(){
      console.log(this.name);
      };
      var person1 = new Person(“Nicholas”);
      var person2 = new Person(“Greg”);
      在原型对象上存储引用值时需要注意:这些引用值会被多个实例共享
      一种更简洁的方式:直接用一个对象字面形式替换原型对象
      function Person(name){
      this.name = name;
      }
      Person.prototype = {
      sayName : function(){
      console.log(this.name);
      },
      toString : function(){
      return “[Person “+ this.name +”]”
      }
      };
      var person1 = new Person(“Nicholas”);
      这种做法的副作用:会使原型对象改变了构造函数的属性,上例中的 person1 现在指向 Object 而不是 Person。这是因为原型对象具有一个 constructor 属性。当一个函数被创建时,它的 prototype 属性也被创建,且该原型对象的 constructor 属性指向该函数。当使用对象字面形式改写原型对象 Person.prototype 时,其 constructor 属性将被自动置为泛用对象 Object。
      解决方案:在改写原型对象时手动重置其 constructor 属性
      function Person(name){
      this.name = name;
      }
      Person.prototype = {
      constructor : Person,

      sayName : function(){
      console.log(this.name);
      },
      toString : function(){
      return “[Person “+ this.name +”]”
      }
      };
      构造函数、原型对象和对象实例之间的关系:
      对象实例和构造函数之间没有直接联系。
      对象实例和原型对象以及原型对象和构造函数之间都有直接联系。

    • 改变原型对象
      [[prototype]]属性只是包含了一个指向原型对象的指针,任何对原型对象的改变都立即反映到所有引用它的对象实例上。

    • 内建对象的原型对象
      原型对象也允许你改变 JavaScript 引擎的标准内建对象
      Array.prototype.sum = function(){
      return this.reduce(function(previous,current){
      return previous + current;
      });
      };
      var numbers = [ 1,2,4,6,3 ];
      var result = numbers.sum();
      第四章总结:
      构造函数就是用 new 操作符调用的普通函数。
      每个函数都具有 prototype 属性,它定义了该构造函数创建的所有对象共享的属性。
      原型对象被保存在对象实例内部的[[prototype]]属性中,这个属性时一个引用而不是一个副本。
      内建对象也有可以被修改的原型对象(不建议在生产环境中这么做)

继承

  • 原型对象链和 Object.prototype
    对象实例继承了原型对象的属性。因为原型对象也是一个对象,它也有自己的原型对象并继承其属性。这就是原型对象链:对象继承其原型对象,而原型对象继承它的原型对象……
    所有的对象都自动继承自 Object.prototype
    var book = {
    title : “JS”
    };
    var prototype = Object.getPrototypeOf(book);
    console.log(prototype === Object.prototype) //true
    • 继承自 Object.prototype 的方法
      如下:
      hasOwnProperty() 检查是否存在一个给定名字的自有属性
      propertyIsEnumerable() 检查一个自有属性是否可枚举
      isPrototypeOf() 检查一个对象是否是另一个对象的原型对象
      valueOf() 返回一个对象的值表达
      toString() 返回一个对象的字符串表达
    • 修改 Object.prototype
      最好不要修改。
  • 对象继承
    对象继承是最简单的继承类型。只需要指定哪个对象是新对象的[[prototype]]。对象字面形式会隐式指定 Object.prototype 为其[[prototype]],也可以用 Object.create()方法显示指定。
    Object.create()方法接受两个参数。第一个参数是需要被设置为新对象的[[prototype]]的对象。第二个可选参数是一个属性描述对象。
    var book = {
    title : “JS”
    };
    //等同于
    var book = Object.create(Object.prototype, {
    title: {
    configurable: true,
    enumerable: true,
    value: “JS”,
    writable: true
    }
    });
    //继承自其它对象
    var person1 = {
    name: “Nicholas”,
    sayName: function(){
    console.log(this.name);
    }
    }
    var person2 = Object.create(person1,{
    name: {
    configurable: true,
    enumerable: true,
    value: “Greg”,
    writable: true
    }
    });
    person1.sayName(); //输出 Nicholas
    person2.sayName(); //输出 Greg
  • 构造函数继承
    function Rectangle(length, width){
    this.length = length;
    this.width = width;
    }

    Rectangle.prototype.getArea = function(){
    return this.length this.width;
    }
    Rectangle.prototype.toString = function(){
    return “[Rectangle ]” + this.length + “
    “ +this.width+ “]”;
    }

    // inherits from Rectangle
    function Square(size){
    this.length = size;
    this.width = size;
    }
    Square.prototype = new Rectangle();
    Square.prototype.constuctor = Square;
    Square.prototype.toString = function(){
    return “[Square ]” + this.length + “*“ +this.width+ “]”;
    }

    var rect = new Rectangle(5,10);
    var square = new Square(6);
    这段代码中有两个构造函数:Rectangle 和 Square。Square 构造函数的 prototype 属性被改写为 Rectangle 的一个对象实例。此时不需要给 Rectangle 的调用提供参数,因为它们不需要被使用。
    Square.prototype 被改写后,其 constructor 属性会被重置为 Square。

    事实上,唯一相关的部分是 Square.prototype 需要指向 Rectangle.prototype 使得继承得以实现。
    可以用 Objecty.prototype 简化例子
    function Square(size){
    this.length = size;
    this.width = size;
    }
    Square.propotype = Object.create(Rectangle.prototype,{
    constructor:{
    configurable: true,
    enumerable: true,
    value: Square,
    writable: true
    }
    });
    Square.prototype.toString = function(){
    return “[Square ]” + this.length + “*“ +this.width+ “]”;
    }

  • 构造函数窃取
    Javascript 中的继承是通过原型对象链来实现的,因此不需要调用对象的父类的构造函数。
    如果需要在子类构造函数中调用父类构造函数,可以利用 call() apply()方法
    function Rectangle(length, width){
    this.length = length;
    this.width = width;
    }
    Rectangle.prototype.getArea = function(){
    return this.length this.width;
    }
    Rectangle.prototype.toString = function(){
    return “[Rectangle ]” + this.length + “
    “ +this.width+ “]”;
    }
    // inherits from Rectangle
    function Square(size){
    Rectangle.call(this, size, size);
    }
    Square.propotype = Object.create(Rectangle.prototype,{
    constructor:{
    configurable: true,
    enumerable: true,
    value: Square,
    writable: true
    }
    });
    Square.prototype.toString = function(){
    return “[Square ]” + this.length + “*“ +this.width+ “]”;
    }

  • 访问父类方法
    上例中,Square 类型有自己的 toString()方法隐藏了其原型对象的 toString()方法。但如果还想访问父类的 toString()方法,可以通过 call() apply()调用父类的原型对象的方法时传入一个子类的对象。
    function Rectangle(length, width){
    this.length = length;
    this.width = width;
    }
    Rectangle.prototype.getArea = function(){
    return this.length this.width;
    }
    Rectangle.prototype.toString = function(){
    return “[Rectangle ]” + this.length + “
    “ +this.width+ “]”;
    }
    // inherits from Rectangle
    function Square(size){
    Rectangle.call(this, size, size);
    }
    Square.propotype = Object.create(Rectangle.prototype,{
    constructor:{
    configurable: true,
    enumerable: true,
    value: Square,
    writable: true
    }
    });
    Square.prototype.toString = function(){
    vartext = Rectangle.prototype.toString.call(this);
    return text.replace(“Rectangle”,”Square”);
    }
    第五章总结
    JavaScript 通过原型对象链支持继承。当将一个对象的[[prototype]]设置为另一个对象时,就在这两个对象之间创建了一个原型对象链。所有的泛用对象都自动继承自 Object.prototype。如果想要创建一个继承自其他对象的对象,可以用 Object.create()指定[[prototype]]为一个新对象。

对象模式

  • 私有成员和特权成员
    • 模块模式
      基本做法是使用立调函数表达( IIFE )来返回一个对象。
      var yourObject = (function(){
      //私有成员
      return {
      //公共方法和属性
      }
      });
    • 构造函数的私有成员
      function Person(name){
      var age = 25; //私有变量
      this.name = name;
      this.getAge = function(){
      return age;
      }
      this.growOlder = function(){
      age++;
      }
      }
  • 混入

  • 作用域安全的构造函数
    function Person(name){
    this.name = name;
    }
    Person.prototype.sayName = function(){
    console.log(this.name);
    }
    var person1 = Person(“Tom”); //没有使用 new 操作符调用构造函数
    console.log(person1 instanceof Person); //false
    console.log(typeof person1); //“undefined
    console.log(name); //Tom
    在这个例子中,由于 Person 构造函数不是用 new 来调用的,导致我们创建了一个全局变量 name。
    我们可以在构造函数内部用 instanceof 来检查自己是否被 new 调用,一个作用域安全的 Person 版本如下:
    function Person(name){
    if(this instanceof Person){
    this.name = name;
    }else{
    return new Person(name);
    }
    }

0%