标签javascript下的文章

Jerry Bendy 发布于 08月29, 2016

6 种方法在 React 中绑定 javascript 的 this 关键字(ES6/ES7)

Javascript 中的 this 关键字对很多 JS 开发者来说都是令人疑惑、头痛东西,很多时候往往搞不清楚某个 this 究竟指的是谁,尤其是在多层回调嵌套的情况下,OH GOD!!

It’s trivial for some other code to rebind the context of the function you’re working with―using the new keyword and some of the methods that are built onto Functon.prototype. This introduces an entire class of confusing scenarios and often you’ll see callback driven code scattered with calls to .bind(this).

问题

因为 React 使用 this 关键字在内部引用组件上下文,也就带来了关于 this 的一些困惑。你很可以见过或写过下面这样的代码:

this.setState({ loading: true });

fetch('/').then(function loaded() {
  this.setState({ loading: false });
});

这段代码会返回一个 TypeError,因为 this.setState is not a function。这是因为在 promise 的回调中,函数执行的上下文已经改变,this 指向了错误的对象(这时 this 指回调函数本身)。下面我们来看下如何避免这种问题的发生。

解决方案

有一些可选的解决方案已经被使用了很多年,还有一些是仅适用于 React 的,甚至有些方案在浏览器环境下是不能使用的。我们先来看一下。

1. 为 this 创建别名

这个应该算是被使用最多的一种方法了吧。在函数的顶部为 this 创建一个别名,并在组件内部通过这个别名访问 this

var component = this;
component.setState({ loading: true });

fetch('/').then(function loaded() {
  component.setState({ loading: false });
});

这种方法很轻量,并且易于理解。使用一个有意义的别名可以很轻松的使用上下文 this

2. bind this

第二种方法是运行时在回调函数上绑定我们自己的上下文环境。

this.setState({ loading: true });

fetch('/').then(function loaded() {
  this.setState({ loading: false });
}.bind(this));

从 ES5 开始,所有 Javascript 函数都有一个bind 方法,可以允许在函数执行的时候重新为函数内部绑定一个 this。一旦函数绑定了上下文 this 就不可以被重写,也就意味着绑定的上下文环境总是正确的。

这种方式对于其它语言的开发者来说可能很难理解,尤其在有多层函数嵌套时使用 bind,这时候就只能凭借自己的理解和记忆来记住究竟哪个 this 指的哪个对象。

3. React 组件方法

React 可以使用 createClass 创建一个组件类对象,组件类对象里的方法会自动绑定到组件的上下文环境,所以你可以直接在这里使用 this。这样可以允许把回调直接写在组件里。

React.createClass({
  componentWillMount: function() {
    this.setState({ loading: true });

    fetch('/').then(this.loaded);
  },
  loaded: function loaded() {
    this.setState({ loading: false });
  }
});

如果你的组件内部不需要做太多工作的话,想必这会是一种非常优雅的方案。这能允许你使用命名的方法、可以使你的代码更加扁平化,并且忘记 this 上下文这回事。事实上,如果你尝试在组件方法中使用 .bind(this), React 将会警告你正在做一件不必要的工作。

bind(): You are binding a component method to the component. React does this for you automatically in a high-performance way, so you can safely remove this call.

很重要的是,这种方式并不适用于 ES6 类语法的创建的 React 组件。 如果你正在使用 ES6 的类语法创建 React 组件,可以尝试下后面的方法。

4. ES2015(ES6)箭头语法

ES2015 中新加入了箭头语法用来更方便的写函数表达式。箭头语法除了可以以更简短的方式写函数外,还有一些很重要的特性,例如箭头语法创建的函数体中没有 this 而总是使用外部的 this 对象。

this.setState({ loading: true });

fetch('/').then(() => {
  this.setState({ loading: false });
});

无论多少层的函数嵌套,箭头函数中总是使用正确的 this 上下文。

很不幸的是,这样我们将无法命名我们的函数。这会使调试变得更困难,调用栈中将会显示这是一个 (anonymous function) (匿名函数)。

如果你使用 Babel 转换 ES6 的代码到 ES5,你将会发现一些比较有趣的现象。

  • 有些情况下编译器会通过变量名推理出函数名
  • 编译器是通过为 this 创建别名的方法来保持上下文
const loaded = () => {
  this.setState({ loading: false });
};

// 将会被编译为

var _this = this;
var loaded = function loaded() {
  _this.setState({ loading: false });
};

5. ES2016 (ES7)的绑定语法

这是一个 ES7 的提案,ES2016(ES7)的绑定语法,使用两个冒号(::)作为新的操作符。绑定操作符要求左侧是一个值,并且右侧是需要处理的函数,使用这种语法相当于把双冒号左侧的数值绑定到右侧处理函数的 this 上下文中。

下面通过 map 来举个简单的例子:

function map(f) {
  var mapped = new Array(this.length);

  for(var i = 0; i < this.length; i++) {
    mapped[i] = f(this[i], i);  
  }

  return mapped;
}

