JavaScript的OO思想(一)

2023-11-13

类class是Object-Oriented面向对象的语言有一个标志,通过类我们可以创建任意多个具有相同属性和方法的对象。JavaScript中没有类的概念,但它也是面向对象的,只是实现方法会有所不同。

创建单个对象有两种基本方法:

1.使用Object的构造函数创建实例然后在实例上添加属性和方法;
2.使用对象字面量的方法。

在简单的场景这两种方法是很实用的,但如果遇到有很多对象有相同的属性,相同的方法的时候,这两种方法的弊端就暴露出来了,会产生大量的重复代码。

为了应对不同的场景和方法,创建对象有以下几种方法。

1、工厂模式。所谓的工厂模式指的是使用函数封装利用Object的构造函数创建实例再添加属性和方法的步骤,然后返回该实例。这样每次运行这个方法我们都会得到一个结构相同的对象。

function createPerson(name, age){
    var person = new Object();
    person.name = name;
    person.age = age;
    person.sayHi = function () {
        alert("My name is " + person.name + ", I'm " + person.age);
    }

    return person;
}

var liLei = createPerson("LiLei", 12);
liLei.sayHi();

这个模式虽然解决了重复代码的问题,但是创建出的对象都是Object类型,没有解决对象识别问题

2、构造函数模式。利用构造函数模式可以解决对象是别问题。改造上面的代码可得:

function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayHi = function () {
        alert("My name is " + this.name + ", I'm " + this.age);
    }
}

var liLei = new Person("LiLei", 12);
liLei.sayHi();

对比工程模式我们可以看到以下3处不同:

1、没有显示创建对象
2、直接将属性和方法赋值给了this对象
3、没有return

使用构造函数会经历一下4个步骤:

1、创建一个新的对象
2、将构造函数的作用域赋值给新的对象,这样this就指向了新的对象
3、执行构造函数中的代码(为这个新对象添加属性或方法)
4、返回新对象

所以上面的的形式就如同以下所示一般:

var hanMeimei = new Object();
Person.call(hanMeimei, "Han Meimei", 12);
hanMeimei.sayHi();

然而构造函数模式并非是完美的,就比如Person的sayHi方法,我们每次创建一个新的实例的时候,都会创建一个新的sayHi并为之开辟新空间,尽管这个sayHi方法做的事是一模一样的

var liLei = new Person("LiLei", 12);
var hanMeimei = new Person("Han MeiMei", 12);
alert(liLei.sayHi == hanMeimei.sayHi); //false

创建多个相同的方法确实没有必要。虽然我们可以像下面的例子一样定义一个全局的函数来解决这个问题,但是由此引发的问题就是,如果我们有多个方法,就需要定义多个全局函数了,这不仅会破坏自定义引用类型的封装性,还会使全局作用域混乱,是一种很不安全的做法。

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

function sayHi () {
    alert("My name is " + this.name + ", I'm " + this.age);
}

3、原型模式。为了解决构造函数的重复定义相同的方法的问题,原型模式便出现了。

首先我们得理解什么是原型,原型即prototype,它是一个指针,指向一个对象,这个对象的用途就是包含特定类型的所有示例共享的属性和方法,我们创建的每一个函数都会有原型(prototype)属性。使用原型的好处是,我们将共享的属性或方法赋值给原型对象,该类型的所有实例都可以访问该属性或方法。

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

Person.prototype.sayHi = function () {
    alert("My name is " + this.name + ", I'm " + this.age);
}

var liLei = new Person("Li Lei", 12);
liLei.sayHi();//My name is Li Lei, I'm 12
var hanMeimei = new Person("Han MeiMei", 12);
hanMeimei.sayHi();//My name is Han MeiMei, I'm 12
alert(liLei.sayHi == hanMeimei.sayHi); //true

关于原型对象有以下3点需要注意:

(1)、如果我们在实例中添加一个属性,该属性与原型对象中的属性同名的话,处理的结果是原型中的属性被屏蔽了,记住是被屏蔽了而不是重写了,因为如果这个时候再删除实例中的这个属性的话,再访问这个值就会访问原型中的同名属性,并且该值为原型初始化时的值,一直未改过。

function Dog(age){
    this.age = age;
}

Dog.prototype.name = "Little White";

var littleWhite = new Dog();
alert(littleWhite.name); // Little White;
littleWhite.name = "Xiao Bai"; 
alert(littleWhite.name); //Xiao Bai
delete littleWhite.name;
alert(littleWhite.name); // Little White;

(2)、原型对象中的引用属性在其中一个实例里被改变,则所有的实例获取该属性都将得到新的值。

function MyToy(){

}

MyToy.prototype.group = ["Dingding", "Dingxi", "Lala"];

var toy1 = new MyToy();
var toy2 = new MyToy();
toy1.group.push("Po");
alert(toy2.group); // Dingding,Dingxi,Lala,Po

如上上述例子所示,我们通过实例toy1修改了原型对象的属性group,toy2去获取的时候值已经发生了变化。

