专栏首页Super 前端JavaScript面向对象精要(二)

JavaScript面向对象精要(二)

四、构造函数和原型对象

1. 构造函数

构造函数就是用new创建对象时调用的函数。使用构造函数的好处在于所有用同一个构造函数创建的对象都具有同样的属性和方法。

function Person(){}
var p1 = new Person();
console.log(p1.constructor === Person); // true
console.log(p1 instanceof Person);      // true

可以使用构造函数来检查一个对象的类型,但还是建议使用instanceof来检查对象类型。因为构造函数属性可以被覆盖,并不一定完全准确。 示例:构造函数返回对象

function Foo(){
    this.name = "foo";
    return {name: "hhh"};
}
var f = new Foo();
f.name;         // hhh
f.constructor;  // Object

示例:构造函数返回原始类型

function Too(){
    this.name = "too";
    return "hhh";
}
var t = new Too();
t.name;         // too
t.constructor;  // Too

构造函数中显示调用return:

  • 如果返回的值是一个对象,它会替代新创建的对象实例返回;
  • 如果返回的值是一个原始类型,它会被忽略,新创建的对象实例会被返回。

2. 原型对象

请参照:【详解prototype与proto区别

3. 改变原型对象

[[Prototype]]属性只是包含一个指向原型对象的指针,并不是一个副本;任何对原型对象的改变都立刻反映到所有引用它的对象实例上。 示例:扩展原型对象

function Person(){}
var p = new Person();
p.sayHi();  //  p.sayHi is not a function(…)
Person.prototype.sayHi = function(){
    console.log("hi");
};
p.sayHi();  // "hi"

注意:在一个对象上使用Object.seal()Object.freeze()时,完全是在操作对象的自有属性,可以通过在原型上添加属性来扩展这些对象实例。 示例:冻结对象

function Person(name){}
var p = new Person();
Object.freeze(p);
p.name = "ligang";
Person.prototype.sayHi = function(){
    console.log("hi");
};
console.log(p.name);    // undefined
p.sayHi();  // "hi"

关于对象,请查看:【面向对象的程序设计】、【JavaScript高级技巧-防篡改对象

五、继承

JavaScript内建的继承方法被称为原型对象链,又称为原型对象继承。当一个对象的[[Prototype]]设置为另一个对象时,就在这两个对象之间创建了一条原型对象链。

1. Object.prototype

所有对象都继承自Object.prototype

方法

说明

hasOwnProperty()

检查是否存在一个给定名字的自有属性

propertyIsEnumerable()

检查一个自有属性是否可枚举

isPrototypeOf()

检查一个对象是否是另一个对象的原型对象

valueOf()

返回一个对象的值表达

toString()

返回一个对象的字符串表达

上述5种方法经由继承出现在所有对象中。 (1)valueOf() valueOf()默认返回对象实例本身,可以定义自己的valueOf()方法,定义的时候没有改变操作符的行为,仅仅定义了操作符默认行为所使用的值。 (2)toString() 一旦valueOf()返回的是一个引用而不是原始值的时候,就会回退调用toString()示例:

var obj1 = {
    valueOf: function(){
        return "valueOf";
    },
    toString: function(){
        return "toString";
    }
}
var obj2 = {
    valueOf: function(){
        return {name: "哈哈"};
    },
    toString: function(){
        return "toString";
    }
}
obj1 + "";  // "valueOf"
obj2 + "";  // "toString"

2. 修改Object.prototype

Object.prototype添加方法,默认是可枚举的,意味着可以出现在for-in循环中。Douglas Crockford(JavaScript之父)推荐在for-in循环中始终使用hasOwnProperty()

var empty = {};
Object.prototype.myName = "LIGANG";
for(var prop in empty){
    console.log(prop);      // 会输出:myName
}
for(var prop in empty){
    if(empty.hasOwnProperty(prop)){
        console.log(prop);  // 无任何内容输出
    }
}

所以,在进行for-in操作时,最好的方式就是增加hasOwnProperty()判断;与此时同,不要修改Object.prototype

3. 对象继承

对象继承是最简单的继承类型,需要做的就是指定哪个对象是新对象的[[Prototype]]。 创建对象过程中,字面量形式会隐式指定Object.prototype为其[[Prototype]],也可以用Object.create()方式显示指定。

var obj1 = {};
var obj2 = Object.create(Object.prototype, {});

Object.create()方法,第一个参数是需要设置为新对象的[[Prototype]]的对象,第二个参数是一个属性描述对象,格式同Object.defineProperties() 示例:原型对象链(继承)

var person = {
    name: "person",
    sayName: function(){
        console.log(this.name);
    }
};
var p1 = Object.create(person, {
    name: {
        cofigurable: true,
        enumerable: true,
        value: "ligang",
        writable: true
    }
});     

末端通常是一个Object.prototype[[prototype]]被置为null。 示例:空对象

var obj1 = {};
var obj2 = Object.create(null);
"toString" in obj1; // true
"toString" in obj2; // false

