资讯专栏INFORMATION COLUMN

Js面向对象及原型原型链总结

Eidesen / 1163人阅读

摘要:继承每个构造函数都有一个原型对象,而原型对象中都有一个默认指向构造函数的指针构造函数实例化后,实例化的对象又有一个内部指针指向原型对象所以他们是存在自内而外的一个逐层调用的关系。

面向对象设计

面向对象都有的概念,所以都可以通过类创建相同属性方法(同一类嘛)的若干个对象,但是ECMAScript中没有类,所以它的对象和基于类的语言中的对象有所不同。对象的基本表现形式:

var person = {
    name:"Nicholas",
    age:29,
    job:"Soft Engineer",
    sayName() {
        alert(this.name); //this.name被解析为person.name
    }
}
属性类型

JavaScript中为了实现JavaScript引擎规定了内部属性,内部属性是不可直接访问的,用[[property]]表示。ECMA有两种属性:数据属性和访问器属性

数据属性

利用这些数据属性更改操作对象如下:

          var person = {
              name:"liming", // [[Value]]特性将被设置为liming,值得改变反应在[[Value]]上
              age:"23", // 同上
          }
          Object.defineProperty(person,"age",{
             writable:false, // 不可改值
             configurable:false // 不可删除值,
          })
  
  

注意configurable只能修改一次,没后悔药可吃。还有Object.defineProperty()如果默认不制定第三个参数,那么将属性的特性默认全部为false,这个方法在IE8上尽量不要用。

访问器属性

访问器属性不包含数据值,他包含一对儿getter和setter(不过这两个都不是必须的).他的核心作用是当一个属性改变的时候,另一个属性也随着他改变。他俩必须同时出现,一下代码可以很好的理解上面的话:

    var article = {
        _year:2018,
        edition: 1
    }
    Object.defineProperty(article,"year",{
        get: function() {
            return _this.year
        }
        set:function(val) {
            if(val > 2018) {
                this._year = val;
                edition += val - 2018;
            }
        }
    })
读取属性的特性

在这个例子中

var book = {};
Object.defineProperties(book,{
    _year: {
        writable:true,
        value:2018
    },
    edition: {
        writable:true,
        value:1
    },
    year: {
        get:function() {
            return this._year
        },
        set:function(val) {
            if(val > 2018) {
                this._year = val;
                this.edition += val - 2018;
            }
        }
    }
})

我们可以使用Object.getOwnPropertyDescriptor的方法获得属性的描述符。它返回一个对象,因此在上面的代码中,加入如下代码:

var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
// {value: 2018, writable: true, enumerable: false, configurable: false}

var descriptor2 = Object.getOwnPropertyDescriptor(book,"year");
// {get: ƒ, set: ƒ, enumerable: false, configurable: false}
创建对象 1.工厂模式
function createPerson(name,age,job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        alert(this.name)
    }
    return o;
}
var person1 = createPerson("namea",27,"joba") // {name: "namea", age: 27, job: "joba", sayName: ƒ}
var person2 = createPerson("nameb",17,"jobb") // {name: "nameb", age: 17, job: "jobb", sayName: ƒ}
2.构造函数模型
    function Person(name,age,job) {
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function() {
            alert(this.name);
        };
    }
    person1 = new Person("namea",27,"joba");
    person2 = new Person("nameb",17,"jobb");

在创建Person实例的过程中,必须要new出来,以这种方式调用构造函数会经历如下四个过程:
1)创建一个新对象
2)把构造函数中的this指向这个新对象
3)执行构造函数的代码
4)返回新对象

实例化构造函数的新对象后,他的对象类型是构造函数类型,同时也是Object类型
function Person(name,age,job) {
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function() {
            alert(this.name);
        };
    }
    person1 = new Person("namea",27,"joba");
    console.log(person1 instanceof Person) // true
    console.log(person1 instanceof Object) // true
    
    

这条规则,也是它胜过工厂模式的一个原因

原型模式
function Person (){}
Person.prototype.name ="andy";
Person.prototype.age = 25;
Person.prototype.sayName = function() {
    alert(this.name)
}
var person1 = new Person();
var person2 = new Person();

person1.sayName === person2.sayName是对的,因为他们共同指向一个引用,而构造函数类型就不一样,所以可以说在一定方面节省了内存。想知道构造函数实例化的对象和构造函数之间原型的关系,要看书上图6-1,当能默写出来基本就理解了。文字描述如下:
当创建一个新函数时,都会创建一个prototype属性->这个属性指向函数的原型对象(理解为prototype指向的是一个对象) -> 所有原型对象又会自动获得一个constructor属性,而它又指向刚才创建的这个函数(绕回来了)。所以上面的过程可以理解为Person === Person.prototype.constructor // true
第二点,我们通过构造函数创建的实例有一个私有属性__proto__,这个私有属性指向构造函数的原型对象,即person1.__proto__ === Person.prototype // true。同理我们可以再继续往上推,构造函数Person是个函数对象,那么Person也应该有__proto__,他的私有属性应该是Function.prototype 即Person.__proto__ === Function.prototype //true

