Giter VIP home page Giter VIP logo

blog's Introduction

blog's People

Contributors

eddieeddieeddiejones avatar slashhuang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

webpack编译流程漫谈

webpack编译流程漫谈

前言

weback在web构建工具的激烈竞争中逐渐脱引而出。 无论是编译速度、报错提示、可扩展性等都给前端开发者耳目一新的感觉。本篇文章是个人对webpack的一点小研究总结。

webpack在开发者社区的反馈

类似gulp把自己定位为stream building tools一样,webpack把自己定位为module building system。
在webpack看来,所以的文件都是模块,只是处理的方式依赖不同的工具而已。

webpack同时也把node的IO和module system发挥的淋漓尽致。 webpack在配合babel(ES6/7)tsc(typescript)等类似DSL语言预编译工具的时候,驾轻就熟,为开发者带来了几乎完美的体验。

webpack整体架构(以webpack.config主要部分进行划分)

  1. entry: 定义整个编译过程的起点
  2. output: 定义整个编译过程的终点
  3. module: 定义模块module的处理方式
  4. plugin 对编译完成后的内容进行二度加工
  5. resolve.alias 定义模块的别名

webpack的核心module

无论你是jsx,tsx,html,css,scss,less,png文件,webpack一视同仁为module。并且每个文件[module]都会经过相同的编译工序 loader==> plugin。

关于以上这点,以如下一个简单的webpack.config文件为例。看下webpack会做什么

module.exports =  {
        watch: true,
        entry: './index.js',
        devtool: 'source-map',
        output: {
            path: path.resolve(process.cwd(),'dist/'),
            filename: '[name].js'
        },
        resolve: {
            alias:{ jquery: 'src/lib/jquery.js', }
        },
        plugins: [
            new webpack.ProvidePlugin({
                $: 'jquery',
                _: 'underscore',
                React: 'react'
            }),
            new WebpackNotifierPlugin()
        ],
        module: {
            loaders: [{
                test: /\.js[x]?$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            },  {
                test: /\.less$/,
                loaders:['style-loader', 'css-loader','less-loader']
            }, {
                test: /\.(png|jpg|gif|woff|woff2|ttf|eot|svg|swf)$/,
                loader: "file-loader?name=[name]_[sha512:hash:base64:7].[ext]"
            }, {
                test: /\.html/,
                loader: "html-loader?" + JSON.stringify({minimize: false })
            } ]
        }
    };

webpack是如何处理如上webpack.config文件解析

1. 确定webpack编译上下文context

默认情况下就是node启动的工作目录process.cwd(),当然也可以在配置中手动指定context。

webpack在确定webpack.config中entry的路径依赖时,会根据这个context确定每个要编译的文件(assets)的绝对路径。

2.entry和output 确定webpack的编译起点和终点

顾名思义,entry定义webpack编译起点,入口模块。 对应的结果为compolation.assets

output定义webpack编译的终点,导出目录

3. module.loaders 和 module.test 确定模块预编译处理方式

以babel为例,当webpack发现模块名称匹配test中的正则/js[x]?的时候。

它会将当前模块作为参数传入babel函数处理,babel([当前模块资源的引用])

函数执行的结果将会缓存在webpack的compilation对象上,并分配唯一id

以上的这一步,非常非常关键。唯一的id值决定了webpack在最后的编译结果中,是否会存在重复代码。
而缓存在compilation对象上,则决定了webpack可以在plugin阶段直接拿取模块资源进行二度加工。

4. plugin阶段贯穿于webpack的整个编译流程,一般用来做一些优化操作。

比如webpack.ProvidePlugin,它会在对编译结果再加工的操作过程中进行自定义的变量注入,当模块中碰到比如_这个变量的时候,webpack将从缓存的module中取出underscore模块加载进引用_的文件(compilation.assets)。
比如WebpackNotifierPlugin,它会在编译结果ready的时通知开发者,output已经就绪。

5.resolve.alias的作用就是对module模块提供别名,并没有什么特殊的。

【副作用】 webpack编译过程中的电脑卡慢?

在weback经历以上流程的时候,查看你的内存,你会发现,内存飙升!!!

这一般都是loader阶段,对DSL进行AST抽象语法树分析的时候,由于大量应用递归,内存溢出的情
况也是非常常见。

