资讯专栏INFORMATION COLUMN

面向对象的小九九

时飞 / 3085人阅读

摘要:由构造函数返回的对象就是表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤创建的对象。运算符返回一个布尔值,表示对象是否为某个构造函数的实例。

面向对象

本人能力有限,有误请斧正

本文旨在复习面向对象(不包含es6)

本文学习思维

创建对象的方式,获取对象属性

构造函数,构造函数的new 做了什么

原型与原型对象

原型链

继承(借用构造继承、原型继承、组合继承、寄生组合继承)

获取对象属性的三个方法

for...in..

Object.keys() ie9以上放心使用

keys的支持

Object.getOwnPeropertyNames 会把所有属性枚举出来(如数组的length)

创建对象的方法

字面量 var o = {};

构造函数 var o = new Object()

Object.create() var o = Object.create( )

object.create()有两个参数第一个是要新创建对象的原型对象, 第二个可选:自己定义的属性,需要配置麻烦一般不用,返回一个新对象,带着指定的原型对象和属性

通过给Object.create()参数传null可以获得一个纯净的没有原型的对象。原因是null是原型链的链末

输出查看原型链发现create在创造一个对象会存在传入的属性与方法
这个方法太适合继承了,他会直接继承传入的属性和方法
通过测试传入{}时,也会存在Object.__proto__ 指向 Object.__proto__;

// Object.create() 实现方式 
// 实际这个是原型式继承的核心
var object = function(proto){
    var F = function(){}; // 创建一个对象
    F.prototype = proto; //变量原型指向传入对象
    return new F();
}
构造函数、实例、原型、原型链

参考资料:MDN-继承与原型链

构造函数:

是一个函数

首字母大写

使用new关键字创建

构造函数(函数声明或者函数表达式)本质还是函数,只是用来创建对象,还有个惯例就是首字母大写

// 构造函数
function Person(){}
// 调用构造函数,创建对象
var p1 = new Person();
为什么说构造函数特殊呢,首先聊聊new关键字

参考:

MDN-new运算符

高程3--p145

阮一峰博客

通过new关键字可以创建新对象!

在new一个对象的时候做了什么事?4个步骤(高程3)

创建一个新对象

将构造函数的作用域赋给新对象 (因此this就指向了这个新对象)

执行构造函数中的代码(为这个新对象添加属性)

返回新对象(如果构造函数中返回了其他对象,则返回其他对象)

MDN简化版,只讨论过程,无法传参
    // MND简化
    
    /*
    一个继承自 Foo.prototype 的新对象被创建。
    
    使用指定的参数调用构造函数 Foo ,并将 this 绑定到新创建的对象。
    new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
    
    由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。
    (一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)
    */
    
    var Foo = function(){};
    var _new2 = function(fn){
        var o = Object.create(fn.prototype);
        var k = fn.call(o);
        if(typeof k === "object"){
            return k;
        }else{
            return o;
        }
    }
    var f = _new2(Foo);

阮老师的版本可以传参,而且很详细了
    function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
      // 将 arguments 对象转为数组
      var args = [].slice.call(arguments);
      // 取出构造函数
      var constructor = args.shift();
      // 创建一个空对象,继承构造函数的 prototype 属性
      var context = Object.create(constructor.prototype);
      // 执行构造函数
      var result = constructor.apply(context, args);
      // 如果返回结果是对象,就直接返回,否则返回 context 对象
      return (typeof result === "object" && result != null) ? result : context;
    }

    // 实例
    var actor = _new(Person, "张三", 28);
实例是什么
构造函数是对一个对象的抽象描述,实例则是对象的具体表现
原型对象(prototype)

好吧!大boss出场,都说javaScript最具有特色的就是原型

参考:

高程3 --p147

原型是什么?(高程3)

我们创建的每个函数都有一个prototype(原型) 属性,这个属性是一个指针,指向一个对象

函数的属性

原型指向一个对象

理解原型对象 高程3 -- p148有兴趣可以去读一下

无论什么时候只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性将指向函数的原型对象。在默认情况下,所有的原型对象会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所有函数的指针。通过这个构造函数,我们还可以继续为原型对象添加其他属性和方法

按照书上的理解:

