【问题标题】:accessing prototype property without using "new" to instantiate function object访问原型属性而不使用“new”来实例化函数对象
【发布时间】:2014-08-25 21:57:59
【问题描述】:

在 Crockford 的“Good Parts”一书中,他提到要避免使用“new”,因为它是一种反模式,因为该语言是原型语言而不是经典语言。但是,根据他的建议,我无法使用原型属性。我尝试了几种不同的方法:

// 在下面的例子中,我们无法访问原型属性,因为当 parent 用一个可以访问原型属性的函数对象初始化时,当执行 parent 时,我们返回一个对象字面量,它没有有权访问该属性。我们在下面得到的错误是“TypeError: p.info is not a function”。发生这种情况是因为 p 未定义 info,因为 p 是对象文字。它是具有原型属性的父级。

var parent = function(name, age){
    var name = name || "";
    var age = age || "";
    var that = {};

    that.name = function(){
            return name;
    }

    that.age = function(){
        return age;
    }
    return that;
}

parent.prototype.info = function(){
    return "name: " + this.name() + " age: " + this.age();  
}

var p = parent("John",25);
console.log("name: " + p.name() + " age: " + p.age() + " info: " + p.info());

// 这里我们遇到了同样的问题:

var parent = function(name, age){
    var name = name || "";
    var age = age || "";

    return {
        name: function(){
            return name;
        },
        age: function(){
            return age;
        }
    }
}

parent.prototype.info = function(){
    return "name: " + this.name() + " age: " + this.age();  
}

var p = parent("John",25);
console.log("name: " + p.name() + " age: " + p.age() + " info: " + p.info());

// 这也不起作用,因为“p”将是未定义的,因为函数的返回值是未定义的。此外,“this”指的是全局对象,即浏览器中的窗口。

var parent = function(name, age){
    var name = name || "";
    var age = age || "";

    this.name = function(){
        return name;
    }
    this.age = function(){
        return age;
    }
}

parent.prototype.info = function(){
    return "name: " + this.name() + " age: " + this.age();  
}

var p = parent("John",25);
console.log("name: " + p.name() + " age: " + p.age() + " info: " + p.info());

// 但是使用“new”关键字来构造对象允许我们访问原型。这一定意味着 Parent 的返回值是一个函数,而不是一个常规对象。根据 Stoyan Stefanov 在他的“Javascript 模式”一书中的说法,当使用 new 关键字时,在幕后会创建一个空白对象,该对象继承自 Parent 的(函数)原型:Object.create(Person.prototype)。然后“this”的所有引用都附加到该对象并返回。

var Parent = function(name, age){
    var name = name || "";
    var age = age || "";

    this.name = function(){
        return name;
    }
    this.age = function(){
        return age;
    }
}

Parent.prototype.info = function(){
    return "name: " + this.name() + " age: " + this.age();  
}

var p = new Parent("John",25);
console.log("name: " + p.name() + " age: " + p.age() + " info: " + p.info());

// 不幸的是,我无法模拟这个。我收到错误:“TypeError:this.prototype 不是对象或空值”。显然,在使用时,“this”还不是父级。

var parent = function(name, age){
    var name = name || "";
    var age = age || "";
    var that = Object.create(this.prototype);

    that.name = function(){
        return name;
    }
    that.age = function(){
        return age;
    }

    return that;
}

parent.prototype.info = function(){
    return "name: " + this.name() + " age: " + this.age();  
}

var p = parent("John",25);
console.log("name: " + p.name() + " age: " + p.age() + " info: " + p.info())

所以当 Crockford 说避免使用“new”时,我们应该如何向原型添加属性?

【问题讨论】:

  • 你可以使用 Object.create() 或者更新的 setPrototypeOf(),或者只是 extend()。
  • 你误用了this
  • @dandavis Object.create() 是在 ES5 中引入的。当他鼓励避免“新”时,他的书就在那之前出现了。所以我想要一个不涉及 ES5 的解决方案,如果这样的解决方案存在的话。
  • @JohnMerlino:我认为道格在当时称它为 begat(),但它基本上是同一回事。大多数 Object.create polyfill 看起来像 Dougs 的旧对象制造商。javascript.crockford.com/prototypal.html
  • Pre-ES5 我认为你不能完全避免new,除了使用__proto__Object.create 是要走的路。

