• 创建对象(学习笔记) —— 《高级教程》


    创建单个对象的缺点:用同一个接口创建很多对象,会产生大量的重复代码。

    工厂模式就是为了解决这个问题。

    工厂模式

    解决了创建多个相似对象的问题

    function createPerson(name, age, job) {
        var o = new Object();
        o.name = name;
        o.age = age;
        o.job = job;
        o.sayName = function() {
            console.log(this.name)
        }
        return o;
    }
    
    var person1 = createPerson('Mike', 28, 'xxx');
    console.log(person1);
    person1.sayName();
    var person2 = createPerson('Mike', 24, 'aaa');
    console.log(person2);
    person2.sayName();

    缺点:无法解决对象识别的问题——怎样知道一个对象的类型

    构造函数模式

    ECMAScript中的构造函数可以用来创建特定类型的对象。

    function Person(name, age , job) {
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function() {
            console.log(this.name);
        }
    }
    
    var person1 = new Person('Mike', 28, 'teacher');
    console.log(person1);
    person1.sayName();
    var person2 = new Person('Danie', 24, 'doctor');
    console.log(person2);
    person2.sayName();

    与工厂模式的区别:

    • 没有显示的创建对象
    • 将属性和方法赋值给了this对象
    • 没有return语句

    构造函数本身也是函数,只不过可以用来创建对象

    用new操作符新建构造函数的实例,经历4个步骤:

    person1 和 person2 分别保存着 Person 的两个不同实例,都有一个 constructor (构造函数) 属性,指向 Person

    console.log(person1.constructor == Person); // true
    console.log(person2.constructor == Person); // true
    console.log(person1.constructor == Object); // false
    console.log(person2.constructor == Object); // false

    person1 和 person2 都是 Person 的实例,也是 Object 的实例,可以通过 instanceof 操作符来检验。

    console.log(person1 instanceof Person); // true
    console.log(person2 instanceof Person); // true
    console.log(person1 instanceof Object); // true
    console.log(person2 instanceof Object); // true

    缺点:每个构造函数中的方法都要在新的实例上创建一遍。

    console.log(person1.sayName == person2.sayName); // false

    将相同的方法移到外部:

    function sayName() { // 将相同的方法移到构造函数的外部
        console.log(this.name);
    }
    
    function Person(name, age , job) {
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = sayName;
    }
    
    var person1 = new Person('Mike', 28, 'teacher');
    console.log(person1);
    person1.sayName();
    var person2 = new Person('Danie', 24, 'doctor');
    console.log(person2);
    person2.sayName();
    // 此时不同实例上的方法就相等了
    console.log(person1.sayName == person2.sayName); // true

    缺点:需要在全局作用域定义很多函数,没有封装性可言

    原型模式

    好处: 所有对象实例可共享它所包含的属性和方法

    function Person() {
    
    }
    
    Person.prototype.name = 'Mike';
    Person.prototype.age = 28;
    Person.prototype.job = 'teacher';
    Person.prototype.sayName = function() {
        console.log(this.name);
    }
    
    var person1 = new Person();
    var person2 = new Person();
    person1.sayName();
    person2.sayName();
    
    console.log(person1.sayName == person2.sayName); // true

    下图以上面代码为例,展示了Person构造函数、Person的原型属性,及两个实例之间的关系。

     

    在实现中,无法访问 [[Prototype]],可以用 isPrototypeOf() 方法来确定对象之间是否有这种关系。

    console.log(Person.prototype.isPrototypeOf(person1)); // true
    console.log(Person.prototype.isPrototypeOf(person2)); // true

    通过 Object.getPrototypeOf() 方法,访问原型对象上的属性

    console.log(Object.getPrototypeOf(person1) == Person.prototype); // true
    console.log(Object.getPrototypeOf(person1).name); // Mike

    修改实例属性 

    // 原型模式 修改实例属性
    
    function Person() {
    
    }
    
    Person.prototype.name = 'Mike';
    Person.prototype.age = 28;
    Person.prototype.job = 'teacher';
    Person.prototype.sayName = function() {
        console.log(this.name);
    }
    
    var person1 = new Person();
    person1.name = 'Gray';
    person1.job = 'doctor';
    var person2 = new Person();
    person1.sayName(); // Gray
    person2.sayName(); // Mike
    
    console.log(person1); // Person {name: "Gray", job: "doctor", __proto__: Object}
    console.log(person2); // Person {__proto__: Object}

    修改实例属性为 null, 不会恢复指向原型的链接

    function Person() {
    
    }
    
    Person.prototype.name = 'Mike';
    Person.prototype.age = 28;
    Person.prototype.job = 'teacher';
    Person.prototype.sayName = function() {
        console.log(this.name);
    }
    
    var person1 = new Person();
    person1.name = 'Gray';
    var person2 = new Person();
    person1.sayName(); // Gray
    person2.sayName(); // Mike
    person1.name = null; // 不会恢复指向原型的链接
    person1.sayName(); // null

    删除实例属性 会重新恢复指向原型对象的链接

    // 原型模式 删除实例属性 会重新恢复指向原型对象的链接
    
    function Person() {
    
    }
    
    Person.prototype.name = 'Mike';
    Person.prototype.age = 28;
    Person.prototype.job = 'teacher';
    Person.prototype.sayName = function() {
        console.log(this.name);
    }
    
    var person1 = new Person();
    person1.name = 'Gray';
    var person2 = new Person();
    person1.sayName(); // Gray
    person2.sayName(); // Mike
    delete person1.name; // 会重新恢复指向原型对象的链接
    person1.sayName(); // Mike

    hasOwnProperty() 检测一个属性存在于实例中还是存在于原型中。

    // 原型模式 hasOwnProperty()
    
    function Person() {
    
    }
    
    Person.prototype.name = 'Mike';
    Person.prototype.age = 28;
    Person.prototype.job = 'teacher';
    Person.prototype.sayName = function() {
        console.log(this.name);
    }
    
    var person1 = new Person();
    console.log(person1.hasOwnProperty('name')); // false
    person1.name = 'Gray';
    console.log(person1.hasOwnProperty('name')); // true
    var person2 = new Person();
    console.log(person2.hasOwnProperty('name')); // false
    delete person1.name;
    console.log(person1.hasOwnProperty('name')); // false

    Object.keys() 获得对象上所有可枚举的实例属性

    // Object.keys()
    function Person() {
    
    }
    
    Person.prototype.name = 'Mike';
    Person.prototype.age = 28;
    Person.prototype.job = 'teacher';
    Person.prototype.sayName = function() {
        console.log(this.name);
    }
    console.log(Object.keys(Person.prototype)); // ["name", "age", "job", "sayName"]
    var person1 = new Person();
    console.log(Object.keys(person1)); // []
    person1.name = 'Gray';
    console.log(Object.keys(person1)); // ["name"]

    更简单的原型语法

    function Person() {
    
    }
    
    Person.prototype = { // prototype 的 constructor 属性不再指向 Person
        name: 'Mike',
        age: 28,
        job: 'Teacher',
        sayName: function() {
            console.log(this.name);
        }
    }
    
    var person1 = new Person();
    
    console.log(person1)

    以上方式,Person.prototype 的 constructor 属性将不再指向 Person。

    对比下面两张图:

    Person.prototype.name = 'Mike'; // 方式创建的

    constructor 会指向 Person

    Person.prototype = {}; // 对象字面量方式创建的

    constructor 不会指向 Person

    通过 instanceof 还能返回正确的结果,但是 constructor 已经不能确定对象的类型了。

    console.log(person1 instanceof Object); // true
    console.log(person1 instanceof Person); // true
    console.log(person1.constructor == Object); // true
    console.log(person1.constructor == Person); // false

    如果 constructor 很重要,可以显示指定

    // 显示指定 constructor
    function Person() {
    
    }
    
    Person.prototype = { // 通过指定 constructor属性,指向 Person
        constructor: Person, // 以这种方式重设,会使它的[[Enumerable]]特性被设置为true
        name: 'Mike',
        age: 28,
        job: 'Teacher',
        sayName: function() {
            console.log(this.name);
        }
    }
    
    var person1 = new Person();
    console.log(person1); // 打印出的结果见下图

    兼容 ECMAScript5的浏览器引擎

    Object.defineProperty()

    function Person() {
    
    }
    
    Person.prototype = {
        name: 'Mike',
        age: 28,
        job: 'Teacher',
        sayName: function() {
            console.log(this.name);
        }
    }
    
    Object.defineProperty(Person.prototype, 'constructor', {
        enumerable: false,
        value: Person
    });

    原型的动态性

    先创建实例,再在Person的原型对象上加方法,实例也可以调用。

    // 原型的动态性
    
    function Person() {
    
    }
    
    var person1 = new Person();
    
    Person.prototype.sayHi = function() {
        console.log('Hi');
    }
    
    person1.sayHi(); // Hi

    重写原型对象

    function Person() {
    
    }
    
    var person1 = new Person();
    
    Person.prototype = { // 通过指定 constructor属性,指向 Person
        constructor: Person,
        name: 'Mike',
        age: 28,
        job: 'Teacher',
        sayName: function() {
            console.log(this.name);
        }
    }
    console.log(person1);
    person1.sayName(); // person1.sayName is not a function

    可以看到,person1的原型对象上没有Person的新原型对象上的属性,因为他们是两个不同的对象。

    下图展示了重写原型之前和重写原型之后各个对象之间的关系。

    原生对象的原型

    所有原生引用类型(Object, Array, String等)都是在其构造函数的原型上定义了方法。

    console.log(typeof Array.prototype.sort); // "function"

    在原生对象的原型上添加方法

    // 注意 es6 中已经实现了该方法
    String.prototype.startsWith = function(text) {
        return this.indexOf(text) === 0;
    }
    
    console.log('Hello javascript'.startsWith('Hello')); // true

    不推荐在原生对象的原型上添加方法,可能会意外的重写原生方法。

    原型对象的问题

    缺点:

    • 省略了为构造函数传递初始化参数
    • 所有属性被实例共享——主要针对引用类型的属性

    看下面代码

    // 原型对象缺点
    function Person() {
    
    }
    
    Person.prototype = {
        constructor: Person,
        name: 'Mike',
        age: 28,
        job: 'Teacher',
        friends: ['Emily', 'David'],
        sayName: function() {
            console.log(this.name);
        }
    }
    
    var person1 = new Person();
    var person2 = new Person();
    
    person1.friends.push('Jennifer');
    
    console.log(person1.friends); // ["Emily", "David", "Jennifer"]
    console.log(person2.friends); // ["Emily", "David", "Jennifer"]
    console.log(person1.friends == person2.friends); // true

    person1 的friends 和 person2 的 friends 共享了,实际应用中,每个实例应该都有自己的 friends才对。

    所以,出现了以下这种模式。

    组合模式 (组合使用构造函数模式和原型模式)

    优点:

    • 构造函数定义实例属性,每个实例都会有一份自己的实例属性副本
    • 原型模式定义方法和共享属性,实例可共享方法,节省内存
    • 支持传参

    改写前面的例子:

    // 组合模式
    function Person(name, age, job) {
        this.name = name;
        this.age = age;
        this.job = job;
        this.friends = ['Emily', 'David'];
    }
    
    Person.prototype = {
        constructor: Person,
        sayName: function() {
            console.log(this.name);
        }
    }
    
    var person1 = new Person('Mike', 28, 'teacher');
    var person2 = new Person('Gray', 24, 'doctor');
    
    person1.friends.push('Jennifer');
    
    console.log(person1.friends); // ["Emily", "David", "Jennifer"]
    console.log(person2.friends); // ["Emily", "David"]
    console.log(person1.friends == person2.friends); // false
    person1.sayName(); // Mike
    person2.sayName(); // Gray

    现在修改任一实例的 friends 属性,都只会影响自身,不会影响到其他实例的。

    动态原型模式

    组合模式中,构造函数和原型是分离的。

    动态原型模式,将原型在构造函数初始化时,就在构造函数中初始化了。

    function Person(name, age, job) {
        this.name = name;
        this.age = age;
        this.job = job;
        if (typeof this.sayName != 'function') {
            Person.prototype.sayName = function() { // 可以立即在所有实例中体现
                console.log(this.name);
            }
        }
    }
    
    var person1 = new Person('Mike', 28, 'teacher');
    person1.sayName(); // Mike

    寄生构造函数模式

    先来看一段代码:

    function Person(name, age, job) {
        var obj = new Object(); // <-- 创建一个 obj
        obj.name = name;
        obj.age = age;
        obj.job = job;
        obj.sayName = function() {
            console.log(this.name);
        }
        return obj; // <-- return obj
    }
    
    var person1 = new Person('Mike', 28, 'teacher');
    person1.sayName(); // Mike

    看起来和构造函数没什么区别,就是将属性都绑定在内部新建的 obj 上,然后返回这个 obj。

    但是,请继续看:

    console.log(person1.constructor); // ƒ Object() { [native code] } 实例的构造函数是 Object 不是 Person
    console.log(person1 instanceof Person); // false <-- 实例

    构造函数中返回的对象与构造函数或构造函数的原型属性之间没有关系。不能依赖 instanceof 来确定对象的类型。

    建议:在可以使用其他模式时,不要用这种模式。

    稳妥构造函数模式

    稳妥对象(durable objects):没有公共属性;方法不引用 this。

    稳妥对象适合在一些安全环境(禁止使用 this 和 new)和防止数据被其他应用程序改动时使用。

    function Person(name, age, job) {
        var obj = new Object();
        obj.name = name;
        obj.age = age;
        obj.job = job;
        obj.sayName = function() {
            console.log(name); // <-- 方法不引用 this
        }
        return obj;
    }
    
    var person1 = Person('Mike', 28, 'teacher'); // 不使用 new 关键字
    person1.sayName(); // Mike

    其中 name 的值,只能通过 sayName 方法访问。

    注:以上所有的文字、代码都是本人一个字一个字敲上去的,图片也是一张一张画出来的,转载请注明出处,谢谢!

  • 相关阅读:
    js的一些总结
    模型矩阵, 视图矩阵, 投影矩阵
    深入理解Three.js(WebGL)贴图(纹理映射)和UV映射
    Cesium学习笔记(四)Camera
    Node.js 命令行程序开发教程 ---------http://www.ruanyifeng.com/blog/2015/05/command-line-with-node.html
    node 模块 fs-extra
    Object.keys() https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
    Object.create() __https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
    JavaScript停止冒泡和阻止浏览器默认行为
    js创建对象的最佳方式
  • 原文地址:https://www.cnblogs.com/lwl0812/p/9764936.html
Copyright © 2020-2023  润新知