JavaScript比较几种创建对象的方法的优缺点及案例

js创建对象方法

创建对象主要有字面量方式、工厂模式、构造函数模式、原型模式、组合模式

字面量方式

大括号创建对象

直接使用大括号{}创建一个对象

// 直接使用大括号创建变量
var obj = {
    name:"apple",
    age:18,
    sayhi(){  // 对象中函数的一种简写,等价于sayhi:function(){}
        console.log(this.name);
    }
}
console.log(obj.__proto__ === Object.prototype); // true   对象原型指向Object.prototype
console.log(obj.constructor === Object);    // true
obj.sayhi(); // apple

缺点:创建多个对象会造成冗余的代码

使用new创建空对象

new的作用:根据构造函数返回值,返回值为非对象或者没有返回值,创建一个新对象;返回值为对象,则创建这个对象。
该对象的原型也是Object.prototype,该对象的构造函数是Object

var obj2 = new Object();
obj2.name = "apple";
obj2.sayHello = function () {
    console.log("hello");
}
obj2.sayHello(); // hello

Object,crerate方法

使用该方法,可以根据一个实例对象生成另一个实例对象

var obj3 = Object.create(obj);
console.log(obj3);  // {}
console.log(obj3.__proto__ === obj);  // true

以传入的对象为原型,构造函数也和传入对象的构造函数一样。

工厂模式

工厂模式使用函数,在函数内创建Object对象,进行操作

function createPerson(name,age) { 
    var obj = new Object;
    obj.name = name;
    obj.age = age;
    obj.sayName = function () { 
        console.log(this.name);
    }
    return obj;
}
var p1 = createPerson("xiaoming",18);
var p2 = createPerson("xiaohua",22);
p1.sayName();  // xiaoming
console.log(p1 instanceof Object);  // true
console.log(p2 instanceof Object);  // true

优点:工厂模式能创建多个类似的对象,解决代码冗余
缺点:但是没有解决对象识别的问题,p1和p1对象都是Object对象,而不能识别为Person
每次新创建对象,重复创建方法,浪费内存

构造函数模式

对构造函数使用new方法创建对象。new方法是创建一个Person对象实例,并使用this指向Person对象实例,调用构造函数,初始化实例的属性和方法。
注意:构造函数不写return或者返回的不是一个对象,默认return this;否则p1和p2通过new获得的值是return返回的值

function Person(name,age) {
    this.name = name;
    this.age = age;
    this.sayName = function () { 
        console.log(this.name);
     }
//    return this;  默认
}
var p1 = new Person("xiaoming",18);
var p2 = new Person("xiaohua",22);
console.log(p1);
console.log(p1 instanceof Person); // true

优点:解决对象识别的问题,p1和p2都属于Person对象
缺点:每次新创建对象,都要创建一个新的sayName方法,而是不共用一个方法,浪费内存

构造函数拓展模式

将对象的方法定义在外面即可解决创建多个相同方法浪费内存的问题。

function Person(name,age) {
    this.name = name;
    this.age = age;
    this.sayName =sayName;
}
function sayName() { 
        console.log(this.name);
}
var p1 = new Person("xiaoming",18);
var p2 = new Person("xiaohua",22);
p1.sayName();  // xiaoming
console.log(p1.sayName === p2.sayName);  // true

优点:p1,p2所有实例共享一个方法,节约内存
缺点:因为对象方法定义在对象外部,污染全局作用域的变量

寄生构造模式

结合工厂模式和构造函数模式

// 寄生构造模式  平时不建议用
function Person(name,age) { 
    var obj = new Object;
    obj.name = name;
    obj.age = age;
    obj.sayName = function () { 
        console.log(this.name);
    }
    return obj;
}
var p1 = new Person("xiaoming",18);
var p2 = new Person("xiaohua",22);
console.log(p1.sayName === p2.sayName);  // false
console.log(p1.__proto__ === Person.prototype);  // false
console.log(p1 instanceof Person);  // false