简述:([[Prototype]] === __proto__

所有构造函数有一个属性指向原型对象(prototype)

所有由构造器生成的实例对象中有个__poroto__指向原型对象

原型对象中都有一个constructor的属性,指向构造函数

        var Person = function() {};
        Person.prototype.age = 1;
        var p = new Person();
        var p2 = new Person();
        console.log(p.__proto__ === Person.prototype); // true
        console.log(p2.__proto__ === Person.prototype); // true
        console.log(Person === Person.prototype.constructor); // true
原型链

原型链就是在查找到某个属性或者方法不断向上查找的一个过程

MDN-非常具有代表的简化原型链

// 让我们假设我们有一个对象 o, 其有自己的属性 a 和 b:
// {a: 1, b: 2}
// o 的 [[Prototype]] 有属性 b 和 c:
// {b: 3, c: 4}
// 最后, o.[[Prototype]].[[Prototype]] 是 null.
// 这就是原型链的末尾,即 null,
// 根据定义,null 没有[[Prototype]].
// 综上,整个原型链如下: 
// {a:1, b:2} ---> {b:3, c:4} ---> null

console.log(o.a); // 1
// a是o的自身属性吗?是的,该属性的值为1

console.log(o.b); // 2
// b是o的自身属性吗?是的,该属性的值为2
// 原型上也有一个"b"属性,但是它不会被访问到.这种情况称为"属性遮蔽 (property shadowing)"

console.log(o.c); // 4
// c是o的自身属性吗?不是,那看看原型上有没有
// c是o.[[Prototype]]的属性吗?是的,该属性的值为4

console.log(o.d); // undefined
// d是o的自身属性吗?不是,那看看原型上有没有
// d是o.[[Prototype]]的属性吗?不是,那看看它的原型上有没有
// o.[[Prototype]].[[Prototype]] 为 null,停止搜索
// 没有d属性,返回undefined

再用对象表示一个

// 属性遮蔽
function Person() {
    this.name = "111";
}
Person.prototype.name = "222";

var p1 = new Person();
console.log(p1.name); // 111
console.log(p1.__proto__.name); // 222


var p2  = new Person();
console.log(p2.age);

现在要查找p2.age属性

实例对象中有没有?没有

实例对象通过__prope__找到原型对象,原型对象中有么?没有

找到Object的原型中查找有么?没

找到null这个对象,作为作用域的链末,也没有,这个值就是undefined

属性屏蔽就是找到了就不会再找了(实例上的属性>原型链上的属性),实际还是存在

几种能遇到的操作符

in操作符

isPrototypeOf()

Object.getPrototypeOf()

instanceof (对象)

typeof

in操作符
如果指定的属性在指定的对象或其原型链中,则in 运算符返回true。语法:prop in object
isPrototypeOf()
isPrototypeOf() 方法用于测试一个对象是否存在于另一个对象的原型链上。
function Foo() {}
function Bar() {}
function Baz() {}

Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);

var baz = new Baz();

console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true
instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
instanceof的原理是检查右边构造函数的prototype属性,是否在左边对象的原型链上。(判断他们的地址指向是否一致)。有一种特殊情况,就是左边对象的原型链上,只有null对象。这时,instanceof判断会失真。

几点instanceof的注意

用于对象(由于instanceof的原理)

与null有关要注意

//instanceof判断会失真
var obj = Object.create(null);
typeof obj // "object"
Object.create(null) instanceof Object // false


//null作为一个特殊的Object却不属于Object创建的实例,null原型链的链末
undefined instanceof Object // false
null instanceof Object // false

// instanceof 用于对象
var str = "1"
var str2 = new String("2");
str instanceof String // false
str2 instanceof String // true
typeof
typeof 1 //number
typeof "" // string
typeof undefined //undefined
typeof true // boolean
typeof function(){} // function
typeof {}  // object
typeof []  // object
typeof null // object
typeof Symbol() //symbol ES6
继承:

继承有几种

我用我总结了一些思维导图

创建对象

继承的优缺点

这里把继承的几种方式罗列出来方便查阅,以下大多是代码,简易的我总结在思维导图中了

1.原型(链)继承

关键点是要打通原型链
由于原型对象是函数初次创建就会存在的对象,所以会共享
共享就会存在共享问题

优点:

共享属性与方法

可以通过instanceof来判断关系

缺点:

共享问题

不能传递参数

        // 父类
        function SuperType() {
            this.property = true;
        }
        SuperType.prototype.getSuperValue = function () {
            return this.property;
        }
        
        // 子类
        function SubType() {
            this.subproperty = false;
        }
        // 继承父类 打通原型链
        SubType.prototype = new SuperType();
        SubType.prototype.getSubValue = function () {
            return this.subproperty;
        }
        
        var instance = new SubType();
        console.log(instance.getSuperValue()); // true
借用构造函数

关键在于环境变量(this)的指向,由于每次创建都会创建一个新的this所以会拥有自己的属性与方法,由于是改变this指向所以无法共享原型对象

优点:

私有属性与方法

可以传参数

缺点:

引用类型,重复创建,冗余浪费内存

无法共享

无法判断关系

        // 父类
        function SuperType() {
            this.colors = ["red"];
        }
        // 子类
        function SubType() {
            // 继承父类
            SuperType.call(this);
        }
        var instance1 = new SubType();
        colors.push("black");
        cosnole.log(instance1.colors); // red,black
        var instance2 = new SubType();
        console.log(instance2.colors); // red
