摘要:写在前面中的作用域和上下文是这门语言的独到之处,每个函数有不同的变量上下文和作用域。不可以当作构造函数,也就是说,不可以使用命令,否则会抛出一个错误。正是因为它没有,所以也就不能用作构造函数。
写在前面
JavaScript中的作用域scope 和上下文 context 是这门语言的独到之处,每个函数有不同的变量上下文和作用域。这些概念是JavaScript中一些强大的设计模式的后盾。在ES5规范里,我们可以遵循一个原则——每个function内的上下文this指向该function的调用方。比如:
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是怎样一种情况呢?
ES6 中的箭头函数在阮一峰老师的《ECMAScript 6 入门》 中,对箭头函数的做了如下介绍:
箭头函数的基本介绍ES6 允许使用“箭头”=> 定义函数。
var f = v => v; //上面的箭头函数等同于: var f = function(v) { return v; };
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; };
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回(重要)
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;
上面代码只用了两行,就定义了两个简单的工具函数。如果不用箭头函数,可能就要占用多行,而且还不如现在这样写醒目。
箭头函数的一个用处是简化回调函数
// 正常函数写法 [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,所以t1、t2、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");React 中的各种 this
目前React的编写风格已经全面地启用了ES6和部分ES7规范,所以很多ES6的坑在React里一个个浮现了。本篇重点介绍 this,也是近期跌得最疼的一个。
Component 方法内部的 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 [{i===0? ] }) } _renderBody() { let _renderRow = this.renderRow; return this.state.dataSource.map((item) => { return _renderRow && _renderRow(item) }) } render() { let state = this.state; return (this.onSelectAll()} onCancel={()=>this.offSelectAll()} />:""}{item.title} ) } } export default Table
{this._renderHead()} {this._renderBody()}
Component是React内的一个基类,用于继承和创建React自定义组件。ES6规范下的面向对象实现起来非常精简,class关键字 可以快速创建一个类,而Component类内的所有属性和方法均可以通过this访问。换而言之,在Component内的任意方法内,可以通过this.xxx的方式调用该Component的其他属性和方法。
接着分析上面的代码,寥寥几行实现的是对一个Table组件的封装,借鉴了ReactNative组件的设计思路,通过外部传递dataSource(数据源)、columns(表格的表头项)、renderRow(当行渲染的模板函数)来完成一个Table的构建,支持全选和取消全选的功能、允许外部传递className和style对象来修改样式。
container 调用 component 时传递的 this从这个例子我们可以发现:只要不采用function定义函数,Component所有方法内部的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 () } //... 省略一大堆代码 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 ( {row.key} {row.name} {row.age} {row.birthday} {row.job} {row.address} ); }
显示效果如下:
分析上面的代码,有几处容易出错的地方:
_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...)
《ECMAScript 6 入门》
@欢迎关注我的 github 和 个人博客 -Jafeney
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/87806.html
相关文章
React中setState几个现象---先知道再理解
摘要:原生事件中的在按钮原生事件中定义的和定时器效果一样,每次都会引起新的事件是合并的成一次的。原生事件事件生成计时器点击按钮,先执行原生事件,再执行事件,但是原生事件会触发两次,事件触发一次。 常规情况 在同一个方法中多次setState是会被合并的,并且对相同属性的设置只保留最后一次的设置; import React from react; export class Test exte...
【进阶1-1期】理解JavaScript 中的执行上下文和执行栈
摘要:首次运行代码时,会创建一个全局执行上下文并到当前的执行栈中。执行上下文的创建执行上下文分两个阶段创建创建阶段执行阶段创建阶段确定的值,也被称为。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第一期,本周的主题是调用堆栈,,今天是第一天 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进...
发表评论
0条评论
Magicer
男|高级讲师
TA的文章
阅读更多
多态
阅读 2569·2021-11-24 09:38
CSS实例详解:Flex布局
阅读 2601·2019-08-30 15:54
white-space、word-wrap和word-break的简单整理
阅读 914·2019-08-30 15:52
前端面试每日3+1——第118天
阅读 1908·2019-08-30 15:44
Flex布局入门
阅读 2711·2019-08-30 13:48
css选择器总结
阅读 767·2019-08-29 16:21
Sass 语法规则
阅读 995·2019-08-29 14:03
「CSS」DOM2 级样式
阅读 2210·2019-08-28 18:15
阅读需要支付1元查看支付并查看