标签: javascript


【解决方案1】:

使用Object.create() 函数,您可以这样做:

// base, its prototype will be used to create a new parent
function Parent(name, age) {
    this.name = name || '';
    this.age = age || '';
};

// factory to create a new parent
function createParent(name, age) {
    var proto = Parent.prototype;
    var properties = {
        name: {writable: true, configurable: true, value: name || ''},
        age: {writable: true, configurable: true, value: age || ''}
    };
    var parent = Object.create(proto, properties);
    return parent;
}

// augment the Parent's prototype
Parent.prototype.info = function(){
    return "name: " + this.name + " age: " + this.age;  
}

// Create a new Parent
var p = createParent("John",25);

// Test
console.log("name: " + p.name + " age: " + p.age + " info: " + p.info());

您可以查看here 以获取有关如何使用Object.create() 函数的更多选项/变体。

您可以根据需要使用不同的方法实现类似的功能。一个简单的例子:

var parentBase = {};
var parent = function(name, age){
    var name = name || "";
    var age = age || "";
    var that = parentBase;

    that.name = function(){
            return name;
    }

    that.age = function(){
        return age;
    }
    return that;
}

parentBase.info = function(){
    return "name: " + this.name() + " age: " + this.age();  
}

var p = parent("John",25);
console.log("name: " + p.name() + " age: " + p.age() + " info: " + p.info());

您最好使用Object.create,因为它在 ECMAS 5 中并为您提供了更多的选项、灵活性和功能,您不必自己实现。 Object.create 实现了 Douglas Crockford 描述的模式。如果平台不支持它,那么您可以按照 Crockford 建议的here 进行自定义实现。

如上所述,您可以使用与Object.create 类似的Object.beget。你可以试试:

if (typeof Object.beget !== 'function') {

     Object.beget = function (o) {

         var F = function () {};

         F.prototype = o;

         return new F();
     };
}

// base, its prototype will be used to create a new parent
function Parent(name, age) {
    this.name = name || '';
    this.age = age || '';
};

// factory to create a new parent
function createParent(name, age) {
    var proto = Parent.prototype;
    var parent = Object.beget(proto);
    parent.name = name || '';
    parent.age = age || '';
    return parent;
}

// augment the Parent's prototype
Parent.prototype.info = function(){
    return "name: " + this.name + " age: " + this.age;  
}

// Create a new Parent
var p = createParent("John",25);

// Test
console.log("name: " + p.name + " age: " + p.age + " info: " + p.info());

【讨论】:

  • 我尝试了他的 Object.beget,但即使使用他的 Object.beget,仍然无法弄清楚如何将属性附加到原型。这会在 Firefox 中返回错误:jsfiddle.net/77o57b1p/1
  • 我编辑了我的答案以包含一个使用 Object.beget() 的工作示例。
  • @JohnMerlino Object.beget 接受一个原型对象并返回一个新创建的对象,其原型链以传入的对象开头(即,Object.beget(p) 返回一个对象obj,其中@987654335 @)。在您的小提琴中,您传递了parent 的返回值,但从parent("John",25) 返回的结果只是一个普通对象,与存储在parent.prototype 中的原型无关。您需要执行Object.beget(person.prototype) 并在从Object.beget 返回的对象上设置实例属性。
【解决方案2】:

试试(这个模式)

var parent = function(name, age){
    var name = name || "";
    var age = age || "";
    // add `info` as `var`
    var that = {}, info;

    // change method to function expression
    that.name = (function(){
            return name;
    }())
    // change method to function expression
    that.age = (function(){
        return age;
    }())
    return that;
// add `|| {}` , for accessing `parent.info`
} || {};

// add param `that`  to `parent.info` function
parent.info = function(that){
    return "name: " + that.name + " age: " + that.age;  
};

var p = parent("John",25); 
console.log("name: " + p.name + " age: " + p.age + " info: " + parent.info(p));

// var results = document.createElement("div");

// results.innerHTML = parent.info(p);

// document.body.appendChild(results);

jsfiddle http://jsfiddle.net/guest271314/v0tdtk80/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-12
    • 2011-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多