这个操作的意思就是:构造函数内部先创建一个对象,对其进行初始化,返回出去;函数外部使用new,可以根据返回的对象创建一个对象,返回的对象为其原型(类似于p1 = Object.create(obj)操作)

稳妥构造函数模式

相当于闭包,函数内部的变量都属于对象的私有变量,外部不可以直接访问,必须通过对象内部的方法去访问

// 稳妥模式:没有公共属性,其他方法不引用this对象
function Person(name) { 
    var age = 18;
    var sex = "female";
    var o = new Object;
    o.sayName = function () { 
        console.log(name);
    };
    return o;
}
var p1 = Person('mm');
var p2 = Person('ll');
p1.sayName();
console.log(p1.sayName === p2.sayName);  // false

原型对象模式

将对象的方法和属性挂载到原型上

方法一

在原型上定义方法和属性,new出来的实例对象共享方法和属性。所以一旦原型上的方法和属性被修改时,所有实例上获取的这种方法和属性都会被修改

//  1.直接挂载到原型上
function Person() {  }
Person.prototype.name = "apple";
Person.prototype.age = 18;
Person.prototype.fav = ["eat","play"];
Person.prototype.showInfo = function () { 
    console.log(this.name+this.age);
}
var p1 = new Person;
var p2 = new Person;
p1.showInfo();  // apple18
console.log(p1.showInfo === p2.showInfo); // true

// p1 和 p2 都指向同一个原型,故原型上的属性和方法是共有的
p1.fav.push("sing");  // 修改原型上的属性
console.log(p1.fav);  // ["eat", "play", "sing"]
console.log(p2.fav);  // ["eat", "play", "sing"]

注意
对于基本类型的属性,实例上不可修改。实际上,从实例上获取原型对象的属性和方法,实例上都不可以修改。
对于像数字,字符串这种基本数据类型,从原型上获取的都是只读的。
对于像数组,对象这种引用数据类型,实例上不能修改,让其指向新对象或者数组,但是可以添加,删除对象、数组中的值。

// 基本类型的属性  
p1.name = "banana";  // 会在p1对象上创建name属性
console.log(p1.name);  // banana 这是对象上的属性
console.log(p2.name);  // apple  这是原型上的属性
// 获取对象自身的方法和属性
console.log(Object.getOwnPropertyNames(p1));  // ["name"]
console.log(Object.getOwnPropertyNames(p2));  //  []

相当于创建一个新name属性在p1上,而不是修改原型上的name属性。

方法二

直接修改原型对象

// 2.修改原型对象  
function Person() {  }
Person.prototype = {
    // constructor:Person,  // 也可以修改原型对象的构造函数指向,默认指向Person
    name:"xiaoming",
    sayName:function(){
        console.log(this.name);
    }
}
var p1 = new Person;
var p2 = new Person;
p1.sayName();
console.log(p1 instanceof Person);  // true

优点:节约内存,对象的方法和属性都是原型上的,创建对象时,不用创建,直接继承原型上的方法和属性
缺点:创建对象时,没有定制对象自身的方法和属性。

组合模式(重点)

通常创建对象使用构造函数模式和原型对象模式的组合模式,构造函数模式可以创建对象自身的属性,原型独享模式可以创建对象公共的方法。

// 组合模式  
function Person(name) { 
    this.name = name;
    this.friends = [];
}
Person.prototype.sayName = function () { 
    console.log(this.name);
}
var p1 = new Person("xiaoming");
var p2 = new Person("xiaohua");
p1.sayName();  // xiaoming
console.log(p1.sayName === p2.sayName);  // true
p1.friends.push("lubenwei");
console.log(p1.friends);  // ["lubenwei"]
console.log(p2.friends);  // []

这种创建模式,创建对象时,都有自身的属性,而且公共方法直接从原型上获取,节约内存。

动态原型模式

在原来基础上做一点小改进,让原型上的方法只有在创建对象时,才创建。

// 动态原型模式  
function Person(name) {
    this.name = name;
    this.friends = [];
    // 判断对象的方法是否存在,不存在则创建
    if (typeof this.sayName === "function") {  
        Person.prototype.sayName = function () {
            console.log(this.name);
        }
    }
}