output目录不是一个渐进的编译目录,只有在最后compilation结果ready的时候,才会写入,造成开发者等待的时候,output目录始终为空。

【webpack编译对象compilation】 webpack将编译结果导出到output是怎么做到的

如上,webpack在plugin结束前,将会在内存中生成一个compilation对象文件模块tree

这个阶段是webpack的done阶段 : webpack写入output目录的分割点。

这棵树的枝叶节点就是所有的module[由import或者require为标志,并配备唯一moduleId],

这棵树的主枝干就是所有的assets,也就是我们最后需要写入到output.path文件夹里的文件内容。

最后,这个compilation对象也是所有webpackPlugin的处理的时候的arguments。

总结

对于开发者来说,整体而言webpack的编译过程细节比较多,但是大体的框架还是比较直观。

里面涉及到的类似DSL,AST的概念及模块缓存等等,在构建工具中还是比较常见的,配合watch模式,debug模式,对于开发者来说实在是一大利器。

一切文件皆为模块也和react的一切dom都可以变为JS一样,对前端世界带来了新的开发理念。

webpack官方文档

ssh运维及node相关知识列表

ssh config服务器远程登录
http://nerderati.com/2011/03/17/simplify-your-life-with-an-ssh-config-file/

linux后台daemon和前端运行指令
https://linuxconfig.org/understanding-foreground-and-background-linux-processes
https://superuser.com/questions/268230/how-can-i-resume-a-stopped-job-in-linux

chrome来debug node应用
https://blog.hospodarets.com/nodejs-debugging-in-chrome-devtools

MicroTask和macroTask的api范畴
macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
microtasks: process.nextTick, Promises, Object.observe, MutationObserver
http://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context

个人生活日记

闻同学结婚有感

今天听闻高中同学快要结婚的消息,心里还是有些小激动。 晚上写完代码走在回家的路上,我看着八月十五的月亮,脑中又开始有些别样的思绪。

结婚是人生大事,工作也是人生的大事。我们马不停蹄的往前赶,路过一站又一站的风景,不会停下,也停不下。

对于我来说,每天除了工作,其余大部分时间还是在馄饨的想法中渡过的。我也会结婚,我也会拥有自己的家庭。等到那一天,我为自己的新娘揭开面纱的时候,那会是何种景象,又会拥有什么样的心情呢。

人的一生需要学会珍惜,珍惜青春,珍惜身边的人,珍惜所被赐予的一切。

it will elapse , it will elapse.

在上海生活了这1年多,我看到了社会的贫富不均,看到了自己所处的位置,看到了人会老去,也看到了孩子的活力。我们要创造美好,争取未来,这是每天面对的最现实的问题。

if you are not grateful ,you are not rich.

我会音乐/代码,她会诗书/物理。

当欲望来临时,让它变为云淡风轻。

当岁月流逝时,让它变为潺潺流水。

当我到了携手白首的日子,让今后的一切变为诗歌,随着岁月悠扬下去。

-----pandakeeper 16/9/19

Angular2下的开发架构初探

Angular2下的开发架构初探

前言

ng2在2015年底发布beta版本,中间发布过各种rc版本的更新,但是总体的架构思路和核心API基本已经确定。这篇文章是我在使用typescript为朋友架构angular2网站过程中的一些心得。

Angular2核心开发理念

  1. 组件: 每个组件都是类class的实例,使用元数据metadata来描述组件的基本信息
  2. 指令: 转换dom树。主要分为属性指令【负责改变dom上的属性,如ngStyle】、组件指令【负责代表某个组件,类似】、结构指令【负责改变dom树的结构,如ngFor,ngIf
  3. 服务: 通过injectable管理组件需要的服务,比如http、自定义服务等。
  4. 元数据metadata:提供处理类class的基本信息

Angular2的API划分

  • @angular/common

定义绝大部分angular2的组件和逻辑的基类typescript接口

  • @angular/core

定义绝大部分angular2的核心处理逻辑

  • @angular/http

处理angular2的网络请求部分

  • @angular/router

处理angular路由处理部分

  • @angular/platform-browser-dynamic

处理angular共享的类似cookie、title、location等单例服务

Angular2的开发难点

  1. 组件通信

