2011-09-06 12 views
6

Hãy xem xét tôi có đoạn mã sauchức năng Bao bì và function.length

/*...*/ 
var _fun = fun; 
fun = function() { 
    /*...*/ 
    _fun.apply(this, arguments); 
} 

Tôi vừa bị mất dữ liệu .length trên _fun vì tôi đã cố gắng để quấn nó với một số logic chặn.

Sau đây không hoạt động

var f = function(a,b) { }; 
console.log(f.length); // 2 
f.length = 4; 
console.log(f.length); // 2 

Các annotated ES5.1 specification states rằng .length được định nghĩa như sau

Object.defineProperty(fun, "length", { 
    value: /*...*/, 
    writable: false, 
    configurable: false, 
    enumerable: false 
} 

Cho rằng logic bên trong fun đòi hỏi .length là chính xác, làm thế nào tôi có thể đánh chặn và ghi đè lên chức năng này mà không phá hủy dữ liệu .length?

Tôi có cảm giác mình sẽ cần sử dụng eval và số Function.prototype.toString tinh vi để xây dựng chuỗi mới có cùng số đối số. Tôi muốn tránh điều này.

+0

Bạn thực sự cần 'độ dài' để được đặt đúng không? Bạn có thể muốn xem https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind, nhiều thư viện cũng mô phỏng hành vi này. – Prusse

+0

@Prusse cách liên kết sẽ khắc phục sự cố của tôi? – Raynos

+0

chỉ là một gợi ý, không thể hiểu rõ mục tiêu của bạn, xin lỗi. – Prusse

Trả lời

3

Tôi biết bạn thích một số cách khác, nhưng tất cả những gì tôi có thể nghĩ là để hack cùng một thứ gì đó với hàm tạo Function. Lộn xộn, để nói rằng ít nhất, nhưng có vẻ như để làm việc:

var replaceFn = (function(){ 
    var args = 'abcdefghijklmnopqrstuvwxyz'.split(''); 
    return function replaceFn(oldFn, newFn) { 
     var argSig = args.slice(0, oldFn.length).join(','); 
     return Function(
      'argSig, newFn', 
      'return function(' 
       + argSig + 
      '){return newFn.apply(this, arguments)}' 
     )(argSig, newFn); 
    }; 
}()); 

// Usage: 
var _fun = fun; 

fun = replaceFn(fun, function() { 
    /* ... */ 
    _fun.apply(this, arguments); 
}); 
+0

lộn xộn, nhưng vẫn đẹp :) –

2

Giả mạo chiều dài chính xác và nhất quán là biên giới cuối cùng trong javascript và đó là khá nhiều sự bắt đầu và kết thúc của nó. Trong một ngôn ngữ mà bạn có thể giả chỉ là về mọi thứ, chiều dài vẫn còn hơi huyền diệu. ES6 sẽ cung cấp, và chúng tôi có thể giả mạo nó bây giờ đến mức độ lớn hơn và thấp hơn tùy thuộc vào động cơ và phiên bản bạn đang ở. Đối với tính tương thích web chung đó là một cách tắt. Proxies/noSuchMethod đã ở Mozilla một thời gian. Proxy và WeakMaps đã có thể sử dụng được trong V8 trong Chromium và và nút (yêu cầu cờ để bật) cung cấp công cụ bạn cần cho độ dài giả chính xác.

Cụ thể về "chiều dài": http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/

Giải pháp cuối cùng: http://wiki.ecmascript.org/doku.php?id=harmony:proxies + http://wiki.ecmascript.org/doku.php?id=harmony:weak_maps

+0

Bạn có thể giải thích cách proxy có thể giúp chiều dài giả. Và các bản đồ yếu kém có thể giúp gì? Ngoài ra bạn tham khảo cho "chiều dài" đề cập đến mảng không hoạt động – Raynos

+0

Với một proxy bạn có thể làm cho một getter catchall cho các chỉ mục và nhân rộng tất cả các hành vi cần thiết để phù hợp với những gì các tài sản chiều dài bản địa nào. WeakMaps là những gì bạn muốn sử dụng để cung cấp các trình truy cập cho các thuộc tính không tồn tại mà proxy đã bị xóa trước khi kiểm tra kiểu/undefined được thực hiện. Khi một cái gì đó để truy cập getter cho 401042 trên mảng giả của bạn, trước tiên bạn xác định rằng tài sản bằng cách sử dụng WeakMap và sau đó thay đổi giá trị của tài sản dài giả của bạn. Tất cả các thuộc tính độ dài riêng là các con trỏ nội bộ đến một cấu trúc dữ liệu thực tế nằm ngoài vùng JavaScripts và do đó có thể hiển thị hành vi "ma thuật". –