obj2没有原型对象链的对象。完全是一个没有任何预定义属性的白板,使其成为一个完美的哈希容器。

4. 构造函数继承

构造函数的所有对象实例共享同一个原型对象,所以它们都继承自该对象,但不能用原型对象继承自有属性。

function Person(name){
    this.name = name; // 私有属性
}
Person.prototype.sayName = function(){  // 共用方法
    console.log(this.name);
};
var p1 = new Person("ligang");
var p2 = new Person("camile");

p1,p2都是构造函数Person的实例,其共享Person.prototype原型对象。但其自有属性name不能通过原型对象继承。 总结:自有属性/方法通过构造函数定义,共有属性/方法通过原型对象继承!!!

六、对象模式

虽然JavaScript没有一个正式的私有(局部)属性的概念(ES6中出现了let语法,可以定义局部变量),但是可以创建仅在对象内可以访问的数据或函数。使用模块模式可对外界隐藏数据;也可以使用立即调用函数表达(IIFE)定义仅可被新创建的对象访问的本地变量和函数。

1. 私有成员和特权成员

var obj = (function(){
    // 私有变量
    var author = "ligang";
    return {
        // 公共的方法和属性
        getAuthor: function(){
            return author;
        }
    };
}());
obj.author; // undefined
obj.getAuhtor(); // "ligang"

上述函数仅存在于被调用的瞬间,一旦执行后立即就被销毁了。

// 上述示例的等价写法
var obj = (function(){
    // 私有变量
    var author = "ligang";
    var getAuthor = function(){
        return author;
    };
    return {
        // 公共的方法和属性
        getAuthor: getAuthor
    };
}());

2. 混入

混入将一个属性从一个对象复制到另一个,从而使得接受者在不需要继承提供者的情况下获得其功能。和继承不同,混入令你在创建对象后无法检查属性来源。若你想要获得更强大的功能且需要知道该功能来自哪里,继承是首选!

function mixin(des, src){
    for(var prop in src){
        if(src.hasOwnProperty(prop)){
            des[prop] = src[prop];
        }
    }
    return des;
}

注意上述方式,不是深拷贝! 奇舞团提供了深拷贝方式:https://github.com/75team/mixin.js

var a = {x:{y:1, z:3}};
mixin(a, {x:{y:2}, z:2}, function(a,b){
    try{
        return mixin(a,b, arguments.callee)
    }catch(ex){
        return b
    }
});

等价于 jQuery.extend(true, a, {x:{y:2}, z:2});

3. 作用域安全的构造函数

function Person(name){
    this.name = name;
}
var p1 = new Person("ligang");
var p2 = Person("camile");
console.log(p1 instanceof Person); // true
console.log(p2 instanceof Person); // false

作用域安全的构造函数是用不用new都可以被调用来生成新的对象实例的构造函数。其this在构造函数一开始执行就已经指向自定义类型的实例。当然,你可以根据new的使用与否决定构造函数的行为。

function Person(name){
    if(this instanceof Person){
        this.name = name;
    }else {
        return new Person(name);
    }

}
var p = Person("ligang");
console.log(p instanceof Person); // true

许多内建构造函数,例如Array、RegExp不需要new也可以工作,正是因为它们被设计之初采用了作用域安全的构造函数。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【实例】调整区域大小&动态隐藏区域

    实例参照地址:https://jsfiddle.net/381510688/fb6Lz9rm/

    奋飛
  • 元素、文字垂直居中

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • CSS布局相关及Flex详解

    对于两个div元素,其是相对独立的,如果在其中一个div元素中加入内容,将会使得两个元素的底部不能对齐,导致页面多出空白区域。

    奋飛
  • 在同一行布局的技巧 原

    例如上面的布局,我们可以使用里面元素浮动,外面的div高度为0的特点来布局,使2个div重叠在一起

    tianyawhl
  • 原生js实现简单移动端轮播图

    最近项目不是很忙,自己就用原生js写了一个简单的移动端轮播图的小demo,可实现自动轮播和手势滑动轮播,然后就把它记录到个人博客里。还有很多不足的地方,希望多多...

    用户1174387
  • 【vue学习】vue改变样式

    Swingz
  • 扩展Yarn资源模型详解1

    问题导读 1.countable资源是指哪些? 2.noncountable资源,本文列举了什么资源? 3.标签是否为资源? 4.如何实现扩展YARN资源模型...

    用户1410343
  • Redis源码学习之对象系统

    在前面的文章中,我介绍了Redis的底层数据结构,但Redis对外提供的命令并没有直接使用它们,而是基于它们构建更高级的数据对象,总共包括5中对象类型,分别为【...

    里奥搬砖
  • Java源码系列1——ArrayList

    本文简单介绍了 ArrayList,并对扩容,添加,删除操作的源代码做分析。能力有限,欢迎指正。

    超超不会飞
  • 微信小程序开发基础

    查看官方文档:https://developers.weixin.qq.com/miniprogram/dev/component/

    达达前端

扫码关注云+社区

领取腾讯云代金券