JavaScript
中的做用域scope
和上下文 context
是这门语言的独到之处,每一个函数有不一样的变量上下文和做用域。这些概念是JavaScript
中一些强大的设计模式的后盾。在ES5规范里,咱们能够遵循一个原则——每一个function
内的上下文this
指向该function
的调用方。好比:html
var Module = { name: 'Jafeney', first: function() { console.log(this); // this对象指向调用该方法的Module对象 var second = (function() { console.log(this) // 因为变量提高,this对象指向Window对象 })() }, init: function() { this.first() } } Module.init()
可是,在ES6规范中,出现了一个逆天的箭头操做符 =>
,它能够替代原先ES5里function
的做用,快速声明函数。那么,在没有了function
关键字,箭头函数内部的上下文this
是怎样一种状况呢?前端
在阮一峰老师的《ECMAScript 6 入门》 中,对箭头函数的作了以下介绍:react
ES6 容许使用“箭头”=>
定义函数。git
var f = v => v; //上面的箭头函数等同于: var f = function(v) { return v; };
若是箭头函数不须要参数或须要多个参数,就使用一个圆括号表明参数部分es6
var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; };
若是箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,而且使用return
语句返回(重要)github
var sum = (num1, num2) => { return num1 + num2; }
因为大括号被解释为代码块,因此若是箭头函数直接返回一个对象,必须在对象外面加上括号(重要)面试
var getTempItem = id => ({ id: id, name: "Temp" });
箭头函数能够与变量解构结合使用设计模式
const full = ({ first, last }) => first + ' ' + last; // 等同于 function full(person) { return person.first + ' ' + person.last; }
箭头函数使得表达更加简洁前端工程师
const isEven = n => n % 2 == 0; const square = n => n * n;
上面代码只用了两行,就定义了两个简单的工具函数。若是不用箭头函数,可能就要占用多行,并且还不如如今这样写醒目。app
箭头函数的一个用处是简化回调函数
// 正常函数写法 [1,2,3].map(function (x) { return x * x; }); // 箭头函数写法 [1,2,3].map(x => x * x);
函数体内的this
对象,就是定义时所在的对象,而不是使用时所在的对象。
不能够看成构造函数,也就是说,不可使用new
命令,不然会抛出一个错误。
不可使用arguments
对象,该对象在函数体内不存在。若是要用,能够用Rest参数代替。
不可使用yield
命令,所以箭头函数不能用做Generator
函数。
this
指向固定化ES5规范中,this
对象的指向是可变的,可是在ES6的箭头函数中,它倒是固定的。
function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } var id = 21; foo.call({ id: 42 }); // 输出 id: 42
注意:上面代码中,
setTimeout
的参数是一个箭头函数,这个箭头函数的定义生效是在foo
函数生成时,而它的真正执行要等到100毫秒后。若是是普通函数,执行时this
应该指向全局对象window
,这时应该输出21。可是,箭头函数致使this老是指向函数定义生效时所在的对象(本例是{id: 42}
),因此输出的是42。
this
指向的固定化,并非由于箭头函数内部有绑定this
的机制,实际缘由是箭头函数根本没有本身的this
,致使内部的this
就是外层代码块的this
。正是由于它没有this
,因此也就不能用做构造函数。因此,箭头函数转成ES5的代码以下:
// ES6 function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } // ES5 function foo() { var _this = this; setTimeout(function () { console.log('id:', _this.id); }, 100); }
上面代码中,转换后的ES5版本清楚地说明了,箭头函数里面根本没有本身的
this
,而是引用外层的this
。
// 请问下面有几个this function foo() { return () => { return () => { return () => { console.log('id:', this.id); }; }; }; } var f = foo.call({id: 1}); var t1 = f.call({id: 2})()(); // 输出 id: 1 var t2 = f().call({id: 3})(); // 输出 id: 1 var t3 = f()().call({id: 4}); // 输出 id: 1
上面代码之中,其实只有一个
this
,就是函数foo的this
,因此t一、t二、t3都输出一样的结果。由于全部的内层函数都是箭头函数,都没有本身的this
,它们的this其实都是最外层foo函数的this。另外,因为箭头函数没有本身的this,因此也不能用call()
、apply()
、bind()
这些方法去改变this的指向。
// 请问下面代码执行输出什么 (function() { return [ (() => this.x).bind({ x: 'inner' })() ]; }).call({ x: 'outer' });
上面代码中,箭头函数没有本身的
this
,因此bind
方法无效,内部的this
指向外部的this
。因此上面的代码最终输出['outer']
。
::
箭头函数能够绑定this
对象,大大减小了显式绑定this对象的写法(call
、apply
、bind
)。可是,箭头函数并不适用于全部场合,因此ES7提出了“函数绑定”(function
bind
)运算符,用来取代call
、apply
、bind
调用。虽然该语法仍是ES7的一个提案,可是Babel转码器已经支持。
函数绑定运算符是并排的两个双冒号(::
),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,做为上下文环境(即this
对象),绑定到右边的函数上面。
foo::bar; // 等同于 bar.bind(foo); foo::bar(...arguments); // 等同于 bar.apply(foo, arguments); const hasOwnProperty = Object.prototype.hasOwnProperty; function hasOwn(obj, key) { return obj::hasOwnProperty(key); }
若是双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
var method = obj::obj.foo; // 等同于 var method = ::obj.foo; let log = ::console.log; // 等同于 var log = console.log.bind(console);
因为双冒号运算符返回的仍是原对象,所以能够采用链式写法。
// 例一 import { map, takeWhile, forEach } from "iterlib"; getPlayers() ::map(x => x.character()) ::takeWhile(x => x.strength > 100) ::forEach(x => console.log(x)); // 例二 let { find, html } = jake; document.querySelectorAll("div.myClass") ::find("p") ::html("hahaha");
this
目前React
的编写风格已经全面地启用了ES6和部分ES7规范,因此不少ES6的坑在React
里一个个浮现了。本篇重点介绍 this
,也是近期跌得最疼的一个。
this
仍是用具体的例子来解释吧,下面是我 Royal
项目里一个Table
组件(Royal
正在开发中,欢迎fork
贡献代码 ^_^)
import React, { Component } from 'react' import Checkbox from '../../FormControls/Checkbox/' import './style.less' class Table extends Component { constructor(props) { super(props) this.state = { dataSource: props.dataSource || [], columns: props.columns || [], wrapClass: props.wrapClass || null, wrapStyle: props.wrapStyle || null, style: props.style || null, className: props.className || null, } this.renderRow = props.renderRow || null } onSelectAll() { for (let ref in this.refs) { if (ref!=='selectAll') { this.refs[ref].setState({checked:true}) } } } offSelectAll() { for (let ref in this.refs) { if (ref!=='selectAll') { this.refs[ref].setState({checked:false}) } } } _renderHead() { return this.state.columns.map((item,i) => { return [<th>{i===0?<Checkbox ref="selectAll" onConfirm={()=>this.onSelectAll()} onCancel={()=>this.offSelectAll()} />:''}{item.title}</th>] }) } _renderBody() { let _renderRow = this.renderRow; return this.state.dataSource.map((item) => { return _renderRow && _renderRow(item) }) } render() { let state = this.state; return ( <div className={state.wrapClass} style={state.wrapStyle}> <table border="0" style={state.style} className={"ry-table " + (state.className && state.className : "")}> <thead> <tr>{this._renderHead()}</tr> </thead> <tbody> {this._renderBody()} </tbody> </table> </div> ) } } export default Table
Component
是React
内的一个基类,用于继承和建立React
自定义组件。ES6规范下的面向对象实现起来很是精简,class
关键字 能够快速建立一个类,而Component
类内的全部属性和方法都可以经过this
访问。换而言之,在Component
内的任意方法内,能够经过this.xxx
的方式调用该Component
的其余属性和方法。
接着分析上面的代码,寥寥几行实现的是对一个Table组件的封装,借鉴了ReactNative
组件的设计思路,经过外部传递dataSource
(数据源)、columns
(表格的表头项)、renderRow
(当行渲染的模板函数)来完成一个Table的构建,支持全选和取消全选的功能、容许外部传递className
和style
对象来修改样式。
从这个例子咱们能够发现:只要不采用
function
定义函数,Component
全部方法内部的this
对象始终指向该类自身。
this
仍是继续上面的例子,下面在一个作为Demo的container
里调用以前 的Table
。
import Table from '../../components/Views/Table/'
接着编写renderRow
函数并传递给Table组件
_renderRow(row) { // ------------ 注意:这里对callback函数的写法 ----------- let onEdit = (x)=> { console.log(x+x) }, onDelete = (x)=> { console.log(x*x) } // --------------------------------------------------- return ( <tr> <td><Checkbox ref={"item_" + row.key} />{row.key}</td> <td>{row.name}</td> <td>{row.age}</td> <td>{row.birthday}</td> <td>{row.job}</td> <td>{row.address}</td> <td> <Button type="primary" callback={()=>onEdit(row.key)} text="编辑" /> <Button type="secondary" callback={()=>onDelete(row.key)} text="删除" /> </td> </tr> ) } //... 省略一大堆代码 render() { let dataSource = [{ key: '1', name: '胡彦斌', age: 32, birthday: '2016-12-29', job: '前端工程师', address: '西湖区湖底公园1号' }, { key: '2', name: '胡彦祖', age: 42, birthday: '2016-12-29', job: '前端工程师', address: '西湖区湖底公园1号' }],columns = [{ title: '编号', dataIndex: 'key', key: 'key', },{ title: '姓名', dataIndex: 'name', key: 'name', }, { title: '年龄', dataIndex: 'age', key: 'age', }, { title: '生日', dataIndex: 'birthday', key: 'birthday', }, { title: '职务', dataIndex: 'job', key: 'job', },{ title: '住址', dataIndex: 'address', key: 'address', }, { title: '操做', dataIndex: 'operate', key: 'operate', }]; return ( <div> <Table dataSource={dataSource} columns={columns} renderRow={this._renderRow}/> </div> ); }
显示效果以下:
分析上面的代码,有几处容易出错的地方:
_renderRow
做为component
的方法来定义,而后在对应的render
函数内经过this
来调用。很重要的一点,这里this._renderRow
做为的是函数名方式传递。
_renderRow
内部Button
组件的callback
是按钮点击后触发的回调,也是一个函数,可是这个函数没有像上面同样放在component
的方法里定义,而是做为一个变量定义并经过匿名函数的方式传递给子组件:
let onEdit = (x)=> { console.log(x+x) } // ..... callback={()=>onEdit(row.key)}
这样就避开了使用this
时上下文变化的问题。这一点是很讲究的,若是沿用上面的写法很容易这样写:
onEdit(x) { console.log(x+x) } // ... callback={()=>this.onEdit(row.key)}
可是很遗憾,这样写this
传递到子组件后会变成undefined
,从而报错。
父组件如要调用子组件的方法,有两种方式:
第一种 经过匿名函数的方式
callback = {()=>this.modalShow()}
第二种 使用 bind
callback = {this.modalShow.bind(this)}
注意:若是要绑定的函数须要传参数,能够这么写:
xxx.bind(this,arg1,arg2...)
@欢迎关注个人 github
和 我的博客 -Jafeney