组合式继承

组合了原型继承与借用构造函数继承继承了优点,但是由于组合,所以创建了两次对象,造成轻微的浪费空间

优点:

私有属性和方法

共享属性与方法

可以确认实例与构造函数之间的关系

缺点

造成内存的冗余浪费

        // 父类
        function SuperType(name) {
            this.name = name;
            this.colors = ["red"];
        }
        SuperType.prototype.sayName = function () {
            console.log(this.name);
        }
        // 子类
        function SubType(name, age) {
            // 继承属性
            SuperType.call(this, name);
            this.age = age;
        }
        // 继承方法
        SubType.prototype = new SuperType();
        SubType.prototype.sayAge = function () {
            console.log(this.age);
        }
        var instance1 = new SubType("name1", 1);
        instance1.colors.push("black");
        console.log(instance1.colors); // red,black
        instance1.sayName(); // name1
        instance1.sayAge(); // 1

        var instance2 = new SubType("name2", 2);
        console.log(instance2.colors); // red
        instance2.sayName(); // name2
        instance2.sayAge(); // 2
寄生组合继承

寄生组合继承是把原型继承给改掉,实际上就是想要父级的原型链,不一定要创建对象所以有了寄生组合继承,该继承是目前最完善的继承方式

        // 寄生继承
        function inheritPrototype(subType, superType) {
            var prototype = Object.create(superType.prototype);
            prototype.constructor = subType;
            subType.prototype = prototype;
        }
        // 父类
        function SuperType(name) {
            this.name = name;
            this.colors = ["red"];
        }
        SuperType.prototype.sayName = function () {
            console.log(this.name);
        }    
        // 子类继承
        function SubType(name, age) {
            SuperType.call(this, name);    
        }
        inheritPrototype(SubType, SuperType);
        SubType.prototype.sayAge = function () {
            console.log(this.age)
        }
参考资料:

MDN-对象

冴羽的博客

阮一峰的网络日志

javaScript高级程序设计第三版

阮一峰的网络日志:

Javascript继承机制的设计思想

Javascript 面向对象编程(一):封装

Javascript面向对象编程(二):构造函数的继承

Javascript面向对象编程(三):非构造函数的继承

《JavaScript 标准参考教程(alpha)》,by 阮一峰

看了高程3与阮一峰老师的博客,结合起来更加好理解

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

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

相关文章

  • 从零开始单排学设计模式「UML类图」定级赛

    摘要:从零开始单排学设计模式的国服排位之旅,今天正式开启目前段位定级赛这篇文章来总结下类图,本来不打算讲类图的,因为我在学习设计模式的时候,一遇到有关的就会自动忽略,一看感觉就很复杂。关联关系用实现箭头来表示。 阅读本文大概需要 3.5 分钟。 本篇是设计模式系列的开篇,虽然之前也写过相应的文章,但是因为种种原因后来断掉了,而且发现之前写的内容也很渣,不够系统。 所以现在打算重写,加上距离现...

    Loong_T 评论0 收藏0
  • (void 0)与undefined之间九九

    摘要:又是啥是原始类型值之一,也是全局对象的属性,在部分低级别的浏览器中可以被修改,在局部作用域中也可以被修改。所以常见的解决方法是在这个问题中提到用去替代标签的空属性会减少页面请求是否属实有待考证结尾第一篇暂时写完了,欢迎大家吐槽和提意见。 前言 原文链接 源码地址 这是underscore.js源码分析的第一篇文章,为什么选择写这篇文章呢?其实主要有两点 下划线源码中通篇可见这样的判断...

    novo 评论0 收藏0
  • (void 0)与undefined之间九九

    摘要:又是啥是原始类型值之一,也是全局对象的属性,在部分低级别的浏览器中可以被修改,在局部作用域中也可以被修改。所以常见的解决方法是在这个问题中提到用去替代标签的空属性会减少页面请求是否属实有待考证结尾第一篇暂时写完了,欢迎大家吐槽和提意见。 前言 原文链接 源码地址 这是underscore.js源码分析的第一篇文章,为什么选择写这篇文章呢?其实主要有两点 下划线源码中通篇可见这样的判断...

    Arno 评论0 收藏0
  • 每日 30 秒 ⏱ img の 九九

    showImg(https://raw.githubusercontent.com/pushmetop/resource/master/30-seconds-for-everyday/img-tag/poster.png); 简介 SEO、跨域、无障碍阅读、事件、图片标签 小九九 最直接的联想便是 九九乘法表,但是 小九九 也用在形容一个人在心里打着算盘 小主意 和 小秘密。小秘密已经被 H1 の...

    lolomaco 评论0 收藏0

发表评论

0条评论

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