目录
- 一、JSX 本质
- 二、前端三大框架
- 三、React 全家桶
- 四、前端为何需要状态管理库(Flux 或redux 或 mobox)
- 五、高阶组件
- 六、函数作为子组件
- 七、React Context API
- 八、Flexbox 和 iOS 原生布局
- 九、前端和iOS原生中的响应式框架
- 十、class 的本质
- 十一、class 表达式
- 十二、只能被继承,不能直接使用的类
- 十三、解构赋值与函数默认值结合使用
- 十四、尾调用优化
- 十五、React Native 和 Flutter
- 十六、JavaScriptCore 简介
- 持续更新中。。。。
- 持续更新中。。。。
- 持续更新中。。。。
- 持续更新中。。。。
- 持续更新中。。。。
一、JSX 本质
const element = Hello, world!
;
上述代码在 JS 代码中直接写 HTML(XML) 标记,即为 JSX。JSX 的本质是动态创建组件的语法糖。
所谓的语法糖是指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。
所谓的动态可以理解为借助 DOM API 动态创建组件,如 document.creatElement()
, 当然也可以使用 React 提供的相关 API 动态创建。所以实际开发中 JSX 不是必须使用的,但是建议使用。如下代码。
const element = (
Hello, world!
);
//借助 React 动态创建等价代码
const element = React.createElement(
'h1',//标签
{className: 'greeting'},//属性
'Hello, world!'//内容
);
JSX 本身是一个表达式,编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。也就是说可以将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX 等。
function getGreeting(user) {
if (user) {
return Hello, {formatName(user)}!
;
}
return Hello, Stranger.
;
}
JSX 的优点:
- 直观:声明式创建界面
- 灵活:代码动态创建界面
- 同其它前端框架相比无需学习新的模板语言
二、前端三大框架
参考
三大前端技术分别为:Angular、React、Vue
Angular:模板功能强大丰富,自带了极其丰富的angular指令。是一个比较完善的前端框架, 适用于大型项目。
Vue:构建数据驱动的Web界面的库,准确来说不是一个框架,它聚焦在V(view)视图层。
React: React 拥有较高的性能,代码逻辑非常简单。对模块化友好,但React 本身只是停留在视图层面,并不是一个完整的框架,所以如果是大型项目想要一套完整的框架的话,基本都需要加上
React Router
和Flux 或redux 或 mobox
才能写大型应用,所谓的 React 全家桶一般指React
、redux
、React Router
。
三、React 全家桶
所谓的 React 全家桶一般指 React
、Flux 或redux 或 mobox
、React Router
。
- Flux 或redux 或 mobox:主要做视图和数据的双向绑定。通过 Store 统一管理数据,所有组件数据更新都和 Store 直接通信,Store 类似中间者。
- React Router:主要用于实现路由,但是此路由不仅仅只是做页面切换,同时也能更好地组织代码(类似iOS原生的组件化)。
四、前端为何需要状态管理库(Flux 或redux 或 mobox)
在没有引入状态管理库时,组件间的通信需要一层一层传递下去。引入状态管理库后,STORE 相当去与一个中介者,所有的组件都会同 STORE 通信,从而减少了组件之间的通信。
五、高阶组件
高阶组件一般自身不涉及 UI 渲染,高阶组件的重心是实现的相关逻辑功能。高阶组件一般接受组件作为参数,然后返回新的组件。
//高阶组件实现代码
import React from "react";
export default function withTimer(WrappedComponent) {
return class extends React.Component {
state = { time: new Date() };
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
time: new Date()
});
}
render() {
return ;
}
};
}
使用的时候直接导出包装后组件 export default withTimer(ChatApp);
,同时该组件携带了高阶组件函数中的 time 属性
//高阶组件使用
import React from "react";
import withTimer from "../c06/withTimer";
export class ChatApp extends React.Component {
render() {
return (
{this.props.time.toLocaleString()}
//携带了高阶组件的time属性
);
}
}
//导出withTimer(ChatApp)
export default withTimer(ChatApp);
六、函数作为子组件
函数作为子组件的最大特点是一个组件如何 render 它的内容,很大一部分可以由使用它的人来决定。核心点在于this.props.children
class MyView extends React.Component{
render() {
return (
{this.props.children('red')}//这里也可以不给子节点传递参数
)
}
}
//父节点传递参数的写法
{(color)=>{
}}
//父节点没传参数的写法
//{}
七、React Context API
Context API 主要用于解决组件间通信问题,根节点提供数据,子节点使用数据。如下代码是一个切换中英文语言的 demo
Provider 相关代码
const enStrings = {
submit: "Submit",
cancel: "Cancel"
};
const cnStrings = {
submit: "提交",
cancel: "取消"
};
const LocaleContext = React.createContext(enStrings);
class LocaleProvider extends React.Component {
state = { locale: cnStrings };
toggleLocale = () => {
const locale =
this.state.locale === enStrings
? cnStrings
: enStrings;
this.setState({ locale });
};
render() {
return (
{this.props.children}
);
}
}
Consumer 相关代码
class LocaledButtons extends React.Component {
render() {
return (
{locale => (
)}
);
}
}
调用
export default () => (
);
八、Flexbox 和 iOS 原生布局
Flexbox 布局参考
虽然 Masonry 和 SnapKit 能够简化布局写法,但和前端的布局思路相比,Auto Layout 的布局思路还处在处理两个视图之间关系的初级阶段,而前端的 Flexbox 已经进化到处理一组堆栈视图关系的地步了。
苹果公司也意识到了这一点,于是借鉴 Flexbox 的思路创造了 UIStackView,来简化一组堆栈视图之间的关系。UIStackView 虽然在布局思路上,做到了和 Flexbox 对齐,但写法上还是不够直观。前端布局通过 HTML + CSS 组合,增强了界面布局的可读性。
SwiftUI 在写法上非常简洁,可读性也很强。除了支持简洁的链式调用外,它还通过 DSL 定制了 UIStackView 的语法。这套 DSL 的实现,使用的是 Function Builders 技术,可以让 DSL 得到编译器的支持。有了这样的能力,可以说苹果公司未来可能会诞生出更多编译器支持的特定领域 DSL。DSL 编写后的处理方式分为两种:
- 通过解析将其转化成语言本来的面目,SwiftUI 使用的就是这种方式;
- 在运行时解释执行 DSL。SQL 就是在运行时解释执行的 DSL。
九、前端和iOS原生中的响应式框架
响应式框架到底是什么,为什么在 iOS 原生开发中没被广泛采用,却能在前端领域得到推广呢?
响应式框架指的是能够支持响应式编程范式的框架。使用了响应式框架,你在编程时就可以使用数据流传播数据的变化,响应这个数据流的计算模型会自动计算出新的值,将新的值通过数据流传给下一个响应的计算模型,如此反复下去,直到没有响应者为止。iOS 中有 RAC、RXSwift,前端中有 React.js。
JavaScript 每次操作 DOM 都会全部重新渲染。React.js 框架中引入了Virtual DOM 概念,页面组件状态会和 Virtual DOM 绑定,用来和 DOM(文档对象模型)做映射与转换。当组件状态更新时,Virtual DOM 就会进行 Diff 计算,最终只将需要渲染的节点进行实际 DOM 的渲染,并不是每一次都去渲染,相比传统的 JS 渲染在性能上有了很大的提升。如下图是虚拟DOM渲染原理。可以看出,操作 Virtual DOM 时并不会直接进行 DOM 渲染,而是在完成了 Diff 计算得到所有实际变化的节点后才会进行一次 DOM 操作,然后整体渲染。而 DOM 只要有操作就会进行整体渲染,避免了重复的整体渲染。正式因为如此React.js 具有很好的性能。
为什么 ReactiveCocoa 在 iOS 原生开发中就没流行起来呢?前端在有了 Virtual DOM 之后,整体渲染性能得到很大的提升。这种性能问题并不存在于 iOS 原生开发,Cocoa Touch 框架的界面节点树结构要比 DOM 树简单得多,没有前端那样的历史包袱。Cocoa Touch 每次更新视图时不会立刻进行整个视图节点树的重新渲染,而是会通过 setNeedsLayout
方法先标记该视图需要重新布局,直到绘图循环到这个视图节点时才开始调用 layoutSubviews
方法进行重新布局,最后再渲染。RAC、RXSwift 这样的框架并没有为 iOS 的 App 带来更好的性能。当一个框架可有可无,而且没有明显收益时,一般团队是没有理由去使用的。
九、虚拟 DOM
虚拟DOM其实就是一个JavaScript对象。通过这个JavaScript对象来描述真实DOM。真实DOM的操作,一般都会对某块元素的整体重新渲染。采用虚拟DOM的话,当数据变化的时候,只需要局部刷新变化的位置就好了。
十、class 的本质
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
上面代码表明,类的数据类型就是函数,类本身就指向构造函数。使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。
class Point {
constructor() {
}
toString() {
}
toValue() {
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
类的所有方法都定义在类的prototype属性上面。
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
在类的实例上面调用方法,其实就是调用原型上的方法。
实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
十一、class 表达式
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
let inst = new MyClass();
inst.getClassName() // Me
Me.name
码使用表达式定义了一个类。需要注意的是,这个类的名字是Me,但是Me只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用MyClass引用。如果类的内部没用到的话,可以省略Me。const MyClass = class { /* ... */ };
十二、只能被继承,不能直接使用的类
class Shape {
constructor() {
//new.target 返回类名,如果是子类调用则返回子类名
if (new.target === Shape) {
throw new Error('本类不能实例化');
}
}
}
class Rectangle extends Shape {
constructor(length, width) {
super();
// ...
}
}
var x = new Shape(); // 报错
var y = new Rectangle(3, 4); // 正确
十三、解构赋值与函数默认值结合使用
请问下面两种写法有什么差别?
// 写法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。
// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]
// x 有值,y 无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
// x 和 y 都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]
十四、尾调用优化
参考
十五、React Native 和 Flutter
目前主流的跨端方案,主要分为两种:
- 将 JavaScriptCore 引擎当作虚拟机的方案,代表框架是 React Native;
- 使用非 JavaScriptCore 虚拟机的方案,代表框架是 Flutter。
React Native 使用 JavaScript 语言来开发,Flutter 使用的是 Dart 语言。这两门编程语言,对 iOS 开发者来说都有一定的再学习成本。
React Native 框架的优势:
- JavaScript 的历史和流行程度都远超 Dart ,生态也更加完善,开发者也远多于 Dart 程序员。
- 从页面框架和自动化工具的角度来看,React Native 也要领先于 Flutter。这,主要得益于 Web 技术这么多年的积累,其工具链非常完善。
- 热更新技术
- 包体积小。Flutter 的渲染引擎是自研的,并没有用到系统的渲染,所以 App 包必然会大些。但是,我觉得从长远来看,网速越来越快,App Store 对包大小的限制只会越来越小,所以说这个问题一定不会成为卡点。
Flutter 的优势:
- 首先在于其性能。JS 在浏览器中解释执行,且无法针对移动端进行优化。Flutter 却不一样。它一开始就抛弃了历史包袱,使用全新的 Dart 语言编写,同时支持 AOT(预先编译) 和 JIT(实时编译) 两种编译方式,而没有采用 HTML/CSS/JavaScript 组合方式开发,在执行效率上明显高于 JavaScriptCore 。
- 重写了 UI 框架,从 UI 控件到渲染,全部重新实现了,依赖 Skia 图形库和系统图形绘制相关的接口,保证了不同平台上能有相同的体验。
十六、JavaScriptCore 简介
JavaScriptCore 框架的框架名是 JavaScriptCore.framework。JavaScriptCore 为原生编程语言 Objective-C、Swift 提供调用 JavaScript 程序的动态能力,还能为 JavaScript 提供调用原生的能力。正是因为这种桥梁作用,出现了很多使用 JavaScriptCore 开发 App 的框架 ,如 React Native、Weex、小程序、WebView Hybird 等。
JavaScriptCore 原本是 WebKit 中用来解释执行 JavaScript 代码的核心引擎。从 iOS7 开始,苹果公司将其作为系统级的框架提供给开发者使用。苹果公司有 JavaScriptCore 引擎、谷歌有 V8 引擎、Mozilla 有 SpiderMonkey。
JavaScriptCore 框架主要由 JSVirtualMachine 、JSContext、JSValue 类组成。
- JSVirturalMachine 的作用是为 JS 代码的运行提供一个虚拟机环境。在同一时间内,JSVirtualMachine 只能执行一个线程。每个 JSVirtualMachine 都有自己的 GC(Garbage Collector,垃圾回收器),以便进行内存管理,所以多个 JSVirtualMachine 之间的对象无法传递。
- JSContext 是 JS 运行环境的上下文,负责原生和 JS 的数据传递。
-
JSValue 是 JS 的值对象,用来记录 JS 的原始值,并提供进行原生值对象转换的接口方法。
下图是 JavaScriptCore 和原生应用的交互。每个 JavaScriptCore 中的 JSVirtualMachine 对应着一个原生线程,同一个 JSVirtualMachine 中可以使用 JSValue 与原生线程通信,遵循的是 JSExport 协议。JSExport 协议的主要作用是将原生类方法和属性提供给 JavaScriptCore 使用,JavaScriptCore 可以将 JSValue 提供给原生线程使用。