(3)、初始化一个实例后,再给函数的原型对象重新赋值,已创建的实例与新的原型对象不会有联系,也就是说已创建的实例不能访问新原型对象的方法和属性。如下所示:

var toy = new MyToy();

MyToy.prototype = {
    group: ["Dingding", "Dingxi", "Lala"]
};

alert(toy.group); // undefined

以上三点也可以很好的帮助我们理解原型prototype,它是一枚指针,默认指向一个Object对象,在创建函数时会相应为其初始化一枚这样的指针,当使用该函数new出一个新的实例时,该指针又会赋值给new出的实例。所以在new出一个新实例后,再给函数的原型对象赋值个新的对象是,函数的原型指针指向了新的对象,但是此时的实例的原型的指针指向的还是旧的对象。
这里写图片描述

如果单单只是用原型模式的话,事实上并不能满足所有的需求。原型模式也是没有缺点,主要体现在以下两点:

1、省去了构造函数传参
2、所有的实例共享原型的属性,这个是非常可怕的

4、组合使用构造函数和原型模式。单独使用原型模式会有弊端,但和其他模式组合起来使用的话,有些问题就迎刃而解了。典型的就是组合使用构造函数和原型模式,它是目前ECMAScript中,认同度最高,使用最广泛的一种创建自定义类型的方法。

function MyToy(owner){
    this.owner = owner;
    this.group = ["Dingding", "Dingxi", "Lala"];
}

MyToy.prototype.sayOwner = function(){
    alert(this.owner);
};


var lileis = new MyToy("Li Lei");
var hanMeimeis = new MyToy("Han Meimei");
lileis.group.push("Po");
alert(lileis.group);//Dingding,Dingxi,Lala,Po
alert(hanMeimeis.group);//Dingding,Dingxi,Lala
alert(lileis.group == hanMeimeis.group); // false
alert(lileis.sayOwner == hanMeimeis.sayOwner); // true

5、动态原型模式。有其它OO语言开发经验的朋友看到独立的函数和独立的原型的时候会有点困惑,甚至难以理解。例如在C#类中,方法和属性是一个整体,是一起定义的

public class MyToy
{
    public string owner { get; set;}
    public string[] group { get; set; }

    public void sayOwner()
    {
        //...
    }
}

当然JS里也可以变成这样,只是方法有所不同。

function MyToy(owner){
    this.owner = owner;
    this.group = ["Dingding", "Dingxi", "Lala"];

    if(typeof this.sayOwner != "function") {
        MyToy.prototype.sayOwner = function(){
            alert(this.owner);
        };
    }
}

var liLei = new MyToy("Li Lei");
liLei.sayOwner();//Li Lei

这便是动态原型模式,当你需要同时定义很多个原型函数的时候,不需要为每一个函数都做一次if判断,仅需要判断一个就可以了,然后把所有的定义都放在一个if语句中。

6、寄生构造函数模式。当我们想封装JS中的引用对象时,可能以上的方式都不适用,这个时候我们就可以使用这个所谓的寄生构造函数模式了。

function MyToy(){
    // Create Array
    var arr = new Array();

    // Add values
    arr.push.apply(arr, arguments);

    // Add method
    arr.toPipedString = function(){
        return this.join("-");
    }

    return arr;
}

var toys = new MyToy("Dingding", "Dingxi", "Lala");
alert(toys.toPipedString()); // Dingding-Dingxi-Lala
alert(toys instanceof MyToy); // false
alert(toys instanceof Array); // true

我们可以注意到此时toys instanceof MyToy的值是false,这主要是因为构造函数返回的对象不是MyToy类型的实例,而由于在构造函数中出现了return arr,所以得到的arr,是一个Array类型的对象。

7、稳妥构造函数模式。所谓的稳妥模式,即使在函数体内不访问this,没有公共属性,变量私有化,这种模式比较适合在安全的环境中或者不想数据被第三方的应用程序改动时使用。

function MyToy (name) {
    var obj = new Object();
    obj.sayName = function(){
        alert(name);
    }
    return obj;
}

var dingding = MyToy("Ding ding");
dingding.sayName();

在这个创建的对象中除了调用sayName方法,是没有其它办法可以访问变量name的。

看起来它和寄生构造函数很相似,都不能使用instanceof来判断创建的对象的类型,不过它们有以下两点是不同的。

1、不使用new操作符来调用构造函数
2、在方法体内不引用this