+0

Thay đổi độ dài trên các chức năng dường như ít hữu ích hơn Mảng nhưng nó vẫn được thấm nhuần một cách kỳ diệu, và trong trường hợp này nó chỉ hoạt động không thể đoán trước giữa các trình duyệt. Mozilla thay đổi kích thước và dường như không có gì khác tôi đã nhìn thấy. V8 trả lại số bạn chỉ định khi bạn đặt, nhưng không thực sự thay đổi giá trị và sẽ trả về số lượng thông số thực tế bất kể bạn làm gì. Chiều dài được coi là ánh xạ trực tiếp tới bộ nhớ giữ bất cứ thứ gì nó đại diện. –

2

tôi sử dụng các chức năng sau đây cho mục đích này; nó thực sự nhanh chóng cho các hàm có số tham số hợp lý, linh hoạt hơn câu trả lời được chấp nhận và hoạt động cho các hàm có hơn 26 tham số.

function fakeFunctionLength(fn, length) { 
    var fns = [ 
     function() { return fn.apply(this, arguments); }, 
     function (a) { return fn.apply(this, arguments); }, 
     function (a, b) { return fn.apply(this, arguments); }, 
     function (a, b, c) { return fn.apply(this, arguments); }, 
     function (a, b, c, d) { return fn.apply(this, arguments); }, 
     function (a, b, c, d, e) { return fn.apply(this, arguments); }, 
     function (a, b, c, d, e, f) { return fn.apply(this, arguments); } 
    ], argstring; 

    if (length < fns.length) { 
     return fns[length]; 
    } 

    argstring = ''; 
    while (--length) { 
     argstring += ',_' + length; 
    } 
    return new Function('fn', 
     'return function (_' + argstring + ') {' + 
      'return fn.apply(this, arguments);' + 
     '};')(fn); 
} 
0

Bạn chỉ cần đi xuống con đường eval/Function nếu bạn cần hỗ trợ chức năng với bất kỳ số lượng các tham số. Nếu bạn có thể thiết lập một giới hạn trên hợp lý (ví dụ của tôi là 5) sau đó bạn có thể làm như sau:

var wrapFunction = function(func, code, where){ 
    var f; 
    switch (where) { 
    case 'after': 
     f = function(t,a,r){ r = func.apply(t,a); code.apply(t,a); return r; } 
    break; 
    case 'around': 
     f = function(t,a){ return code.call(t,func,a); } 
    break; 
    default: 
    case 'before': 
     f = function(t,a){ code.apply(t,a); return func.apply(t,a); } 
    break; 
    } 
    switch (func.length) { 
    case 0: return function(){return f(this, arguments);}; break; 
    case 1: return function(a){return f(this, arguments);}; break; 
    case 2: return function(a,b){return f(this, arguments);}; break; 
    case 3: return function(a,b,c){return f(this, arguments);}; break; 
    case 4: return function(a,b,c,d){return f(this, arguments);}; break; 
    case 5: return function(a,b,c,d,e){return f(this, arguments);}; break; 
    default: 
     console.warn('Too many arguments to wrap successfully.'); 
    break; 
    } 
} 

Phương pháp này của gói mã cũng được mở rộng bằng cách tạo ra khác nhau where chuyển mạch. Tôi đã triển khai beforeafter vì chúng hữu ích nhất cho dự án của riêng tôi — và around chỉ vì nó nhắc tôi nhớ đến. Sử dụng thiết lập này, bạn cũng có thể tắt gói (var f) sang mã bên ngoài cho phép bạn phát triển plugin như hệ thống cho các từ khóa where, có nghĩa là bạn có thể dễ dàng mở rộng hoặc ghi đè những gì được hỗ trợ wrapFunction.Rõ ràng là bạn có thể thay đổi cách mã thực sự được bao bọc nhưng bạn thích, phím thực sự chỉ là sử dụng kỹ thuật tương tự với 999 và AdrianLang, mà không cần lo lắng về việc tạo chuỗi và chuyển đến new Function.