JS学习笔记 对象、包装类、原型、原型链、命名空间、对象枚举

对象

对象是JavaScript的一个基本数据类型,是一种复合值,它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。即属性的无序集合。

对象的创建

对象直接量/字面量

var obj = {
    name : '守敬',
    age : '18'
}
console.log(obj.name);//守敬

构造函数(重点)

系统自带构造函数
var obj = new object();
obj.name = '守敬';
console.log(obj.name);//守敬
自定义构造函数

为了和普通函数区分,构造函数采用大驼峰式命名法。

function Car(carName, color) {
    this.name = carName;
    this.color = color;    
}
var car = new Car('BMW', 'red');
console.log(car.name);//BMW
console.log(car.color);//red
  • 自定义构造函数内部原理
    1. 在函数体最前面隐式的加上this{}
    2. 执行this.xxx = xxx;
    3. 隐式的返回this(return)
      原理解释:
      注意隐士创建的this对象中有个名为__proto__的属性,其属性值为该构造函数继承的原型prototype。
function Car(carName, color) {
	//隐式创建this对象
    //var this = {
    //    color : '',
    //    name : '',
    //     health : '',
    //     run = function (){
    //                  this.health --;
    //              }
    // };
    this. color : color,
    this.name : carName,
    this.health : 100,
    this.run = function () {
        //对象方法
        this.health --;
    }
    //return this;返回this对象
}

Object.create

此方法创建一个继承该原型的实例对象
留坑待补

对象的增删改查

  • 增: 所谓增添一个对象的属性,就是直接对该属性进行赋值操作即可,这就相当于为该对象添加了一个新属性,而打印未添加的属性,浏览器不会报错,而是会打印出undefined
var obj = {};
    console.log(obj.name); //undefined (不会报错)
    obj.name = 'shoujing';
    console.log(obj.name); // shoujing
  • 删:我们通过delete操作符来删除一个对象的属性
var obj = {
       name : 'shoujing'
     }
     console.log(obj.name); //shoujing
     delete obj.name; 
     console.log(obj.name); //undefined
  • 改: 修改一个对象的属性是最简单的了,直接通过赋值操作赋予其其他的值即可
var obj = {
      name: 'shoujing'
    }
    console.log(obj.name); //shoujing
    obj.name = 'obj';
    console.log(obj.name); // obj
  • 查:查询一个对象的属性值有两种方法
var obj = {
      name: 'shoujing'
    };
    // 第一种方法
   console.log(obj['name']); //shoujing
  //  第二种方法
    console.log(obj.name); //shoujing
  //p.s.最本质的是第一种方法,因为在使用第二种方法时,后台自动将其转换为第一种字符串的形式来查询

p.s.以上的增、删、改三种操作都只是针对当前对象的属性进行操作,而不会影响到当前对象的原型的属性。而查询是先看看当前对象本身是否设置了该属性,如果当前对象未设置该属性,则再看该对象的原型中是否设置了该属性,若两者都没有,则返回undefined

包装类

  1. 五个原始值:number, string , boolean, undefined, null.其中number, string, boolean是分别拥有自己的包装类,而undefined和null是没有自己的包装类的

  2. 原始值不是对象,无法拥有自己的属性,但因为的包装类的存在,原始值就好似可以拥有自己的属性了,但其拥有的属性又有点特殊之处,如下用string来举例:

引例:

//  str是string类型的,非对象,不能拥有属性,为什么能打印出                str.length?
     var str = 'abcd';
     console.log(str.length); //4

解释:

// 因为每次执行完一条完整js语句后该类型对象的包装类就会将该语句包装,所以也就不会导致报错了
     var str = 'abcd';
    //  var str = new String('abcd');
     console.log(str.length); //4
    str.len = 4;
    consloe.log(str.len);//undefinend
    //length较为特殊,如果是自定义的len属性,则系统会进行如下过程:
    //(new string('abcd')).len = 4; delete
    //因此最后打印就是undefinde

举例

   var str = 'abcd';
   str.lenth = 2;
   console.log(str);  // ab or abcd ? answer is abcd
   console.log(str.length); // 2 or 4 ? answer is 4

原型

  • 原型的定义: 原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
  • 利用原型特点和概念,可以提取共有属性。将一类对象的共有属性提取出来,放到该类对象的原型中,从而不需要每次用new操作符时都重新定义一遍该共有属性。
    如下,定义一个Person构造函数,而属于Person多构造对象共有的属性方法,则定义到Person的原型中