实例中的属性和原型中的属性

创建实例属性和原型中的属性重名的时候,他们会同时存在,但是你获取对象的属性时,会按照先在实例中搜索再从原型中搜索的顺序为搜索原则,即使将实例中的同名属性设置为null,也会读取实例属性,而用delete这个属性后,实例中搜不到了,才会去原型中获取。

function Person() {}
Person.prototype.name = "andy";
Person.prototype.sayName = function() {
    console.log(this.name);
};
Person.prototype.age = 25;

var person1 = new Person();
var person2 = new Person();
person1.name = "liming";
console.log(person1.name) // liming
console.log(person2.name) // andy
person1.name = null;
console.log(person1.name) // null
delete person1.name // andy
hasOwnProperty

用于检测属性是存在于原型中还是实例中。因为是Object继承下来的方法,所以当然是存在于对象中返回true,上面的例子加上如下处理:

function Person() {}
Person.prototype.name = "andy";
Person.prototype.sayName = function() {
    console.log(this.name);
};
Person.prototype.age = 25;

var person1 = new Person();
var person2 = new Person();
person1.hasOwnProperty("name") // false
person1.name = "liming";
person1.hasOwnProperty("name") // true
原型与in操作符(in与for in)

in操作符返回的是true和false,无论在实例中,还是在原型中in都能找到,而hasOwnProperty()只能检测出在实例中,而对象中有没有就不清楚了。所以引申出了一个检测只存在于原型中的方法,如下:

    function hasPrototypeProperty(obj,property) {
        return !obj.hasOwnProperty(property) && (property in obj);
    }
    function Person() {};
    Person.prototype.msg = "I"m only in prototype";
    var person1 = new Person();
    person1.name = "andy";
    hasPrototypeProperty(person1,"msg"); // true
    hasPrototypeProperty(person1,"name"); // false

想获取对象中实例和原型中的所有可枚举属性:for-in([[Enumerable]]为true)的,constructor,hasOwnProperty(),toString()...这类默认都设置成了false,所以枚举不出来他们,例如:

   function Person() {};
   Person.prototype.msg = "I"m only in prototype";
   person1.__proto__.age = 25; // 和上面定义方式的结果一样
   var person1 = new Person();
   person1.name = "andy";
   
   
   for(var prop in person1) {
       console.log(prop) // name msg age  //没有constructor,因为不可枚举
   }

想获取对象中或实例和原型中的所有可枚举属性:Object.keys()

    //接上个例子
    Object.keys(Person.prototype); // ["msg","age"]
    Object.keys(person1) // ["name"]

想获取对象中实例或原型中的所有可枚举和不可枚举属性:Object.getOwnPropertyNames()

    Object.hasOwnPropertyNames(Person.prototype) //  ["constructor", "msg", "age"]
字面量形式定义对象原型

上面的方式中,定义原型属性的时候都太过繁琐,可以用对象字面量的形式来定义,如下:

function Person() {};
Person.prototype = {
    age:25,
    name:"andy",
    job:"Soft engineer",
    sex:"man"
}

但这样定义产生了一个问题 -> constructor的指向不再是Person了,因为我们重写了Person.prototype(constrctor是原型对象中自动生成的属性),如果constructor真的很重要,需要我们这样写原型,再以上基础添加一个constructor属性:

function Person() {};
Person.prototype = {
    constructor:Person,
    age:25,
    name:"andy",
    job:"Soft engineer",
    sex:"man"
}

但这样写又有一个问题 -> constructor的[[Enumerable]] 从默认的false变成true了。所以还要运用前面的知识,Object.defineProperty(Person.prototype,"constructor"),就和之前一样了。

原型的动态性:

用字面量重写了对象的原型后,实际上会改变对象原型的指针,如高程书中6-3,描述的非常形象,这里看一个例子就能深刻明白了:

    var friend = new Person();
    Person.prototype.sayHi = function() {
        alert("hi");
    };
    friend.sayHi(); // hi

实例与原型存在松散关系,所以引用这个方法不会有问题;

    function Person() {}
    var friend = new Person();
    Person.prototype = {
        ......,
        sayHi: function() {
            alert("hi");
        };
    }
    friend.sayHi(); // error
原生对象的原型

要理解,我们的引用类型,比如arr = new Array 的Array,Object,String...背后也都有prototype原型的,里面有不少方法,当然我们可以为它添加方法,例如:

String.prototype.startWith = function(word) {
    return this.indexOf(word) === 0;
};
"kdsfkjs".startWith("abc") // false;
"kdsfkjs".startWith("kdsf") // true;