在开发者社区,经常会看见前端同学问组件通信及typescript结合ng2的问题。其实在angular2的官方文档上,对组件交互这块已经做了很好的回答见如下链接。[https://angular.io/docs/ts/latest/cookbook/component-communication.html]

2.JAVA理念的渗透,class、interface很多

angular2结合typescript号称可以匹敌java8+spring,这是当时谷歌的开发经理放下的一些豪言,从中,我们不难体会到typescript在ng2中的重要程度。
以form表单为例。对于每个input框的依赖追踪,ng2定义了ngControl作为父类,ngFormControl作为子类,来控制表单的输入验证及更新。而这些API源码都是用typescript完成的。如果开发者对组件化开发有一定研究,同时再结合typescript。那么根据这些定义好的class做扩展,将会是一件灵活而有趣的事情。

3、 社区资料少

目前我在写代码的过程中,碰到问题基本在国内是搜不到结果的。很多组件的开发都是基于github开源项目做的二次优化,我觉得和react-native一样,这是个新兴的技术点,相信之后能够在开发者社区掀起一定的波澜。当然,为此我也和几个小伙伴开始整了个repo,专门用来开发ng2的组件,目前开发进度还可以,代码地址https://github.com/slashhuang/ng2-components。

Angular2中渗透的设计模式

pub/sub 订阅发布模式 eventEmitter
observer模式: 在追踪依赖更新的时候,还是少不了它。ng2比ng1在这块上面,优化了不少。

总结:

本文说明的ng2的东西比较大而全,在具体的API细节上面,确实有很多的**可以深究。
anyway,期待ng2的社区进一步繁荣

前端工程化下的组件设计原则漫谈

前言

前端项目已经走向工程化,这个事实已经被大多数前端工程师所认识到。

在UI + Model+ Logic三块并重的前端领域,组件化 + 模块化 + 规范化 + 工程化是前端领域的四个现代化。
对于以上四个方面,业界已经有不少讨论,无论是react和vue等前端框架对组件化的实践、web-components提出的对组件设计规范的约束、webpack对amd、cmd、commonJS、umd模块规范的实现,还是phatomJS在腾讯、谷歌、fb等的同构JS架构实践。

业界的前端大咖们已经在不经意间,将前端的各种新理念推送给每一位前端开发工程师。

写一个组件具备的基本条件

形式上来看: 组件是由html+css+js组合而成的。

功能上来看: 组件管理着自己的内部逻辑、同时也能给其他模块提供数据调用接口。

如何写一个组件

借鉴react、vue框架的设计理念,我们来看下如何安排一个简单的html+css+js结构

  • html ==> 采用字符串或者模板引擎来处理【仅仅处理首次渲染】
  • js ==> 在html的基础上挂载事件及处理逻辑
  • css ==> 剥离在html和js之外【本文的讨论均不涵盖css】

JS如何代理model到view映射的职责

抽象前端的model

model一般而言可以分为UI层面和功能层面的数据模型,同时UI的数据来源必须只有一个。在这个共识基础上,我们可以设计一个类似mongo或者redis的前端运行时数据管理方案。

以一个hashMap前端数据结构为例,它一般会长如下的样子

       var model={
             state<HashMap>, //组件内部的数据集合static state
             props<HashMap> //组件动态的数据集合dynamic props
       }

然而如果model的数据结构按照如上设计后,UI会因为有state和props两种hash表而无法统一更新的数据来源。

同时更新策略也无法对比本次和上次model的不同而做出弹性处理,所以我们在这个基础上修正model的设计方案如下。

       var staticModel = {
             state<HashMap>, //组件内部的数据集合static state,不存在数据对比
       }
       var previousModel={
             props<HashMap> //组件动态的数据集合dynamic props,存储组件的上次model
       }
      var currentmodel={
             props<HashMap> //组件动态的数据集合dynamic props,存储组件的本次model
       }

如上的model基本满足我们对UI的处理方案了,接下来开始设计model的对外接口。

抽象model的接口

针对以上model的设计方式,我们设计setState(),updateProps(),getModel()来完成一个基本的model更新接口,比如基本的功能可以设计如下。

       var setState=function(obj){
               Object.assign(staticModel,obj)
       }
      var updateProps=function(obj){
               previousModel = currentModel;
               currentmodel = Object.assign({},currentmodel,obj);
             //这里只是伪代码,比如react-redux里面是采用的shallowequal来处理的
              if(previousModel!=currentmodel){ 
                     //执行controller逻辑
               }
       }
     var getModel=function(obj){
               return { staticModel,currentmodel,previousModel}
       } 

设计controller以便完成model到UI更新策略

所有的UI更新应该发生在model接口调用、数据更新完毕后,这时model和view的映射关系是如何建立的对于组件的性能提升至关重要。

为了快速的完成dom的更新。model到view的映射关系设计应该尽量的精确,同时dom的更新层级应该尽量的少。

借鉴主流的react前端框架

以虚拟dom框架的代表react为例,它提供的方案是在dom节点添加data-reactid来处理model和dom节点的映射关系。
同时由于JS速度快于dom,react对dom更新策略全都发生在JS运行时。具体更新策略按照如下几步执行。

  1. 以setState调用的component为起点,生成新的model
  2. 对比新生成的model和旧有的model,如果有不同,则执行生成新的虚拟dom策略
  3. 按照虚拟dom本身的attributes,innerHTML,children来比对数据
  4. 递归children调用patch diff对比操作,执行remove,update,move ,appendNode等操作,完成dom更新。

然而,以上策略对于一个简单的组件,尤其是jquery组件来说,实在太重了。

我们可以在借鉴部分**的基础上将组件的controller设计的更加颗粒化。

设计组件的简单controller以完成model到UI的更新策略

对于dom的映射关系,如果不采用虚拟dom【react】或者遍历dom树添加reference【vue/angular】来建立model和view映射关系的话。

目前我能想到的就是手动添加data-model-id规则来标示这种关系了。

比如建立一个规则,在dom上标注data-model-id='color,height'就表明这个节点依赖color和height两个键值。

我们根据以上的一些讨论和启发,来写一个简单controller。

       var html= '<div data-model-id='color,height'  id='hello'>hello</div>';
       var controller={ 
           nodeList:$(['data-model-id']),
           changeNodeList:(key)=>{
                  return this.nodeList.filter((node,key)=>{
                            return     node.data('modelId').split(',').indexOf(key)>-1
                  ])
          },
          compileDom(dom){
                 //这里面写dom如何修正,比如mustache语法的数据绑定规则或者underscore之类的
           },
           model2UI:(key)=>{
                   let model = model.getModel();
                   let changeNodeList = this.changeNodeList(key);
                   this.compileDom(changeNodeList);
            } ,     
            events:()=>{
                    let self =this;
                   //可以和vue一样写成内联的形式,提取依赖对dom统一挂载事件
                    this.nodeList.addEventListener('click',(e)=>{
                                     model.setState({color:red});
                                     self.model2UI('color')
                     })
             }
       }

以上的controller实现技术已经包括了模板提取技术、dom更新策略、view和model映射关系的一个简单实现了。

事实上,对于日常写组件的需求,在使用jquery等工具的时候,完全可以抛弃view=>model映射关系的处理,而仅仅保留controller和events。

这样的组件也是MVC分层及其清晰的。

对外接口

一个组件不可能完全独立于其他模块,这个时候就需要提供对外接口了。

一个简单的方案就是_定义规范_,比如每个组件都必须实现prototype.subscribe(callback)方法,在每次数据更新的时候,就可以通知其他组件,完成基本的通信需求了。

结语

对于市面上的框架来说,对于组件层面的渲染而言,vue的dom输出非常干净清爽,没有任何框架的痕迹。

而对于react、angular来说,输出的dom是在太多框架的痕迹。然而对于这个,可以不认为是个问题。

挺好奇vue对于view<=>model的映射关系建立是如何做的,大致猜测下是vue component在ready的时候,就完成dom节点referernce的建立。

我觉得这对于model和dom节点分层的抽象来说,是个很大的技术难点。

回归主题。前端组件的设计采用MVC分层的方式,好处就是一目了然、便于维护、功能更加封装。

不管是用jquery还是框架,这种设计原则将会在未来几年内逐步普及。

完。

深入浅出webpack编译体系

7 important tapable instances

  1. compiler: exosed via node api + central dispatch start/stop
  const webpack = require('webpack');
  const compiler = webpack(options);
  1. compilation:
    created by the compiler,
    contain dependency graph algorithm.

  2. resolver
    make sure file/folder/path exist

  3. module factory
    takes successfully resolved requests
    collects source for that file,
    creates a module object

  4. parser
    takes a module object,
    turns into AST and parse.
    find all dependency statements(require/import etc).

  5. template
    dataBinding for your modules,
    creates the code in your bundles.

   1. main template
   2. module template
   3. dependency template

核心**:

  1. every instance can be plugged into.

compiler ==options==> compilation -->

--> resolver(make sure entry point exist)
== moduleFactory --> modules -->

== Loaders or Rules --> parser
|--------------------->|

==> AST statements(requie/import etc)

==> attach to Modules(dependency graph)

==> recursively flow for dependency

inspired by https://www.youtube.com/watch?v=NHI_PhoykVU @reactRally

webpack设计**及源码组织

webpack设计**及源码组织

首先思考,如果我是webpack作者,我会怎么写

执行入口

  • 按照entry的文件路径作为执行入口

解析依赖,生成entry文件的连续依赖关系

  • main
    |- file1
    |----|---file1.1
    |----|---file1.2
    |- file2
    |----|---file2.1
    |----|---file2.2

将每个依赖文件按照绝对路径单独解析并独立存储

    //存储源文件
    { 'fileAbsolutePath':'fileString' }

按照文件依赖关系递归调用webpack及loader

    //存储解析后的源文件,并且让存储的fileID和如上的fileAbsolutePath建立映射关系
    {'fileID':'loader=>fileString' }

生成解析后的依赖关系

  • main
    |- file1-ID
    |----|---file1.1-ID
    |----|---file1.2-ID
    |- file2-ID
    |----|---file2.1-ID
    |----|---file2.2-ID

采用自定义的require方法,组装所有的编译字符串,形成编译完成的main文件.
(姑且叫按照如上依赖进行组装的require体系为__webpack_require__)

输入存储

  • 按照文件绝对路径,存储所有的source源文件。
    {'fileAbsolutePath':'fileString' }

基本数据结构及规范

  • entry ===> inputFileSystem
  • output===> outputFileSystem
  • 约定loader的规范
  • 约定plugin的规范

基本执行方式

  • 调用loader进行AST解析 > 将loader的执行

webpack几大骨架

    webpack.WebpackOptionsDefaulter = WebpackOptionsDefaulter;
    webpack.WebpackOptionsApply = WebpackOptionsApply;
    webpack.Compiler = Compiler;
    webpack.MultiCompiler = MultiCompiler;
    webpack.NodeEnvironmentPlugin = NodeEnvironmentPlugin;
    webpack.validate = validateWebpackOptions;
  1. 处理webpack的option
  • 转换: 使用WebpackOptionsDefaulter将option由facade模式(只是为了开发者友好,而采用的一种api设计模式)变为细节形式
  • 应用模式: WebpackOptionsApply

2、 生成编译实例compiler处理

作为一名前端的一些职业困惑和感想

从传统行业转入互联网领域

从房地产领域跨入互联网技术这行也快2年了。
现在在上海马路上行走的时候,看到某些楼盘或者工地上的农民工同志们,总能勾起一些回忆。

在这2年里,我大概有如下前端开发经历。

  • 技术框架上算是学过了react、angular、knockout、vue。
  • 在工具库上面是jQuery、lodash。
  • 在SPA单页面应用上用过PJAX、router等方案。
  • 在hybrid混合开发上调试过OC代码、写过部分JSbridge。
  • 在构建工具上面用过webpack、gulp[4]。
  • 在非babel时代用过requireJS。
  • 在源码层面研究过redux、react、requireJS、jQuery、webpack。
  • 在服务端层面阅读过nodeJS的child_process等文档,可以独立调试JAVA代码。
  • 在运维层面略微了解过nginx。
  • 在语言上面基本掌握JS和typescript。

上面列举的种种往往给人一种我学过好多东西的感觉,其实话说回来,对于传统的业务开发而言,前端东西并不多,用来用去就是万变不离其宗,学习成本比较低。

最近的困惑和呆板

其实最近我真的非常困惑和低落,一方面最近每天写业务代码,被业务逻辑搞得头大了,另一方面觉得自己接触的东西太狭隘,思维越来越单线程。

有时候,我会去对比一些从业年限更久的程序员。很多软院或计算机出身的朋友,他们有着很好的基础,但是对技术的热情不高;很多物理、数学、化学等出身的,反而因为技术好奇和热情而后来居上。而我有幸在这头两年里面写代码比较勤快,在代码能力上面是可以不输前者的。

然而,任何事物的发展都是有它的生命周期的。

当新鲜期过去,当大部分的好奇都揭开面纱,是不是能继续好奇的做下去,做什么,怎么做? 这些都是很现实的问题,但是很难回答。

我自己思考了下如上问题,脑海里隐隐的给出了几个答案。

跟着风口,猪都能飞起来。围绕有名的开源框架比如webpack、react、vue,做些建设性的工作,写写插件,写写组件,这些工作能够让自己在技术社区的影响力得到不错的提升。

研习后端、算法等。在传统的web、数据库、算法层面,是不是能够达到独当一面的能力,是个衡量是否是全栈工程师的重要标准,这个是技术提升上的一个突破口。但是,根据目前的发展,公司是很少会让一个前端转岗去写后端,想实践也只能在业余。

兼做一款产品。技术的价值离不开产品,比如在微信平台做个小游戏平台,比如做个社区,比如做个基于LBS的巨大的聊天室,比如做个通往名企的过来人平台,比如做个类似二维码之类的开发。

想法很多,现实很残酷,现实还很迷茫呢!
有时觉得自己真的很SB,脑子像停滞了一样,不知道在想啥东西。

最后,在github和公司写了代码后,我得到一些star和fork,关于代码价值有如下的体会。

在社区写代码,发表文章而得到同行的认可和转发,作为技术从业者是会有一定存在感和价值感的。
通过写代码,将代码价值转移到产品上面,给广大用户使用,这也会带来不错的成就感。

代码就是价值,想办法让代码有价值。

所以,代码有时可以理解为一种价值传递! 传递信息、沟通人群。
从这个角度再看现在的技术职业道路,我心里不禁得到了一些宽慰!

前端相关资料及坑点整理

  1. 雪碧图合并compass 安装:
      ruby ==> gem
      gem install compass
      #  [mac环境会报没有权限]
      # 解决方案 [mac环境将gem的库二进制文件直接写入系统环境变量]
      sudo gem install -n /usr/local/bin compass -V
  1. 制作VR视频的前端资料

krpano
国内的资料

  1. 安卓开发sdk下载国内镜像:

安卓开发工具集合
rn安卓安装gist
adb设备启动相关
安卓学习指南

4.吐槽:npm的鬼受网络影响挺大的,一次安装不行就再安装一次。。。就好了

个人爱好

gtp6安装指南

词法作用域与函数式编程语言

前言

几乎每个前端JSER都会碰到闭包、函数式语言、高阶函数等概念,而大多数社区的回答都有点含糊不清。本文从词法作用域的角度来解析什么是闭包,并对程序语言中的一些术语(terms)进行解释。

作用域

要讨论词法作用域,必须先理解什么是作用域。
首先我先摘录一段wikipedia在computer science条目下的解释

The term "scope" is used to refer to the set of all entities that are visible 
or names that are valid within a portion of the program or at a given point in a program, 
which is more correctly referred to as context or environment.

通俗点就是说:在程序的某个节点上的作用域指的是,该代码节点能够阅读到的所有实体(entity),也被称为上下文或者执行环境。
(注:entity简单来讲就是由标识符代表的代码和变量)

作用域的表现形式

以上我们提到作用域的讨论依赖具体的程序节点,这个程序节点可以细分为如下两块。

  • 源代码的文本片段(area of text)
  • 源代码的节点运行时(runtime)

如果是第一种情况,我们就称它为lexical scope(词法作用域)
如果是第二种情况,就称它为dynamic scope(动态作用域)

概念性的东西确立后,我们就可以讨论词法作用域和动态作用域了。

词法作用域和动态作用域的执行方式

对于词法作用域而言,程序在某个节点上运行的时候。

变量查找先从该节点所属的函数(或代码块)开始,如果找不到,则往上面一级的函数(或者代码块)开始查找,直到根作用域。

对于动态作用域而言,由于作用域依赖于runtime,程序在某个节点上运行的时候。

变量查找按照执行栈(call stack)进行查找,变量先在执行函数里面查找,如果找不到则往调用该执行函数的栈里面查找。

可以看到的是,两种作用域形式首先都会在当前函数作用域下寻找变量。

举个直观的例子,以bash脚本(使用dynamic scoping)为例

   $ x=1
   $ function g () { echo $x ; x=2 ; }
   $ function f () { local x=3 ; g ; }
   $ f 
   $ echo $x 

对于如上的代码,分析下执行结果。
如果采用dynamic scoping来分析的话,变量查找依赖于运行的执行栈。
首先函数f执行,由于g在函数f中被执行,因此g中的x访问的是f中的本地变量,打印出来3并修改f中的局部变量x为2。
然后程序运行echo $x来说,访问的是全局作用域下的x,因此打印出来的是1。最终结果打印出来的是3和1

如果采用的是lexical scoping的话,则g在f中执行,访问的是全局变量x,因此打印出来1,同时修改全局变量x为2,因此echo $x打印出来的为2。最终结果打印出来1和2.

由这个简单的例子,大家其实可以看清楚两种作用域下不同的scope方式。
下面我们就可以说说词法作用域下的FP(函数式编程)下的闭包closure了。

函数式编程语言与闭包

开篇先理清楚FP、closure、lexical scoping的联系。

** 闭包是词法作用域在函数式编程语言的集中体现。 **

** 在实践上,闭包就是函数和上下文的绑定。 **

** 闭包允许闭包内的函数,访问闭包创建时拷贝的上下文变量(值或者引用) **

举个直观的例子,以JavaScript为例

     function startAt(x)
         let fnY = (y)=> x + y ;
         return fnY;
    let closure1 = startAt(1)
    let closure2 = startAt(5)

基本学过JS的都能够知道closure1(3)返回4,closure2(3)返回8。

由于fnY在startAt函数的词法作用域里面,因此startAt(1)在执行后,返回一个闭包。

这个闭包拷贝了变量x(值或引用),因此fnY在执行的时候,直接可以获取到变量x的值。

需要补充说明的是,closure1和closure2返回的是不同的闭包,这种不同体现在上下文变量的拷贝不同,导致了fnY函数在执行的时候,阅读到的上下文不同,而产生不同的执行结果

闭包在语言中的实现方式

要实现闭包,在数据结构选型方面,肯定不是线性stack,因为闭包在执行时,仍应该保持绑定上下文的不变,而不是去阅读对应的执行环境。而线性(stack)显然无法满足要求。

实际上对于大部分拥有闭包的语言,程序语言采用的是堆(heap)的形式存储上下文non-local变量。 也正因为如此,这些语言基本自带GC(垃圾回收)。

闭包在github上也有很多的C语言实现,我的基本思路如下

根据function的嵌套关系,在单向链表中存储函数执行的context>context>context。

每次碰到要创建context的时候,则复制链表之前的数据作为当前函数执行的闭包环境。

变量引用的时候,则沿着当前的闭包环境单向链表不断向上寻找即可。

总结

这篇文章是我根据wikiPedia及查阅了相关资料的一个小总结。

对于词法作用域的阐述参考了较多的wikipedia,而对于closure的理解则参考了MDN及chrome dev tool给出的实验数据。

citation引用

wikipedia在computer science条目下的解释
entity
wiki对闭包的解释
MDN closure

开发中的开源项目列表

目前业余主要进行的开源项目

  • cuty : a Node.js framework

  • markdown-parser : a Markdown intermediate presenter which can be easily customized with classNames

前端学习笔记

文档

  1. WC.org CSS
  2. linux文档

Android

  1. HAXM :安卓硬件加速Hardware Accelerated Execution Manager
  2. AVD: android virtual device
  3. Gradle:

Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各种繁琐配置,目前主要面向JAVA语言。

  1. NDK native development kit
    5.ABI:application binary interface

文档

  1. 安卓中文文档
    2.groovy基本语法中文版

IOS

1.h5唤醒app之universal link

nodeJS

技术名词

  1. REPL(read eval print loop)
  2. event Loop :setImmediate(I/0 cycle Libuv api) vs process.nextTick(nextTick queue)
  3. ENOENT: Error NO Entry,

react-native

好的开源组件

1.React-Native-ViewPager
2.react-native-vector-icons

Android vs JS通知机制分析【react-native webview等】

前言

最近公司开始上RN android项目,作为一个基本上不写java的纯野生web前端开发,
我还是想来窥探一下react-native/android下对RN项目提供的jar包结构和类方法,以便对项目开发更具有掌控力。

正文

传统JS-Android开发模式【Webview接口】

通过使用android提供的Webview类,即可完成JS和java代码的通信。
基本的使用方式如下:

  1. 添加webview页面
    <!--  res/layout/下的文件  -->
    <WebView  xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/webview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
    />
  1. 添加程序清单,增加网络权限
    <!--  androidManifest.xml 添加Internet权限-->
    <?xml version="1.0" encoding="utf-8"?>
        <manifest ... >
        <uses-permission android:name="android.permission.INTERNET" />
    </manifest>
  1. 添加webview接口
    /**添加Webview**/

    WebView myWebView = (WebView) findViewById(R.id.webview);
    myWebView.loadUrl("http://www.example.com");

    /*添加java接口*/
    public class WebAppInterface {
        Context mContext;
        /** 设置context */
        WebAppInterface(Context c) {
            mContext = c;
        }
        /**点击跳出toast*/
        @JavascriptInterface
        public void showToast(String toast) {
            Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
        }
    }

    /*添加JS调用java的接口描述,webview可以调用window['android']来触发native代码*/
    myWebView.addJavascriptInterface(new WebAppInterface(this), "android");

    /*添加Java直接调用JS的接口*/
    myWebView.evaluateJavascript("getGreetings()", new ValueCallback<String>(){
        @Override
        public void onReceiveValue(String value) {
           println(value);
        }});
    });
    /* 启用JS */
    myWebView.getSettings().setJavaScriptEnabled(true);
  1. 添加前端代码
    <!--   调用Android代码   -->
    <input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />
    <script type="text/javascript">
        function showAndroidToast(toast) {
            window.android.showToast(toast);
        }
        function getGreetings() {
            return 1;
        }
    </script>
  1. 网络请求拦截,最经典的JSBridge实现方案
    UIWebView组件【ios】
    shouldOverrideUrlLoading((WebView view, WebResourceRequest request))【Android】

