和taro一起做SPA 1.基础知识
这一章的主要目的是和大家一起复习一下taro涉及的基础知识,为后面的学习做好基础
1. 基础知识
1.1 this指针
this指针大概是javascript中最令初学者困惑的语法了,简单说,this指针就是指向函数或方法运行的上下文环境。既然叫上下文环境,肯定和运行的环境相关。
在浏览器环境下:
- 当this出现在函数调用中,指向的是他运行的上下文:window对象;
- 当this出现在对象方法调用时,则指向了他运行的上下文:对象本身;
让我们举个例子说明一下:
function fn(){
console.log(this);
}
var obj={
fn:fn
}
fn();
obj.fn();
通过运行代码,你会发现,fn返回的是window对象,而obj.fn返回的则是obj对象
为了加深理解,我们看一个有点迷惑的问题
<!DOCTYPE html>
<html>
<div id="root"></div>
</html>
<script type="text/javascript"/>
var e1=document.getElementById("root");
var getId=document.getElementById
var e2=getId("root");
</script>
这是一个常见的场景,每次使用document.getElementById方法很麻烦,所以重新定义一个函数 getId,指向document.getElementById方法.
但是你发现,在chrome浏览器中调用getId方法居然系统会抛一个异常:
test.htm:9 Uncaught TypeError: Illegal invocation
发生异常的原因就在于,getElementById内部实现使用了this指针指向document对象.但是,当你用自己定义的getId方法时,getElementById已经由对象调用变成了方法调用,this指针被指向了window对象.
既然this指针有这样的不确定性,那么,自然就可以想到如何根据需要变更他的指向。
变更this指针指向有两种方法,定义时绑定和运行时绑定。
- 定义时绑定 bind
- 运行时绑定 apply/call
还是让我们看个例子:
对于我们上个getId的例子,你只要这样调用就可以了
定义时通过bind方法绑定
const getId=document.getElementById.bind(document);
const e=getId("root");
运行时通过call方法绑定
const e=getId.call(document,"root")
运行时通过apply方法绑定
const e=getId.apply(document,["root"])
通过上面的例子也可以看到call和apply两者的区别是:apply绑定时,传入的参数为数组形式,而call绑定则是采用枚举的方式。所以如果getId方法需要使用apply方法时,必须将参数包装成数组的形式.
1.2 高阶函数
在javascript语言中,函数是一类成员,函数可以作为变量,也可以作为输入参数和返回参数.将函数作为输入参数或输出参数的函数称之为高阶函数.后面我将会带大家一起了解一下高阶函数.
1.2.1 闭包
让我们看一下第一个高阶函数,闭包.
闭包利用了javascript函数作用域内变量被引用不会消除的特性.闭包被应用的场景非常多.
让我们先看一个获取递增ID的例子,首先,让我们看一下传统的方法,传统的方法获取递增ID,你需要先做一个全局变量.
let globalID=0
function getID(){
return globalID++;
}
console.log(getID(),getID(),getID())
这种方法由于使用了全局变量,任何一个人都有可能不经意的修改globalID的值导致你的方法失效.
采用闭包的写法,你先创建一个crGetID方法通过闭包保存ID,并返回getID函数.然后通过getID方法获取ID
function crGetID(){
let id=0;
return function(){
return id++;
}
}
var getID=crGetID();
console.log(getID(),getID(),getID())
这样,没有人可以直接修改你的ID值.
1.2.2 currying
让我们再看一个闭包的应用:currying,currying解决的问题是把一个函数的多个参数转换为单参数函数.
举个例子,假设我们需要累计一个用户7天的数据:
function add(d1,d2,d3,d4,d5,d6,d7){
return d1+d2+d3+d4+d5+d6+d7
}
如果是30天,可能需要30个输入参数,如果不定天数呢?
采用currying则可以解决这个问题:
function curryAdd(){
let s=[];
return function(...arg){
if (arg.length==0){
return s.reduce(function(p,v){return p+v},0)
}else{
s.push(...arg)
console.log("s",s);
}
}
}
var ca=curryAdd();
ca(1);
ca(2);
ca(3);
ca(4);
console.log(ca());
通过将一个函数currying后,函数可以随时被调用,直到输入参数为空时才进行计算.
闭包的特性使之成为javascript中运用最广的特性.后续在代码中,我们还会继续看到大量的闭包用法.
1.3 es6语法
react 大量使用了es6的语法,如果你对javsascript的印象还停留在原始的印象里,你可能根本没法看懂react的代码。所以在这里我们简单对用到的es6语法做一些介绍,并尽可能以react实际使用作为学习的例子。详细的es6语法介绍,可以参考相关的技术文档。
1.3.1变量解析
- 数组变量解析
es6 支持对数组直接进行解析,举个例子,如果需要对变量x,y互换值,传统的做法是:
function(x,y){
var t;
t=x;
x=y;
y=t;
}
如果用数组解析,就容易多了:
let [x,y]=[y,x]
数组解析可以用到输入参数传递
function test ([x,y,z]){
return x+y+z;
}
console.log(test([1,2,3]))
上面的例子,打印出来的结果是6.
还可以用到一次返回多个参数:
function retMult(){
return [1,2,3]
}
let [x,y,z]=retMult();
console.log(x,y,z)
函数会打印出 1,2,3
- 对象变量解析
对象解析用的更广泛,让我们举个简单的例子:
let {x:x,y:y}={x:2,y:3}
console.log(x,y);
函数打印结果 2,3
还可以简写为:
let {x,y}={x:2,y:3}
console.log(x,y);
让我们看一下下面的例子,如果对象属性名称和变量名称可以更进一步进行简写:
let x=1;
let obj={x}; //相当于 obj={x:x}
console.log(obj.x);
和数组解析一样,对象解析大量应用在输入参数的传值上,让我们举一个redux的实际应用的例子(这个例子里,假设state对象仅包含一个value属性):
function reducer({value},{type,payLoader}){
switch(type){
case "calc":
return {value:value+payLoader}
default:
return {value}
}
}
这个例子里,使用了对象的解析,代码更加简洁和异动.如果不使用对象解析,你的代码是这样的:
function reducer(state,action){
switch(action.type){
case "calc":
return {value:state.value+action.payLoader}
default:
return state;
}
}
1.3.2箭头函数
箭头函数可以让代码更加简洁和直观,另外,由于箭头函数对this指针的特殊处理,因此,被大量的运用。
让我们还是以数组提供的map函数为例子说明:
let arr=[1,2,3,4];
let m=arr.map(v=>v*2);
使用箭头函数,即简洁又直观,对数组中的每个元素直接乘以2.
如果使用传统的方式,你需要这样写:
let n=arr.map(function(v){
return v*2;
})
console.log(n);
使用箭头函数需要注意以下几点:
- 如果是一个参数,可以省略(),如果多个参数或没有参数,则必须使用()
- 如果函数直接返回箭头后的表达式,可以不加{},否则,需要在箭头后使用{}
让我们再举个数组提供的reduce方法的实际例子:
var r=[1,2,3,4].reduce((p,n)=>p+n);
console.log(r);
这个例子通过reduce函数计算数字元素的累加和,reduce函数的输入参数是一个函数,函数的输入参数分别为累加之和p以及下一个元素n.
如果用传统的方法,你需要这样写:
var r=[1,2,3,4].reduce(function(p,n){
return p+n;
})
console.log(r);
1.3.3类
ES6提供了类,类实际上就是一个语法糖.让我们还是通过一个具体的例子来看一下类的实现:
class Counter extends Component{
constructor(props){
super(props)
}
sub(){
let value=this.state.value-1;
this.setState({value})
}
add(){
let value=this.state.value+1;
this.setState({value})
}
render(){
return (
<div>
<button onClick={this.add.bind(this)}>+</button>
<button onClick={this.desc.bind(this)}>-</button>
</div>
)
}
}
这个例子说明了ES6的类的用法.
首先,类的定义语法是:
class Name{
}
你的类可以继承自另一个类,在刚才的例子里我们的类Counter继承自React的Component类
class Counter extends Component{
}
你可以根据需要实现类的构建器,构建器可以通过super方法引用父类的构建器:
constructor(props){
super(props)
}
类的方法可以缩写为methodName(){}的形式
sub(){
let value=this.state.value-1;
this.setState({value})
}