2012-02-01 33 views
6

Trước khi tôi bị mắng vì đã thử cái gì đó liều lĩnh, hãy để tôi nói với bạn rằng tôi sẽ không làm điều này trong đời thực và đó là một câu hỏi học thuật.Tiếp tục từ một lỗi

Giả sử tôi đang viết thư viện và tôi muốn đối tượng của mình có thể tạo các phương thức khi cần.

Ví dụ, nếu bạn muốn gọi một phương thức .slice(), và tôi không có một sau đó xử lý window.onerror sẽ bắn nó cho tôi

Dù sao tôi đã chơi xung quanh với điều này here

window.onerror = function(e) { 
    var method = /'(.*)'$/.exec(e)[1]; 
    console.log(method); // slice 
    return Array.prototype[method].call(this, arguments); // not even almost gonna work 
}; 

var myLib = function(a, b, c) { 
    if (this == window) return new myLib(a, b, c); 
    this[1] = a; this[2] = b; this[3] = c; 
    return this; 
}; 

var obj = myLib(1,2,3); 

console.log(obj.slice(1)); 

Cũng (có lẽ tôi nên bắt đầu một câu hỏi mới) tôi có thể thay đổi constructor của tôi để có một số lượng không xác định của args?

var myLib = function(a, b, c) { 
    if (this == window) return new myLib.apply(/* what goes here? */, arguments); 
    this[1] = a; this[2] = b; this[3] = c; 
    return this; 
}; 

BTW Tôi biết tôi có thể tải đối tượng của tôi với

['slice', 'push', '...'].forEach(function() { myLib.prototype[this] = [][this]; }); 

Đó không phải những gì tôi đang tìm kiếm

+0