Person.prototype = {
        eat : function (food) {
            console.log('I have eated ' + food);
        },
        sleep : function () {
             console.log("I am sleeping");
        }
      }
// 人的构造函数
function Person (name, age) {
        this.name = name;
        this.age = age;
 }
      var person1 = new Person('shoujing', 18);
      console.log(person1.name); //shoujing
      person1.eat('apple'); //I have eated apple
  • 如何查看原型:
    • 之前是不允许我们查看构造函数的原型的,但后来提供了一个可查看构造函数原型的接口:隐士属性__proto__(其实我们能够访问原型的属性,或者说继承原型,靠的就是__proto__属性连接着构造函数和原型,可以说没有__proto__属性的存在,就无法实现原型的继承)
    • 首先我们先说明一下__proto__这个接口是存放到哪里的看过以上对象创建过程的都应该知道在用new创建一个对象时,内部会隐式自动创建一个this的对象,进过一系列处理后再隐士将this对象返回。而__proto__就在隐士创建的this对象中,如下代码:
 // 原型
    Person.prototype = {
      say: function () {
        console.log("I am saying ");
      },
      play: function () {
        console.log("I am playing");
      }
    }
    // 构造函数
    function Person (name) {
      
      // var this = Object.create(Person.prototype);
      // p.s.在隐士创建的this对象中存在一个属性,即__proto__,该属性存储了Person.prototype

      this.name = name;

      // return this;
    }
    // 对象的创建
    var person1 = new Person('lyl');
    // 打印原型
    console.log(person1.__proto__);
- 如何查看对象的构造函数,我们通过属性constructor来查看:contructor属性位于构造函数的原型中,其中存储的是构造函数信息,所以在不知道原型的情况下,由原型继承原理,我们可以用实例对象来直接访问constructor,即获取创建该实例的构造函数。
function Person () {
           this.name = 'myName';
           this.age = 18;
       }
       var person = new Person();
       console.log(person.constructor); // function Person(){...}

call、apply

call和apply的作用是改变构造函数中this的指向,二者唯一的区别在一参数传递的方式不同,如:

function Person (name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}

function Student (name, age, sex, grade){
    Person.call(this, name, age, sex)//call传参
    Person.apply(this, [name, age, sex])//apply传参使用数组
    this.grade = grade;
}

原型链

  • 定义:顾名思义,原型链就是将一个个原型串连起来,形成一条原型继承的链子。
  • 原型链的构成
    如下代码例子, Son继承Father, Father继承Grand, 而Grand没有自定义原型,所以默认为原型链的最顶端new Object();为什么Object为最顶端,因为Object.prototype为null,为null是没有原型的。
//原型链 Son --> new Father() --> new Grand() --> new Object();
function Grand () {
}
Father.prototype = new Grand();
function Father () {
}
Son.prototype = new Father();
function Son () {
}
var son = new Son();
console.log(son);

原型链上属性的增删改查

以上述原型链为例说明:Son --> new Father() --> new Grand() --> new Object()


  • 为son实例对象添加属性,总是添加为其自己本身的属性,为对原型和原型链是没有影响的。(再具体说即对和其相同构造函数构造的实例对象无法造成影响,以下说法同此)

  • 使用delete操作符只能删除son实例对象自己本身的属性,而无法删除由原型继承而来的属性.
    • 若修改的属性为继承自原型的,且值类型为原始值,则仅仅修改的是该实例对象的属性,对原型无法造成影响。
    • 若修改的属性为继承自原型的,属性值类型为引用值,则对引用值的修改又分两种情况
      • 是直接对该修改的属性赋值 => 此情况仅仅修改的是实例对象的该属性,无法对原型造成影响
      • 对该修改的属性添加内容或去除内容,而不是对其重新赋值 => 此情况会对原型造成影响。如下例
Person.prototype = {
    has : [1, 2, 3]
}
function Person () {
    this.name = 'shoujing';
}
var person1 = new Person();
var person2 = new Person();
person1.has.push(4);
//person1 和 person2都改变了,因为person1的修改影响到了原型,进而影响到了另一个实例对象
console.log(person1.has); //[1, 2, 3, 4]
console.log(person2.has); // [1, 2, 3, 4]

  • 查询过程如下,首先看构造函数中是否有要查询的属性,若有,则直接返回,若没有,则看其原型有没有要查询的属性,若没有,则再看原型的原型上是否有要查询的属性,以此顺序在原型链上查询,若一直到原型链顶端后仍没有要查询的属性,则返回undefined