不同于 lodash,我们不需要在参数中传递数据,这使 map 函数看起来更像是数据的一个成员方法。

[1, 2, 3]::map(x => x * 2)
// [2, 4, 6]

是不是曾经像下面这下写过?

[].map.call(someNodeList, myFn);
// or
Array.from(someNodeList).map(myFn);

ES7 绑定操作符允许你直接在类数组结构上使用 map 函数。

someNodeList::map(myFn);

我们也可以在 React 组件中使用这种语法:

this.setState({ loading: true });

fetch('/').then(this::() => {
  this.setState({ loading: false });
});

可能我会是第一个站出来承认这种语法会让人觉得恐惧的人。

了解这个操作符会觉得很有趣,虽然它不是专为这种使用场景而生的。它解决了很多由于.bind(this)产生的缺点(事实上 babel 最终还是会将它编译成 .bind(this)),在解决很多层的嵌套代码中 this 的问题时可以放心的使用这种方式。当然这可能会使用其它开发人员有些困惑。

React component context probably isn’t the future of the bind operator, but if you are interested take a look at some of the great projects where it’s being used to great effect (such as mori-ext).

6. 函数指定

有些函数可以在执行时手动为它指定上下文的 this,例如 map,它接受的最后一个参数将会作为回调函数内的 this

items.map(function(x) {
  return <a onClick={this.clicked}>x</a>;
}, this);

虽然这可以解决问题,但却不存在通用性。因为大部分的函数是不能接受重新指定的 this 的。

总结

上面我们说了一些上下文中使用正确的 this 的方法。如果担心性能问题,为 this 创建别名将会是最快的方法(由于箭头函数在编译后与创建别名相同,所以使用 ES6 的箭头函数也是很好的选择)。当然,也许直到你的界面有上万个组件也许都不会看到这种性能差别,也许到那时 this 的问题也不会成为真正的瓶颈。

If you’re more concerned about debugging, then use one of the options that allows you to write named functions, preferably component methods as they’ll handle some performance concerns for you too.

At Astral Dynamics, we’ve found a reasonable compromise between mostly using named component methods and arrow functions, but only when we write very short inline functions that won’t cause issues with stack traces. This allows us to write components that are clear to debug, without losing the terse nature of arrow functions when they really count.

Of course, this is mostly subjective and you might find that you prefer to baffle your colleagues with arrow functions and bind syntax. After all, who doesn’t love reading through a codebase to find this?

this.setState({ loading: false });

fetch('/')
  .then((loaded = this::() => {
    var component = this;
    return this::(() =>
      this::component.setState({ loaded: false });
    }).bind(React);
  }.bind(null)));

via sitepoint

阅读全文 »

Jerry Bendy 发布于 01月20, 2016

Javascript运算符中的一些小技巧

记下一些关于运算符的小技巧或容易出错的地方

%运算符

取余运算符运算结果的正负号由第一个运算子的正负号决定,比如:

-1 % 2 // -1
1 % -2 // 1

所以有时候对负数取余会出现错误,使用绝对值函数可以避免错误:

// 会出错
function isOdd(n) {
    return n % 2 === 1;
}
isOdd(-5) // false
isOdd(-4) // false

// 正确了
function isOdd(n) {
    return Math.abs(n % 2) === 1;
}
isOdd(-5) // true
isOdd(-4) // false

+运算符

+运算符与其他运算符不太一样,我们知道它可以用来连接字符串操作,是因为用+运算符的时候它通常会将其他类型的值转为字符串,但是除了它比如说-运算符等都会将其他类型的值转换为数值,像这样:

var now = new Date();
typeof (now + 1) // "string"
typeof (now - 1) // "number"

当运算子中出现对象的时候:

1 + [1,2]
// "11,2"
1 + {a:1}
// "1[object Object]"

则先调用该对象的valueOf方法。如果返回结果为原始类型的值,则转换为字符串;否则继续调用该对象的toString方法,然后转换为字符串。 但是:

{a:1} + 1
// 1
({a:1})+1
"[object Object]1"

这是为什么呢?此时{a:1}被当做了代码块处理,而这个代码块没有返回值,所以整个表达式就返回1了。但是放在了圆括号中的{a:1},因为js预期()中是一个值,所以它就又被当做对象处理了。

特殊表达式:

1. 空数组+空数组

先调用valueOf()返回空数组本身,再调用toString(),返回空字符串。

[] + []
// ""

2. 空数组+空对象

[]得到'',{}得到"[object Object]"

[] + {}
// "[object Object]"

3. 空对象+空数组

{}被视作代码块省略,+[]就是将[]转换为数值的意思了得到0.

{} + []
// 0

4. 空对象+空对象

同样{}被当做代码块省略了,+{}转数值得到NaN

{} + {}
// NaN

如果第一个空对象不被当做空代码块的话:

({}) + {}
// "[object Object][object Object]"

({} + {})
// "[object Object][object Object]"