RN 安卓的基本类和实现原理

RN的基本实现思路是比较简单的,不同于传统的webview按照jsBridge形式进行native和h5的通信方案,
RN的实现方式充分利用了ios/android内置的JS引擎。
一个基本RN的activity依赖这么几个重要的类/库

  1. 安卓项目运行

RN的实现原理【创建原生模块为例】

原生模块 extends ReactContextBaseJavaModule

    public class ToastModule extends ReactContextBaseJavaModule {

      private static final String DURATION_SHORT_KEY = "SHORT";
      private static final String DURATION_LONG_KEY = "LONG";

      public ToastModule(ReactApplicationContext reactContext) {
        super(reactContext);
      }
      /*派生类实现getName方法*/
      @Override
      public String getName() {
        return "ToastAndroid";
      }
      /*一个可选的方法getContants返回了需要导出给JavaScript使用的常量*/
      @Override
      public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
        constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
        return constants;
      }
      /*要导出一个方法给JavaScript使用,Java方法需要使用注解@ReactMethod*/
      @ReactMethod
      public void show(String message, int duration) {
        Toast.makeText(getReactApplicationContext(), message, duration).show();
      }
      /*提供给JS进行回调*/
      @ReactMethod
      public void measureLayout(
          int tag,
          int ancestorTag,
          Callback errorCallback,
          Callback successCallback) {
        try {
          measureLayout(tag, ancestorTag, mMeasureBuffer);
          float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
          float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
          float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
          float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
          successCallback.invoke(relativeX, relativeY, width, height);
        } catch (IllegalViewOperationException e) {
          errorCallback.invoke(e.getMessage());
        }
      }
    }
      /*注册模块*/
      class AnExampleReactPackage implements ReactPackage {
          @Override
          public List<Class<? extends JavaScriptModule>> createJSModules() {
            return Collections.emptyList();
          }

          @Override
          public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
            return Collections.emptyList();
          }

          @Override
          public List<NativeModule> createNativeModules(
                                      ReactApplicationContext reactContext) {
            List<NativeModule> modules = new ArrayList<>();

            modules.add(new ToastModule(reactContext));

            return modules;
          }
    }
    /*将注册模块的列表添加进MainApplication.java*/
    protected List<ReactPackage> getPackages() {
        return Arrays.<ReactPackage>asList(
                new MainReactPackage(),
                new AnExampleReactPackage()); // <-- 添加这一行,类名替换成你的Package类的名字.
    }

参考资料

安卓webview实现原理
react-native架构初探
react-native 安卓模块
facebook关于react-native执行原理的讲解

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.