có vẻ như bạn đang suy nghĩ về javascript [ "pollyfills" hoặc "miếng chêm"] (http://remysharp.com/2010/10/08/what-is-a-polyfill/) –

+1

cờ nó thường thanh lịch và đáng tin cậy hơn để bắt ngoại lệ với một khối try-catch. –

Trả lời

4

Như bạn đã yêu cầu một câu hỏi học tập, tôi cho rằng khả năng tương thích trình duyệt không phải là một vấn đề. Nếu nó thực sự không, tôi muốn giới thiệu proxy hài hòa cho việc này. onerror không phải là một thực hành rất tốt vì nó chỉ là một sự kiện được nêu ra nếu ở đâu đó xảy ra lỗi. Nó nên, nếu bao giờ hết, chỉ được sử dụng như một phương sách cuối cùng. (Tôi biết bạn nói rằng bạn không sử dụng nó anyway, nhưng onerror chỉ là không rất thân thiện với nhà phát triển.)

Về cơ bản, proxy cho phép bạn chặn hầu hết các hoạt động cơ bản trong JavaScript - đáng chú ý nhất là bất kỳ tài sản nào hữu ích ở đây. Trong trường hợp này, bạn có thể chặn quy trình nhận .slice.

Lưu ý rằng proxy là "lỗ đen" theo mặc định. Chúng không tương ứng với bất kỳ đối tượng nào (ví dụ: thiết lập thuộc tính trên proxy chỉ cần gọi bẫy set (bộ chặn); lưu trữ thực tế bạn phải tự thực hiện). Nhưng có một "handler chuyển tiếp" có sẵn để định tuyến tất cả mọi thứ thông qua một đối tượng bình thường (hoặc một thể hiện của khóa học), để proxy hoạt động như một đối tượng bình thường. Bằng cách mở rộng trình xử lý (trong trường hợp này là phần get), bạn có thể dễ dàng định tuyến các phương thức Array.prototype thông qua như sau.

Vì vậy, bất cứ khi nào sở hữu bất kỳ (với tên name) đang được tải xuống, con đường mã như sau:

  1. Hãy thử trở lại inst[name].
  2. Nếu không, hãy thử trả về một hàm áp dụng Array.prototype[name] trên cá thể với các đối số đã cho cho hàm này.
  3. Nếu không, chỉ cần trả lại undefined.

Nếu bạn muốn chơi xung quanh với proxy, bạn có thể sử dụng phiên bản V8 gần đây, ví dụ trong phiên bản Chromium hàng đêm (đảm bảo chạy dưới dạng chrome --js-flags="--harmony"). Một lần nữa, proxy không có sẵn cho việc sử dụng "bình thường" vì chúng tương đối mới, thay đổi rất nhiều phần cơ bản của JavaScript và trên thực tế chưa được chỉ định chính thức (vẫn là bản nháp).

Đây là một sơ đồ đơn giản về cách nó diễn ra như thế nào (inst thực sự là proxy mà cá thể đã được đưa vào). Lưu ý rằng nó chỉ minh họa nhận được thuộc tính; tất cả các hoạt động khác chỉ đơn giản là được truyền bởi proxy vì trình xử lý chuyển tiếp chưa sửa đổi.

proxy diagram

Mã ủy quyền có thể là như sau:

function Test(a, b, c) { 
    this[0] = a; 
    this[1] = b; 
    this[2] = c; 

    this.length = 3; // needed for .slice to work 
} 

Test.prototype.foo = "bar"; 

Test = (function(old) { // replace function with another function 
         // that returns an interceptor proxy instead 
         // of the actual instance 
    return function() { 
    var bind = Function.prototype.bind, 
     slice = Array.prototype.slice, 

     args = slice.call(arguments), 

     // to pass all arguments along with a new call: 
     inst = new(bind.apply(old, [null].concat(args))), 
     //      ^is ignored because of `new` 
     //       which forces `this` 

     handler = new Proxy.Handler(inst); // create a forwarding handler 
              // for the instance 

    handler.get = function(receiver, name) { // overwrite `get` handler 
     if(name in inst) { // just return a property on the instance 
     return inst[name]; 
     } 

     if(name in Array.prototype) { // otherwise try returning a function 
            // that calls the appropriate method 
            // on the instance 
     return function() { 
      return Array.prototype[name].apply(inst, arguments); 
     }; 
     } 
    }; 

    return Proxy.create(handler, Test.prototype); 
    }; 
})(Test); 

var test = new Test(123, 456, 789), 
    sliced = test.slice(1); 

console.log(sliced);    // [456, 789] 
console.log("2" in test);   // true 
console.log("2" in sliced);  // false 
console.log(test instanceof Test); // true 
            // (due to second argument to Proxy.create) 
console.log(test.foo);    // "bar" 

Việc xử lý chuyển tiếp có sẵn tại the official harmony wiki.

Proxy.Handler = function(target) { 
    this.target = target; 
}; 

Proxy.Handler.prototype = { 
    // Object.getOwnPropertyDescriptor(proxy, name) -> pd | undefined 
    getOwnPropertyDescriptor: function(name) { 
    var desc = Object.getOwnPropertyDescriptor(this.target, name); 
    if (desc !== undefined) { desc.configurable = true; } 
    return desc; 
    }, 

    // Object.getPropertyDescriptor(proxy, name) -> pd | undefined 
    getPropertyDescriptor: function(name) { 
    var desc = Object.getPropertyDescriptor(this.target, name); 
    if (desc !== undefined) { desc.configurable = true; } 
    return desc; 
    }, 

    // Object.getOwnPropertyNames(proxy) -> [ string ] 
    getOwnPropertyNames: function() { 
    return Object.getOwnPropertyNames(this.target); 
    }, 

    // Object.getPropertyNames(proxy) -> [ string ] 
    getPropertyNames: function() { 
    return Object.getPropertyNames(this.target); 
    }, 

    // Object.defineProperty(proxy, name, pd) -> undefined 
    defineProperty: function(name, desc) { 
    return Object.defineProperty(this.target, name, desc); 
    }, 

    // delete proxy[name] -> boolean 
    delete: function(name) { return delete this.target[name]; }, 

    // Object.{freeze|seal|preventExtensions}(proxy) -> proxy 
    fix: function() { 
    // As long as target is not frozen, the proxy won't allow itself to be fixed 
    if (!Object.isFrozen(this.target)) { 
     return undefined; 
    } 
    var props = {}; 
    Object.getOwnPropertyNames(this.target).forEach(function(name) { 
     props[name] = Object.getOwnPropertyDescriptor(this.target, name); 
    }.bind(this)); 
    return props; 
    }, 

    // == derived traps == 

    // name in proxy -> boolean 
    has: function(name) { return name in this.target; }, 

    // ({}).hasOwnProperty.call(proxy, name) -> boolean 
    hasOwn: function(name) { return ({}).hasOwnProperty.call(this.target, name); }, 

    // proxy[name] -> any 
    get: function(receiver, name) { return this.target[name]; }, 

    // proxy[name] = value 
    set: function(receiver, name, value) { 
    this.target[name] = value; 
    return true; 
    }, 

    // for (var name in proxy) { ... } 
    enumerate: function() { 
    var result = []; 
    for (var name in this.target) { result.push(name); }; 
    return result; 
    }, 

    // Object.keys(proxy) -> [ string ] 
    keys: function() { return Object.keys(this.target); } 
};