以上便是在JavaScript中创建对象7种常见方法,每个方法都有利有弊,最重要的还是在合适的场景中使用合适的方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaScript的OO思想(一) 的相关文章

  • setInterval() 如何影响性能?

    我们正在使用 Twitter Bootstrap 作为框架构建一个 Web 应用程序 但在显示 隐藏工具提示时遇到问题 除了尝试找到实际问题的解决方案之外 我还有一个关于我们同时使用的解决方法的问题 从性能角度来看 使用 setInterv
  • 动态添加jinja模板

    我有一个 jinja 模板 它是一组 div 标签内的唯一内容 div include temppage html div 当我按下按钮时 我想用其他内容替换标签之间的所有内容 我希望用另一个 jinja 模板 include realpa
  • Firebase 模拟器无法促进/运行新功能

    我有三个云功能 其中两个已部署到我的 firebase 项目中 其中一个是我刚刚添加的 我希望在部署之前在本地测试新的功能 但是当我尝试使用它时却无法使用 并且只有两个已部署的功能可用 Firebase 模拟器在端口上运行良好5001 像往
  • 如何使用 CSS 或 javascript 创建圆角

    复制 使用 CSS 创建圆角的最佳方法是什么 https stackoverflow com questions 7089 what is the best way to create rounded corners using css 7
  • 如何在没有数据库的情况下创建AJAX分页?

    是否可以在没有 MySQL 帮助的情况下获取 AJAX 分页页面 难道我不能只添加一个包含我需要显示的文本和标记的 PHP 文件 然后通过单击页码将该内容提供给用户吗 那么可以用纯 jQuery 和 PHP 来实现吗 您会使用什么代码方法来
  • Java:从 ScriptEngine javascript 返回一个对象

    我正在尝试使用 Java 来评估 javascript脚本引擎 https docs oracle com javase 7 docs api javax script ScriptEngine html班级 这是我正在尝试做的事情的一个简
  • Puppeteer 无法在 VPS (DigitalOcean) 上工作

    我在水滴中数字海洋 https www digitalocean com 我收到这个错误 node 5549 UnhandledPromiseRejectionWarning TimeoutError Navigation Timeout
  • 有效地获取下拉列表中的选定选项(XHTML Select 元素)

    背景 使用 XHTML Select 元素的下拉列表中有大量选项 数十个 我需要使用 JavaScript 检索所选选项 Problem 目前我正在使用 jQuery selectedCSS 选择器并且它按预期工作 但这种方法效率不高 因为
  • jQuery 和所有 .js 文件无法在本地运行,只能在外部运行

    我有一个奇怪的问题 我正在编写一个网站 包括 jQuery 和一些插件 它们存储在 js 文件夹中 当我尝试通过浏览器 jQuery 打开它时 插件和所有自定义脚本都不起作用 也许这与我的代码有关 但不这么认为 当然 当我在外部包含 jQu
  • 输入和文本区域可以拖动吗?

    MDN 规范以及我能通过 Google 找到的每个网站都说所有 HTML 元素都可以拖动 然而 在实践中 我发现我无法拖动文本输入或文本区域 即使它们已被禁用 例如 使用以下代码 img src http www placehold it
  • 字符串化 JavaScript 对象

    我正在寻找字符串化一个对象 我想要这样的输出 1 valeur dalebrun usager experttasp date 2013 08 20 16 41 50 2 valeur test usager experttasp date
  • Nuxt + Vuex - 如何将 Vuex 模块分解为单独的文件?

    在 Nuxt 文档中 here https nuxtjs org guide vuex store module files 它说 您可以选择将模块文件分解为单独的文件 state js actions js mutations js an
  • Chrome:window.print() 打印对话框仅在页面重新加载后打开 (javascript)

    我面临着一个非常奇怪的问题 我正在从 javascript 文件调用 window print 这在 Safari IE Firefox 中运行良好 直到两小时前 它在 Chrome 中也运行良好 版本29 0 1547 57 我没有更改我
  • Array.indexOf 如何比 Array.some 更高效

    这个问题的灵感来自于这个问题的竞争答案 具有多个参数的indexOf https stackoverflow com questions 39000151 indexof with multiple arguments 用户想知道一种有效的
  • 使用 jquery 时出现控制台错误 - Uncaught TypeError: Object # has no method

    我尝试使用以下 js 添加类或 css 样式 但出现控制台错误 var i 0 question i addClass show 收到以下控制台日志错误 Uncaught TypeError Object has no method add
  • 谷歌浏览器不显示一个网站的alert()弹出窗口

    我正在开发一个 javascript 循环 该循环会随着循环的进行而提醒每个键值 为了加快速度 我选中了 阻止此页面创建其他对话框 框 通常这只会抑制一个例程的弹出窗口 但它们还没有回来 在 Google Chrome 中 alert 消息
  • 关于 Node.js Promise then 和 return?

    我对承诺感到困惑 I use 那么就答应没有返回像这样 new Promise resolve reject gt resolve 1 then v1 gt console log v1 new Promise resolve reject
  • 为什么 JavaScript 默认导出不可用?

    为什么默认导出不像命名导出那样实时 lib js export let counter 0 export function incCounter counter export default counter main1 js import
  • 限制在三角形内

    我正在寻找一段通用代码 javascript 它可以与 jquery UI 一起使用来限制三角形内 div 的移动 拖动 与此类似 http stackoverflow com questions 8515900 how to constr
  • 具有值的 TextInput 不会更改值

    在我的react native应用程序中 我有一个API 我从中检索数据 并且我在输入值中设置数据 用户应该能够编辑这些输入并更新 但是当我尝试输入输入时它不会输入任何内容并且值保持原样 这是 TextInput 代码

随机推荐