案例:基于面对对象的选项卡样式实现

常规面对过程的写法

主要是获取元素,添加事件,修改类,这几步

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        ul{
            list-style: none;
        }
        a{
            color: #000;
            text-decoration: none;
        }

        #box{
            width: 600px;
            height: 400px;
            margin: 100px auto;
            background-color: #FF9966;
            border: 1px solid #000;
        }

        .topBar{
            overflow: hidden;
            text-align: center;
        }
        .topBar li{
            width: 198px;
            height: 50px;
            line-height: 50px;
            font-size: 22px;
            border: 1px solid #000;
            border-left: none;
            float: left;
        }
        .topBar li:last-child{
            border-right: none;
        }
        .topBar li:hover{
            background-color: red;
            color: #fff;
        }
        .topBar li.active{
            border-bottom: none;
        }
        .topBar li.active a{
            color:#fff;
        }
        .topBar li a{
            width: 100%;
            display: inline-block;
        }
      
        #content{
            width: 600px;
            height: 330px;
            margin-top:20px;
            overflow: hidden;
            text-align: center;
            line-height: 330px;
        }
        #content .item{
            width: 100%;
            height: 330px;
            display: none;
        }
        #content .item.active{
            display: block;
        }
    </style>
</head>
<body>
    <div id="box">
        <ul class="topBar">
            <li class="active"><a href="javascript:void(0);">电影</a></li>
            <li><a href="javascript:void(0);">音乐</a></li>
            <li><a href="javascript:void(0);">小说</a></li>
        </ul>
        <div id="content">
            <div class="item active">这是电影模块</div>
            <div class="item">这是音乐</div>
            <div class="item">这是小说</div>
        </div>
    </div>
    <script>
        // 获取元素  
        const topBar = document.getElementsByClassName("topBar")[0];
        const lis = topBar.children;
        const content = document.getElementById("content");
        const items = content.getElementsByClassName("item");
        console.log(lis,items);
        // 添加点击事件
        [...lis].forEach((element,index)=>{
            element.onclick = function () { 
                // 清除导航栏li标签active,添加目标点击的li类为active
                [...lis].forEach((el,i)=>{
                    if(index === i){
                        el.className = "active";
                    }else{
                        el.className = "";
                    }
                });
                //显示相应的模块  
                [...items].forEach((item,i) => {
                    if(index === i){
                        item.classList.add("active");  
                    }else{
                        item.classList.remove("active");
                    }
                });
                // 使用className或者classList都可以添加类
            }
        })

    </script>
</body>
</html>

使用className或者classList都可以添加类

面对对象写法

相当于将原来的代码进行封装,可以单独作为一个模块来使用

// 获取元素  
const topBar = document.getElementsByClassName("topBar")[0];
const content = document.getElementById("content");
// 使用面对对象形式封装
function tabSwitch(topBar, content) {
    this.topBar = topBar;
    this.content = content;
    this.lis = topBar.children;
    this.items = content.children;

    // 给每个元素添加点击事件的方法
    for (let i = 0; i < this.lis.length; i++) {
        // 这里使用箭头函数的原因是,让里面的this指向tabSwitch对象,而不是this.lis[i]每个导航按钮
        // 这样就可以调用对象原型上的方法
        this.lis[i].onclick = ()=>{ 
            console.log(this);
            this.clickHandler(i);
        }
    }
}
tabSwitch.prototype.clickHandler = function (index) {
    console.log(this);
    // 清除导航栏li标签active,添加目标点击的li类为active
    [...this.lis].forEach((el, i) => {
        if (index === i) {
            el.className = "active";
        } else {
            el.className = "";
        }
    });
    //显示相应的模块  
    [...this.items].forEach((item, i) => {
        if (index === i) {
            item.classList.add("active");
        } else {
            item.classList.remove("active");
        }
    });
}
const tab = new tabSwitch(topBar,content);
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值