绝大多数对象最终都会继承自Object.prototype

null,和undefined是没有原型的

对象继承史

传统模式

  • 即正常的通过构造函数创建实例对象,来继承原型–>原型链
  • 缺点:继承了过多不必要的属性

借用构造函数

  • 原理是通过call/apply改变构造函数内this对象的引用
  • 不能继承借用构造函数的原型
  • 每次构造函数都要多走一个函数
  • 举例
function Person (name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}

function Student (name, age, sex, grade){
    Person.call(this, name, age, sex)//call传参
    //Person.apply(this, [name, age, sex])//apply传参使用数组
    this.grade = grade;
}
var student = new Student('张三', 18, 'male', 2107);
console.log(student);

共享原型

  • 即将其他构造函数的原型直接赋值给本构造函数的原型
  • 两个原型会相互影响,更改其中一个原型,另一个对应的原型也会被更改。
  • 举例
Person.prototype = {
    name : 'shoujing',
    age : 18,
    sex : 'male'
}
function Person () {
    this.hooby = 'sing';
}
Student.prototype = Person.prototype;
function Student() {
    this.grade = 2017;
}
Son.prototype = Person.prototype;
function Son () {
    this.grand = 'luo';
}
var person = new Person();
var student = new Student();
var son = new Son();
console.log(person.age);//18
console.log(student.age);//18
console.log(son.age);//18
//原型继承成功
Student.prototype.age = 20;//更改其中一个原型的age属性
console.log(student.age);//20
console.log(son.age);//20
//继承Person原型的Student和Son相互影响

圣杯模式

每次继承的都是新创建的F构造函数实例,相互之间不会影响。其实此处针对F形成了闭包,Target引用了F,导致F不会销毁。
正常函数形式:

function inherit (Target, Origin) {
        // 借用F这个中间量来继承,而不是直接共享原型
        var F = function (){}
        F.prototype = Origin.prototype;
        Target.prototype = new F();
        // 自定义构造函数原型时,同时要更正自定义原型的constructor,否则一般默认为Object(),次函数若不指定constructor,则constructor为Origin
        Target.prototype.constructor = Target; 
        
        Target.prototype.uber = Origin; //记录真正继承的是谁
    }

闭包形式(推荐,YUI)

var inherit = (function(){
        var F = function (){};
        return function (Target, Origin) {
            F.prototype = Origin.prototype;
            Target.prototype = new F();
            Target.prototype.constructor = Target;
            Target.prototype.uber = Origin;
        }
    })();

命名空间

我们可以利用对象(闭包)创建命名空间来管理变量,防止污染全局,适用于模块开发,举个例子:

 var workSpace = {
           person1: {
               name: 'one',
               age: 18
           },
           person2: {
               name: 'two',
               age: 20
           }
       }
    // 这样两个人虽然有同名变量,但不会相互影响,因为位于不同命名空间
     //    访问第一个人的姓名
    console.log(workSpace.person1.name); // one
    console.log(workSpace.person2.name); //two

对象枚举

  • prop in obj
var obj = {
    name : '123',
    age : 18,
    sex : 'male',
    height : 180,
    weight : 75,
}
for (var prop in obj) {
    console.log(obj[prop]);
}
  • obj.hasOwnProperty(‘prop’);
  • 该方法的作用是来判断对象obj的自身属性中是否含有属性prop,自身属性是在构造函数中生成的或者实例对象后来自己添加的,而继承属性则是从原型上继承的属性,所以该方法就是判断该属性是从原型继承来的还是自身的。返回结果为true,则为自身属性。
var obj = {
    name : '123',
    age : 18,
    sex : 'male',
    height : 180,
    weight : 75,
    __proto__ : {
        lastName : 'deng'
    }
}
for (var prop in obj) {
    if(obj.hasOwnProperty(prop)) {
        console.log(obj[prop]);
    }
}
  • object instanceof Object;
  • instanceof操作符用来判断object实例对象是否为Object构造函数创建的,举例
Person.prototype.age = 18;
     function Person () {
         this.name = 'lyl';
     }
     var person = new Person();
     console.log(person instanceof Person); // true
     console.log(new Object() instanceof Person); //false

猜你喜欢

转载自blog.csdn.net/shoujing1001/article/details/85487979
今日推荐