欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-17 13:12:41
...

对象

对象是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
相关标签: JS 对象