这种写法虽然很好,但是不建议用,因为可能产生命名冲突等问题。

原型对象的缺点

它最大的问题在于共享引用类型的属性上面,如下例子

    function Person() {}
    Person.prototype = {
        constructor: Person,
        name: "andy",
        age: 25,
        friend : ["shelby","jony"],
        sayHi : function() {
            alert("Hi");
        }
    }
    var person1 = new Person();
    var person2 = new Person();
    
    person1.friend.push("lee") // 目的是给person1这个对象里的friend增加自己的一个值。
    
    alert(person1.friend) //["shelby","jony","lee"]
    alert(person2.friend) //["shelby","jony","lee"]

由于friend是引用类型属性,指向一个地址,而friend最初还在prototype中,所以给引用类型操作后,只是在引用地址不变的前提下修改属性值,而person2的friend属性也引用该地址,所以就会产生我们不想要的结果。

组合使用构造函数和原型

构造函数用来实例化独特的属性,而原型方法可以创建公有的方法,节省内存,所以他俩组合使用是很好的方式。

function Person(name,age,job) {
    this.age = age;
    this.name = name;
    this.job = job;
    this.friends = ["lee","jony"];
}
Person.prototype = {
    constructor:Person,
    sayName:function() {
        alert(this.name)
    }
}
var person1 = new Person("Nicholas",29,"Software engineer");
var person2 = new Person("Greg",27,"Doctor");
person1.friends.push("marry");
console.log(person1.friends) // ["lee","jony","marry"];
console.log(person2.friends) // ["lee","jony"]
继承

每个构造函数都有一个原型对象(prototype),而原型对象中都有一个默认指向构造函数的指针(constructor),构造函数实例化后,实例化的对象又有一个内部指针(__proto__)指向原型对象所以他们是存在自内而外的一个逐层调用的关系。需要把继承链的图捯饬清楚就和上面原型链的知识没什么区别了

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/96635.html

相关文章

  • JS程序

    摘要:设计模式是以面向对象编程为基础的,的面向对象编程和传统的的面向对象编程有些差别,这让我一开始接触的时候感到十分痛苦,但是这只能靠自己慢慢积累慢慢思考。想继续了解设计模式必须要先搞懂面向对象编程,否则只会让你自己更痛苦。 JavaScript 中的构造函数 学习总结。知识只有分享才有存在的意义。 是时候替换你的 for 循环大法了~ 《小分享》JavaScript中数组的那些迭代方法~ ...

    melody_lql 评论0 收藏0
  • 做个JS面向对象的笔记

    摘要:组合构造原型模式将自身属性于构造函数中定义,公用的方法绑定至原型对象上原型对象的解释每一个函数创建时本身内部会有一个固有的原型对象,可以通过函数名去访问,而其原型对象又有一个属性指针指向该函数。 每次遇到JS面对对象这个概念,关于继承及原型,脑海里大概有个知识框架,但是很不系统化,复习下,将其系统化,内容涉及到对象的创建,原型链,以及继承。 创建对象 两种常用方式,其余的比较少见工厂模...

    GitCafe 评论0 收藏0
  • JavaScript原型原型

    摘要:每个原型对象都有一个属性指向关联的构造函数为了验证这一说话,举个例子。 本文共 1475 字,读完只需 6 分钟 一、概述 在 JavaScript 中,是一种面向对象的程序设计语言,但是 JS 本身是没有 类 的概念,JS 是靠原型和原型链实现对象属性的继承。 在理解原型前,需要先知道对象的构造函数是什么,构造函数都有什么特点? 1. 构造函数 // 构造函数 Person() ...

    liaosilzu2007 评论0 收藏0
  • 再谈JavaScript面向对象思想继承

    摘要:面向对象中有三大特征,封装,继承,多态。这不仅无法做到数据共享,也是极大的资源浪费,那么引入对象实例对象的属性指向其构造函数,这样看起来实例对象好像继承了对象一样。实例对象的原型指向其构造函数的对象构造器的指向。 前言 为什么说是再谈呢,网上讲解这个的博客的很多,我开始学习也是看过,敲过就没了,自以为理解了就结束了,书到用时方恨少啊。实际开发中一用就打磕巴,于是在重新学习了之后分享出来...

    svtter 评论0 收藏0
  • 复习Javascript专题(三):面向对象对象的创建与继承,原型原型

    摘要:在创建子类实例时,不能向超类型的构造函数中传递参数。构造函数继承子类传进的值是基本思想是在子类构造函数的内部调用超类或父类型构造函数。继承保证构造函数指针指向如果想同时继承多个,还可使用添加属性的方式类继承, OOP:Object Oriented Programming 面向对象编程。 题外话:面向对象的范围实在太大,先把这些大的东西理解理解。 1.什么是对象? 根据高程和权威指南上...

    testHs 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<