github的地址 歡迎star
前言
最近由於在看一些react 優化的方面,注意到了之前沒有注意到的東西,都知道在react DOM事件的繫結方式有幾種方式:
-
在constructor中進行繫結。
-
或者用箭頭函式(無this)。
-
對於需要動態傳參的形式:
-
可以使用閉包的方式
-
或者可以直接把處理函式傳入子元件,子組建時可以拿到引數,再執行父元件的處理函式就可以了
-
class App extends Component {
removeCharacter = index => () => {
const {list} = this.state;
list.splice(index, 1);
this.setState({
list
})
}
render() {
return (
<div>
{
this.state.list.map((value, index) =>(
<div
onClick={this.removeCharacter(index)}
key={value.id}
data={value}
>
點選我
</div>
))
}
</div>
)
}
}
// 子元件處理的方式
class OtherApp extends Component {
removeCharacter = index => {
const {list} = this.state;
list.splice(index, 1);
this.setState({
list
})
}
render() {
return (
<div>
{
this.state.list.map((value, index) =>(
<Child
onClick={this.removeCharacter}
index={index}
key={value.id}
data={value}
/>
))
}
</div>
)
}
}
class Child extends Component {
handleClick = () => {
const { index, onClick} = this.props;
onClick(index)
}
render() {
return (
<div onClick={this.handleClick}>
{this.props.data}
</div>
)
}
}
複製程式碼
重點介紹了需要傳參的方式,對於效能比較是要看具體的環境(和瀏覽器,平臺的優化都有關係),沒有什麼是絕對效能好的。
看到了這篇部落格的結論,就想具體實際比較一下:
如果每次都在 render 裡面的 jsx 去 bind 這個方法,會消耗效能,因為每次bind都會返回一個新函式,重複建立靜態函式肯定是不合適的(閉包也是這樣,但bind內部有一系列的演算法,比閉包複雜多了)
複製程式碼
對於react中使用bind和閉包傳參效能的比較
Chrome Version 72.0.3626.119 (Official Build) (64-bit)
react version "16.8.3"
node version v10.15.1
通過chrome的JavaScript Profiler進行效能分析,發現渲染1千次事件的渲染時間是差不多的(均採用首次重新整理渲染)
可以看到效能上雙方相差不多,而Stack Overflow上的 stackoverflow.com/questions/1… 以及在chrome中各個版本bind和closure以及proxy效能測試(點選)發現bind是要比closure慢的多, 對於bind的實現如下:
Function.prototype.bind = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
複製程式碼
發現內部的實現也是閉包!當然這個不是完整的處理,從實現上bind是對閉包的封裝,可讀性來說bind好,因此bind是要比closure慢的,但是 v8做了優化,導致在react中差異也不是很大。
v8裡面的實現(來自stackoverflow.com/questions/1…),還沒有檢視最新的實現。
function FunctionBind(this_arg) { // Length is 1.
if (!IS_SPEC_FUNCTION(this)) {
throw new $TypeError('Bind must be called on a function');
}
var boundFunction = function () {
// Poison .arguments and .caller, but is otherwise not detectable.
"use strict";
// This function must not use any object literals (Object, Array, RegExp),
// since the literals-array is being used to store the bound data.
if (%_IsConstructCall()) {
return %NewObjectFromBound(boundFunction);
}
var bindings = %BoundFunctionGetBindings(boundFunction);
var argc = %_ArgumentsLength();
if (argc == 0) {
return %Apply(bindings[0], bindings[1], bindings, 2, bindings.length - 2);
}
if (bindings.length === 2) {
return %Apply(bindings[0], bindings[1], arguments, 0, argc);
}
var bound_argc = bindings.length - 2;
var argv = new InternalArray(bound_argc + argc);
for (var i = 0; i < bound_argc; i++) {
argv[i] = bindings[i + 2];
}
for (var j = 0; j < argc; j++) {
argv[i++] = %_Arguments(j);
}
return %Apply(bindings[0], bindings[1], argv, 0, bound_argc + argc);
};
%FunctionRemovePrototype(boundFunction);
var new_length = 0;
if (%_ClassOf(this) == "Function") {
// Function or FunctionProxy.
var old_length = this.length;
// FunctionProxies might provide a non-UInt32 value. If so, ignore it.
if ((typeof old_length === "number") &&
((old_length >>> 0) === old_length)) {
var argc = %_ArgumentsLength();
if (argc > 0) argc--; // Don't count the thisArg as parameter.
new_length = old_length - argc;
if (new_length < 0) new_length = 0;
}
}
// This runtime function finds any remaining arguments on the stack,
// so we don't pass the arguments object.
var result = %FunctionBindArguments(boundFunction, this,
this_arg, new_length);
// We already have caller and arguments properties on functions,
// which are non-configurable. It therefore makes no sence to
// try to redefine these as defined by the spec. The spec says
// that bind should make these throw a TypeError if get or set
// is called and make them non-enumerable and non-configurable.
// To be consistent with our normal functions we leave this as it is.
// TODO(lrn): Do set these to be thrower.
return result;
複製程式碼
我們在bind的實現方法裡面可以看到一些額外的開銷,如'%_IsConstructCall()',這些額外的東西是為了實現bind的規範,這也造成了在大多數情況下bind比簡單的閉包慢的情況。 另一方面,bind方法也有稍微的不同,使用Function.prototype.bind創造的函式沒有原型屬性或者是[[Code]], [[FormalParameters]], 和 [[Scope]] 內部屬性。
總之,在bind和closure不成為效能瓶頸的時候,優先考慮可讀性,儘量保證程式碼的簡潔
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!