console.log({} + {})
// "[object Object][object Object]"

var a = {} + {};
a
// "[object Object][object Object]"

此外,当+运算符作为数值运算符放在其他值前面的时候,可以用于将任何值转为数值,就像Number函数那样:

+true // 1
+[] // 0
+{} // NaN

!取反运算符

!取反运算符连续对同一个值进行取反运算等于将其转换为对应的布尔值,就像Boolean函数那样:

!!x

// 等同于

Boolean(x)

此外,如果我们想排除null这个对象,可以这样写:

if(!!x){
//do something!
}

这是因为:!!null 值是 false,其他的 object !!obj 值都是 true。

~否运算符

~运算符是根据值的二进制二进制形式进行运算的。

~ 3 // -4

它的运算原理就是根据数值的32位二进制整数形式运算,补码存储的原理如果是负数,需要将取反后的值减一再取反然后加上负号。 比较麻烦,但是我们可以记成一个值与它取反后的值相加等于-1.

~~2.9
// 2

两次否运算能够对小数取整,并且这是取整方法中最快的一种。

^异或运算符

两次异或运算交换两个数的值:

var a = 10;
var b = 99;

a^=b, b^=a, a^=b;

a // 99
b // 10

右移运算符

右移运算符>>

右移运算可以模拟2的整除运算:

5 >> 1
// 相当于 5 / 2 = 2

21 >> 2
// 相当于 21 / 4 = 5

21 >> 3
// 相当于 21 / 8 = 2

21 >> 4
// 相当于 21 / 16 = 1

左移运算符

左移运算符<<

左移0位可用于取整:

13.5 << 0
// 13

-13.5 << 0
// -13

左移运算可以将颜色的RGB值转为HEX值:

var color = {r: 186, g: 218, b: 85};

// RGB to HEX
var rgb2hex = function(r, g, b) {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).substr(1);
}

rgb2hex(color.r,color.g,color.b)
// "#bada55"

此外,void运算符的作用是用来执行一个表达式,然后返回undefined,而且它的运算符优先级也比较高void 4+7 实际上等同于 (void 4) +7。一般运算符是左结合的,但是=和三目运算符?:却是右结合的:

w = x = y = z;
q = a?b:c?d:e?f:g;
//相当于:
w = (x = (y = z));
q = a?b:(c?d:(e?f:g));

文章来源:http://www.cnblogs.com/skylar/p/4299235.html

阅读全文 »

Jerry Bendy 发布于 06月05, 2015

使用JavaScript检测浏览器支持哪种CSS动画完成事件

以前或许我们在做前端效果时都是在使用JS来,如JQuery的animate,而如今在前端效果中,CSS3占据越来越重要的作用,如何检测一个CSS3的动画是否结束并在结束后执行下一个动画呢?

用jQuery的时候可能是这样:

$('.element').animate({left: '100px'}, function(){
    alert('动画执行结束');
});

下面以一个简单的例子演示下使用CSS3的动画如何响应这种操作:

/* 一个简单的CSS3动画(这里不再写 -webkit- 之类的前缀了 */
@keyframes fade{
    from {left: 0;}
    to {left: 200px;}
}

.animate-fade{
    animation: fade 2s ease both;
}

使用 jQuery 操作(假设浏览器是webkit内核)

$('.element').addClass('animate-fade').on('webkitAnimationEnd', function(){
    alert('动画执行结束');
});

好吧,其实上面代码中的webkitAnimationEnd部分才是本文要说的重点。因为目前不同的浏览器对HTML5和CSS3的支持不尽相同,很多CSS3样式不同内核的浏览器也有不同的前缀,同样,不同浏览器也支持不同的动画结束事件。

下面的代码演示了如何判断用户的浏览器支持哪一种动画结束事件,并返回。其基本原理是创建一个简单的DOM对象,并判断事件是否在DOM对象内存在。

// 判断animationEnd事件
function detectAnimationEndEvents(){
    var t;
    var el = document.createElement('fakeelement');
    var animEndEventNames = {
      'WebkitAnimation' : 'webkitAnimationEnd',
      'OAnimation' : 'oAnimationEnd',
      'msAnimation' : 'MSAnimationEnd',
      'animation' : 'animationend'
    };

    for(t in animEndEventNames){
        if( el.style[t] !== undefined ){
            return animEndEventNames[t];
        }
    }
}

var animEndEventName = detectAnimationEndEvents();

// 判断transitionEnd事件
function detectTransitionEvents(){
    var t;
    var el = document.createElement('fakeelement');
    var transitions = {
      'transition':'transitionend',
      'OTransition':'oTransitionEnd',
      'MozTransition':'transitionend',
      'WebkitTransition':'webkitTransitionEnd'
    }

    for(t in transitions){
        if( el.style[t] !== undefined ){
            return transitions[t];
        }
    }
}

var transitionEventName = detectTransitionEvents();

以上代码返回支持的动画结束事件的名称,直接用on就OK啦

阅读全文 »