首页 > 代码库 > 前端编程之路

前端编程之路

前言:本人作为一个前端小白,梳理一下这段时间开发前端一个页面的过程,希望对自己起到备忘并思考的作用,希望我的经验对于别人也有一点帮助的作用。由于本文碎碎念的部分过长,导致本文过长。还请读者见谅。—更新中(更新周期大概还会持续一个月)

前端框架

现如今,前端框架也越来越多了,比如ember,angular,以及这里要说到的vuejs.。下面的内容首先要介绍vue.js的原理。

Vue.js ?

  • 原理

vue.js是一个 MVVM 前端框架,(Model / View / ViewModel)
这里的viewModel是什么含义呢?
这意味着我们不需要撰写任何 DOM 操作代码,被绑定增强的 HTML 模板(<template>)是底层数据状态的声明式的映射,数据不过是普通 JavaScript 对象。我们的视图完全由数据驱动。
技术分享
vue将普通的对象(也就是js的普通的对象)的属性通过Object.defineProperty转换为[ES5特性]之一的 getter/setter。

OBJECT
OBJECT.DEFINEPROPERTY()
该方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。Object.defineProperties()与其一样,只是可以同时定义多个属性。
该方法允许精确添加或修改对象的属性。常用的场景
定义setter和getter。
定义对象属性是否可枚举enumerable。
可枚举的属性键值能够被for inObject.keys获得。
最常见的例子就是,数组中索引属性是可枚举的,而成员方法就是不可枚举的。
~! 这也是为什么我们不要使用for in遍历数组的原因,因为可能有一些拙劣的上下文代码,为数组添加了一个可枚举的方法,因此我们在扩展一个特殊对象属性时特别需要特别关注这一点
OBJECT.KEYS()
把对象的返回一个包括对象可枚举键值的数组。

模板中每个指令/数据绑定 都有一个对应的 watcher 对象, 当修改对象值的时,首先会触发属性的setter,在setter被调用时,会触发 watcher 重新计算 ,也就会导致它的关联指令更新 DOM。
上面的内容把vuejs的内容已说明的很清楚了,如果你能理解指令和数据绑定(下面还会有更多的解释)的话。
Vue.js 的核心是一个响应的数据绑定系统,它让数据与 DOM 保持同步非常简单。在使用 jQuery 手工操作 DOM 时,我们的代码常常是命令式的、重复的与易错的。Vue.js 拥抱数据驱动的视图概念。通俗地讲,它意味着我们在普通 HTML 模板中使用特殊的语法将 DOM “绑定”到底层数据。一旦创建了绑定,DOM 将与数据保持同步。每当修改了数据,DOM 便相应地更新。这样我们应用中的逻辑就几乎都是直接修改数据了,不必与 DOM 更新搅在一起。这让我们的代码更容易撰写、理解与维护。

  • 现在重要的问题是用vue来解决问题

比如我想在一个项目里面使用到一个灵活的jsoneditor,通过调研发现vuejs并没有直接的组件可以用,但是发现javascript有一个很好用的叫做jsoneditor的组件,那么我们如何在一个vue.js的工程里面加入这个jsoneditor?

这里就会包括很多要解决的问题了,下面的内容将一一道来。

模块系统

伴随着移动互联的大潮,当今越来越多的网站已经从网页模式进化到了 Webapp 模式。它们运行在现代的高级浏览器里,使用 HTML5、 CSS3、 ES6 等更新的技术来开发丰富的功能,网页已经不仅仅是完成浏览的基本需求,并且webapp通常是一个单页面应用,每一个视图通过异步的方式加载,这导致页面初始化和使用过程中会加载越来越多的 JavaScript 代码,这给前端开发的流程和资源组织带来了巨大的挑战。
前端开发和其他开发工作的主要区别,首先是前端是基于多语言、多层次的编码和组织工作,其次前端产品的交付是基于浏览器,这些资源是通过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和资源,并且保证他们在浏览器端快速、优雅的加载和更新,就需要一个模块化系统,这个理想中的模块化系统是前端工程师多年来一直探索的难题。

模块系统主要解决模块的定义、依赖和导出,先来看看已经存在的模块系统。

<script>标签
<script src=http://www.mamicode.com/"module1.js"></script>
<script src="module2.js"></script>
<script src="libraryA.js"></script>

这是最原始的 JavaScript 文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在 window 对象中。

CommonJS

服务器端的 Node.js 遵循 CommonJS规范,该规范的核心思想是允许模块通过 require 方法来同步加载所要依赖的其他模块,然后通过 exportsmodule.exports 来导出需要暴露的接口。

require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;

上面的代码很好理解,但是看起来似乎能解决的问题并不多。那么期望的模块系统是什么样子的呢?

webpack

webpack 就是一个很好的模块化系统。

  • 可以兼容多种模块风格,尽量可以利用已有的代码,不仅仅只是 JavaScript 模块化,还有 CSS、图片、字体等资源也需要模块化。
  • 前端模块是怎么加载的呢?前端模块要在客户端中执行,所以他们需要增量加载到浏览器中。模块的加载和传输,我们首先能想到两种极端的方式,一种是每个模块文件都单独请求,另一种是把所有模块打包成一个文件然后只请求一次。显而易见,每个模块都发起单独的请求造成了请求次数过多,导致应用启动速度慢;一次请求加载所有模块导致流量浪费、初始化过程慢。这两种方式都不是好的解决方案,它们过于简单粗暴。分块传输,按需进行懒加载,在实际用到某些模块的时候再增量更新,才是较为合理的模块加载方案。要实现模块的按需加载,就需要一个对整个代码库中的模块进行静态分析、编译打包的过程
  • 所有资源都是模块:在上面的分析过程中,我们提到的模块仅仅是指JavaScript模块文件。然而,在前端开发过程中还涉及到样式、图片、字体、HTML 模板等等众多的资源。这些资源还会以各种方言的形式存在,比如 coffeescript、 less、 sass、众多的模板库等等。如果他们都可以视作模块,并且都可以通过require的方式来加载,将带来优雅的开发体验,比如:require("./style.css");
    require("./style.less");
    require("./template.jade");
    require("./image.png");

静态分析

如何做到让 require 能加载各种资源呢?在编译的时候,要对整个代码进行静态分析,分析出各个模块的类型和它们依赖关系,然后将不同类型的模块提交给适配的加载器来处理。比如一个用 LESS 写的样式模块,可以先用 LESS 加载器将它转成一个CSS 模块,在通过 CSS 模块把他插入到页面的 <style> 标签中执行。

Webpack 是一个模块打包器。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。Webpack 本身只能处理原生的 JavaScript 模块,但是 loader 转换器可以将各种类型的资源转换成 JavaScript 模块。这样,任何资源都可以成为 Webpack 可以处理的模块。

NODE_ENV

通过NODE_ENV可以来设置环境变量(默认值为development)。
一般我们通过检查这个值来分别对开发环境和生产环境下做不同的处理。可以在命令行中通过下面的方式设置这个值:

linux & mac: export NODE_ENV=production
windows: set NODE_ENV=production
比方说如果代码中要对生产环境下做一些处理,可以这样写:

if (process.env.NODE_ENV === ‘production‘) {
    // just for production code
}

express结合webpack实现hmr

webpack 和 Express 实现前后端热更新开发,
Webpack dev server 是一个轻量的node.js express服务器,实现了 webpack 编译代码实时输出更新。在前后端分离的前端项目开发中经常用到。不过这篇文章应该不会讲到它。
“webpack-dev-middleware”: “^1.6.1”,
Webpack hot middleware 它通过订阅 Webpack 的编译更新,之后通过执行 webpack 的 HMR api 将这些代码模块的更新推送给浏览器端。

express

为了提供诸如图像、CSS 文件和 JavaScript 文件之类的静态文件,请使用 Express 中的 express.static
内置中间件函数。

express.static是一个内置的中间件

app.use(express.static(‘public‘));

现在,可以装入位于 public 目录中的文件:

http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/images/bg.png
http://localhost:3000/hello.html

Express 相对于静态目录查找文件,因此静态目录的名称不是此 URL 的一部分。

app.use(express.static(‘public‘));
app.use(express.static(‘files‘));

要为 express.static 函数提供的文件创建虚拟路径前缀(路径并不实际存在于文件系统中),请为静态目录指定安装路径,如下所示:

app.use(‘/static‘, express.static(‘public‘));

现在,可以装入具有 /static 路径前缀的 public 目录中的文件。


http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css
http://localhost:3000/static/js/app.js
http://localhost:3000/static/images/bg.png
http://localhost:3000/static/hello.html

Node.js path 模块提供了一些用于处理文件路径的小工具,我们可以通过以下方式引入该模块:

var path = require("path")

path.join([path1][, path2][, …])
用于连接路径。该方法的主要用途在于,会正确使用当前系统的路径分隔符,Unix系统是”/”,Windows系统是”\”。

webpack命令

webpack 2.3.3
Usage: https://webpack.js.org/api/cli/
Usage without config file: webpack <entry> [<entry>] <output>
Usage with config file: webpack

Config options:
  --config  Path to the config file
                         [string] [default: webpack.config.js or webpackfile.js]
  --env     Environment passed to the config, when it is a function

Basic options:
  --entry      The entry point                                          [string]
  --watch, -w  Watch the filesystem for changes                        [boolean]
  --debug      Switch loaders to debug mode                            [boolean]
  --devtool    Enable devtool for better debugging experience (Example:

所以webpack的主要作用就是将入口文件最后变为出口文件,我觉得可能相当于make吧

常见的loader和作用

Loader 可以理解为是模块和资源的转换器,它本身是一个函数,接受源文件作为参数,返回转换的结果。这样,我们就可以通过 require 来加载任何类型的模块或文件,比如 CoffeeScript、 JSX、 LESS 或图片。
Loader 本身也是运行在 node.js 环境中的 JavaScript 模块,它通常会返回一个函数。大多数情况下,我们通过 npm 来管理 loader,但是你也可以在项目中自己写 loader 模块。

通常在webpack.config.js的module.exports中导出的对象中:
比如css loader

module: {
    loaders: [
      {test: /\.css$/, loader: ‘style-loader!css-loader‘}
    ]
  }

Adds CSS to the DOM by injecting a <style> tag
npm install style-loader –save-dev
style-loader的用法如何?
It’s recommended to combine it with the css-loader: require(“style-loader!css-loader!./file.css”).
add rules in file.css to document
The css-loader interprets @import and url() like import/require() and will resolve them.
Loader 可以通过管道方式链式调用,每个 loader 可以把资源转换成任意格式并传递给下一个 loader ,但是最后一个 loader 必须返回 JavaScript。
loader 一般以 xxx-loader 的方式命名,xxx 代表了这个 loader 要做的转换功能,比如 json-loader。
Loader 可以在 require() 引用模块的时候添加,也可以在 webpack 全局配置中进行绑定,还可以通过命令行的方式使用。

加载 CSS 需要 css-loader 和 style-loader,他们做两件不同的事情,css-loader会遍历 CSS 文件,然后找到 url() 表达式然后处理他们,style-loader 会把原来的 CSS 代码插入页面中的一个 style 标签中。

require(“!style-loader!css-loader!./style.css”)

因为本文要将的是vue.js,所以下面还会仔细分析vue-loader。
在webpack中加入:

{
        test: /\.vue$/,
        loader: ‘vue‘
      },

而vue-loader的定义:

module.exports = function (content) {
  this.cacheable()
  var isServer = this.target === ‘node‘
  var isProduction = this.minimize || process.env.NODE_ENV === ‘production‘

  var loaderContext = this
  var query = loaderUtils.getOptions(this) || {}
  var options = this.options.__vueOptions__ = Object.assign({}, this.options.vue, this.vue, query)
  var rawRequest = getRawRequest(this, options.excludedPreLoaders)

  var filePath = this.resourcePath
  var fileName = path.basename(filePath)

  var context = (this._compiler && this._compiler.context) || this.options.context || process.cwd()
  var moduleId = ‘data-v-‘ + genId(filePath, context, options.hashKey)

  var cssLoaderOptions = ‘‘
  if (!isProduction && this.sourceMap && options.cssSourceMap !== false) {
    cssLoaderOptions += ‘?sourceMap‘
  }
  ‘‘‘
   // final export
    if (options.esModule) {
      output += ‘\nexports.__esModule = true;\nexports["default"] = Component.exports\n‘
    } else {
      output += ‘\nmodule.exports = Component.exports\n‘
    }
  } else {
    // inject-loader support
    output =
      ‘\n/* dependency injection */\n‘ +
      ‘module.exports = function (injections) {\n‘ + output + ‘\n‘ +
      ‘\nreturn Component.exports\n}‘
  }

  // done
  return output
}

除此之外,还需要了解的两个loader分别是url-loader和babel-loader.
url-loader的实现源码如下:


    var limit = (this.options && this.options.url && this.options.url.dataUrlLimit) || 0;
    if(query.limit) {
        limit = parseInt(query.limit, 10);
    }
    var mimetype = query.mimetype || query.minetype || mime.lookup(this.resourcePath);
    if(limit <= 0 || content.length < limit) {
        return "module.exports = " + JSON.stringify("data:" + (mimetype ? mimetype + ";" : "") + "base64," + content.toString("base64"));
    } else {
        var fileLoader = require("file-loader");
        return fileLoader.call(this, content);
    }
}
module.exports.raw = true;

作为url-loader的package.json的文件中,就已经有这些依赖了:

 "dependencies": {
    "loader-utils": "^1.0.2",
    "mime": "1.3.x"
  },
  "peerDependencies": {
    "file-loader": "*"
  },

而file-loader的源码是像这样:

var publicPath = "__webpack_public_path__ + " + JSON.stringify(url);
    if (config.publicPath !== false) {
        // support functions as publicPath to generate them dynamically
        publicPath = JSON.stringify(
            typeof config.publicPath === "function"
            ? config.publicPath(url)
            : config.publicPath + url
        );
    }

    if (query.emitFile === undefined || query.emitFile) {
        this.emitFile(outputPath, content);
    }

    return "**module.exports** = " + publicPath + ";";

var url = require(“file-loader!./file.png”);
// => emits file.png as file in the output directory and returns the
public url //
=> returns i. e. “/public-path/0dcbbaa701328a3c262cfd45869e351f.png”

The url loader works like the file loader, but can return a Data Url if the file is smaller than a byte limit.The limit can be specified with a query parameter. (Defaults to no limit,没有限制最好了,最大的文件也会被潜入到网页中)

If the file is greater than the limit (in bytes) the file-loader is used and all query parameters (url查询参数)are passed to it.

网页优化的一大首要任务是减少HTTP 请求 (http request) 的次数,例如通过合并多个JS文件,合并CSS样式文件。除此之外,还有一个data URL 的密技,让我们直接把图像的内容崁入网页里面,这个密技的官方名称是 data URI scheme
(哈哈,如果用url-loader的话,我的那个文件请求就能得到正确的格式)(在本地内存中加载)

不然就会报错(变成了相关的请求报错,因为其实就是本地文件而已)

This package allows transpiling JavaScript files using Babel and webpack.

  • babel
    This package allows transpiling JavaScript files using Babel and webpack.Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。你可以现在就用ES6编写程序,而不用担心现有环境是否支持

  • markdown-highlight-loader;

安装

$ npm i -S markdown-highlight-loader

使用npm 安装,出入的参数是i 和-S i代表可能是install -S代表的应该是save…

用法其实很简单:

/*
include highlight.js theme styles, for example in "vendor" chunk:
entry: {
    vendor: [
        …
        ‘highlight.js/styles/railscasts.css‘
    ],
    …
}
*/
module: {
    loaders: [ {
        test: /\.md$/,
        loader: ‘html!markdown-highlight‘
        // loader: ‘html!markdown-highlight?+breaks&-smartLists‘
    } ]

hightlight的作用,使得代码高亮

更加简单的,如果不需要代码高亮的,直接使用markdown就好了:

{
    module: {
        rules: [{
                test: /\.md$/,
                use: [
                    {
                        loader: "html-loader"
                    },
                    {
                        loader: "markdown-loader",
                        options: {
                            /* your options here */
                        }
                    }
                ]
            }]
    }
}

插件系统

Webpack 还有一个功能丰富的插件系统。大多数内容功能都是基于这个插件系统运行的,还可以开发和使用开源的 Webpack 插件,来满足各式各样的需求。
插件可以完成更多loader不能完成的功能。
插件的会用一般是在webpack的配置信息plugins选项中指定。
我们利用一个最简单的 BannerPlugin 内置插件来实践插件的配置和运行,这个插件的作用是给输出的文件头部添加注释信息。

var webpack = require(‘webpack‘)
module.exports = {
  entry: ‘./entry.js‘,
  output: {
    path: __dirname,
    filename: ‘bundle.js‘
  },
  module: {
    loaders: [
      {test: /\.css$/, loader: ‘style-loader!css-loader‘}
    ]
  },
  plugins: [
    new webpack.BannerPlugin(‘This file is created by zhaoda‘)
  ]
}

再比如第三方插件extract-text-webpack-plugin:Extract text from bundle into a file.

for webpack 2
npm install –save-dev extract-text-webpack-plugin
for webpack 1
npm install –save-dev extract-text-webpack-plugin@1.0.1
const ExtractTextPlugin = require(“extract-text-webpack-plugin”);

module.exports = {
module: {
rules: [
{
test: /.css$/,
use: ExtractTextPlugin.extract({
fallback: “style-loader”,
use: “css-loader”
})
}
]
},
plugins: [
new ExtractTextPlugin(“styles.css”),
]
}这个插件的作用是什么呢?它会将所有通过require("style.css")s的entry块放到一个单一的css文件中去,使得你的styles不再嵌入到JS文件中,如果你的所有的css量很大,这会更快(因为css bundle是并行的导入到js bundle中去的)

node

  • 前面提到的内容很多都涉node和npm,然而我却一句未提,因为我知道我需要多花一点时间来慢慢的解剖这只麻雀。Node应用由模块组成,采用CommonJS模块规范。根据这个规范,每个文件就是一个模块,有自己的作用域,Node内部提供一个Module构建函数所有模块都是Module的实例。
    module.id 模块的识别符,通常是带有绝对路径的模块文件名。
    module.filename 模块的文件名,带有绝对路径。
    module.loaded 返回一个布尔值,表示模块是否已经完成加载。
    module.parent 返回一个对象,表示调用该模块的模块。
    module.children 返回一个数组,表示该模块要用到的其他模块。
    **module.exports 表示模块对外输出的值。**

    npm做nodejs的包依赖管理,bower做javascript的包依赖管理
  • javascript的import 语句用于导入从外部模块,另一个脚本等导出的函数,对象或原语。
  • 关于默认导出方式,每个模块只有一个默认导出。一个默认导出可以是一个函数,一个类,一个对象等。当最简单导入的时候,这个值是将被认为是”入口”导出值。export default{} 对于只导出一部分的值来说 命名导出的方式很有用。。。在导入时候,可以使用相同的名称来引用对应导出的值。
  • require
    最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了。后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载。下面的网页代码,相信很多人都见过。
      <script src="http://www.mamicode.com/1.js"></script>
      <script src="http://www.mamicode.com/2.js"></script>

     这段代码依次加载多个js文件。
    这样的写法有很大的缺点。首先,加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长;其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。require.js的诞生,就是为了解决这两个问题。

包管理器 npm

  • –save

    当你为你的模块安装一个依赖模块时,正常情况下你得先安装他们(在模块根目录下npm install
    module-name),然后连同版本号手动将他们添加到模块配置文件package.json中的依赖里(dependencies)。

    -save和save-dev可以省掉你手动修改package.json文件的步骤。 npm install module-name -save 自动把模块和版本号添加到dependencies部分 npm install module-name -save-dev 自动把模块和版本号添加到devdependencies部分

  • package.json
    dependencies部分:字段指定了项目运行所依赖的模块
    devdependencies部分:字段指定了项目开发所依赖的模块

If someone is planning on downloading and using your module in their program, then they probably don’t want or need to download and build the external test or documentation framework that you use.In this case, it’s best to map these additional items in a devDependencies object.
如果有人想下载并使用你的模块,但是他们可能不想或者不需要下载或安装额外的文档框架。就最好使用devDependencies 这个对象。
These things will be installed when doing npm link or npm install from the root of a package, and can be managed like any other npm configuration param. See npm-config for more on the topic.
For build steps that are not platform-specific, such as compiling CoffeeScript or other languages to JavaScript, use the prepare script to do this, and make the required package a devDependency.
这写东西会在安装的时候就用npm安装到package的root下面。

这两者的区别?
npm install 在安装 npm 包时,有两种命令参数可以把它们的信息写入 package.json 文件:

–save
–save-dev
但它的文档里1,只提到一个小区别,–save 会把依赖包名称添加到 package.json 文件 dependencies 键下,–save-dev 则添加到 package.json 文件 devDependencies 键下。
。它们真正的区别是,devDependencies 下列出的模块,是我们开发时用的,比如 grunt-contrib-uglify,我们用它混淆 js 文件,它们不会被部署到生产环境。dependencies 下的模块,则是我们生产环境中需要的依赖。

dependencies配置的依赖是在正常运行(一般为生产环境)该包时必须要的依赖项

什么意思?很简单,就是假设你的包被别人依赖了,那么别人在安装你的包时会自动安装你的包里的dependencies中配置的这些依赖包,也就是说你的包没有这些包就不能正常使用。

devDependencies配置的依赖是你在开发你的包时安装的一些在生产环境非必要的依赖项。

那什么是生产环境非必要的依赖项?比如说:你想使用mocha来测试你的包,那么就可以安装mocha到devDependencies,而不是dependencies,因为他不是生产环境所必须的。依赖你的包的人,是想依赖你的核心代码,而不是需要使用你包里的mocha

CSS /LESS/stylus

Less 是一门 CSS 预处理语言,它扩充了 CSS 语言,增加了诸如变量、混合(mixin)、函数等功能,让 CSS 更易维护、方便制作主题、扩充。

stylus 是一个CSS的预处理框架,2010年产生,来自Node.js社区,主要用来给Node项目进行CSS预处理支持,所以 Stylus 是一种新型语言,可以创建健壮的、动态的、富有表现力的CSS。比较年轻,其本质上做的事情与 SASS/LESS 等类似,应该是有很多借鉴,所以近似脚本的方式去写CSS代码。Stylus默认使用 .styl 的作为文件扩展名,支持多样性的CSS语法。

全局安装:
npm install stylus -g

比如&就是一个非常灵活的用法:

ul
    li a
        display: block
        color: blue
        padding: 5px
        html.ie &
            padding: 6px
        &:hover
            color: red

编译为css:

ul li a {
  display: block;
  color: #00f;
  padding: 5px;
}
html.ie ul li a {
  padding: 6px;
}
ul li a:hover {
  color: #f00;
}

下面具体一点:

就CSS本身而言,对于大多数Web前端从业人员来说就不是问题。学过CSS的人都知道,它不是一种编程语言。你可以用它开发网页样式,但是没法用它编程。换句话说,CSS基本上是设计师的工具,不是程序员的工具。在程序员的眼里,CSS是很头痛的事情,它并不像其它程序语言,比如说PHP、Javascript等等,有自己的变量、常量、条件语句以及一些编程语法,只是一行行单纯的属性描述,写起来相当的费事,而且代码难易组织和维护。

很自然的,有人就开始在想,能不能给CSS像其他程序语言一样,加入一些编程元素,让CSS能像其他程序语言一样可以做一些预定的处理。这样一来,就有了“CSS预处器(CSS
Preprocessor)”。
一、什么是CSS预处器

CSS预处理器定义了一种新的语言,其基本思想是,用一种专门的编程语言,为CSS增加了一些编程的特性,将CSS作为目标生成文件,然后开发者就只要使用这种语言进行编码工作。通俗的说,CSS预处理器用一种专门的编程语言,进行Web页面样式设计,然后再编译成正常的CSS文件,以供项目使用。CSS预处理器为CSS增加一些编程的特性,无需考虑浏览器的兼容性问题,例如你可以在CSS中使用变量、简单的逻辑程序、函数等等在编程语言中的一些基本特性,可以让你的CSS更加简洁、适应性更强、可读性更佳,更易于代码的维护等诸多好处。

CSS预处理器技术已经非常的成熟,而且也涌现出了很多种不同的CSS预处理器语言,比如说:Sass(SCSS)、LESS、Stylus、Turbine、Swithch CSS、CSS Cacheer、DT CSS等。如此之多的CSS预处理器,那么“我应该选择哪种CSS预处理器?”也相应成了最近网上的一大热门话题,在Linkedin、Twitter、CSS-Trick、知呼以及各大技术论坛上,很多人为此争论不休。相比过计我们对是否应该使用CSS预处理器的话题而言,这已经是很大的进步了。

到目前为止,在众多优秀的CSS预处理器语言中就属Sass、LESS和Stylus最优秀,讨论的也多,对比的也多。本文将分别从他们产生的背景、安装、使用语法、异同等几个对比之处向你介绍这三款CSS预处理器语言。相信前端开发工程师会做出自己的选择——我要选择哪款CSS预处理器。

less是2009年的一个开源项目,LESS提供了多种方式能平滑的将写好的代码转化成标准的CSS代码,在很多流行的框架和工具中已经能经常看到LESS的身影了(例如Twitter的Bootstrap框架就使用了LESS著作权归作者所有。
Stylus,2010年产生,来自于Node.js社区,主要用来给Node项目进行CSS预处理支持,在此社区之内有一定支持者,在广泛的意义上人气还完全不如Sass和LESS。

[Stylus]被称为是一种革命性的新语言,提供一个高效、动态、和使用表达方式来生成CSS,以供浏览器使用。Stylus同时支持缩进和CSS常规样式书写规则。

Sass、LESS和Stylus源文件(除了LESS源文件在客户端下运行之外)都不能直接被浏览器直接识别,这样一来,要正常的使用这些源文件,就需要将其源文件转译成浏览器可以识别的CSS样式文件,这也是使用CSS预处理器很关键的一步,如果这一步不知道如何操作,那么意味着写出来的代码不可用著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
原文: https://www.w3cplus.com/css/css-preprocessor-sass-vs-less-stylus-2.html ? w3cplus.com
Stylus的语法花样多一些,它的文件扩展名是“.styl”,Stylus也接受标准的CSS语法,但是他也像Sass老的语法规则,使用缩进控制,同时Stylus也接受不带大括号({})和分号的语法,如下所示:

/style.styl/
/类似于CSS标准语法/
h1 {
color: #963;
background-color:#333;
}
/省略大括号({})/
h1
color: #963;
background-color: #333;
/省略大括号({})和分号(;)/
h1
color:#963
background-color:#333
在Stylus样式中,你也可以在同一个样式文件中使用不同的语法规则,下面这样的写法也不会报错:

/style.styl/
h1 {
color #963
}
h2
font-size:1.2em

小区插曲:
早在90年代中期到后期起草的 CSS1规范中就介绍过!important,它能够帮助开发者和用户在修改样式表的时候轻松覆盖原本的权重。著作权归作者所有。
样式的应用依赖具体的情况,一个更加具体的选择器往往会比一个笼统选择器获得更大的权重。
样式的应用依赖样式出现的顺序(即,后面的会覆盖前面的)
从这个提纲中,你可能已经明白!important会如何改变权重以及它在层叠中扮演一个什么样的角色。接下来让我们看一下!important更多的细节。

举一个例子就可以很好的说明了:

语法和描述

!important为开发者提供了一个增加样式权重的方法。应当注意的是!important是对整条样式的声明,包括这个样式的属性和属性值(感谢Brad Czerniak指出其中的差别)。这里有个简单的代码示例可以清晰地说明!important是如何应用于原本的样式中的:

#example {
  font-size: 14px !important;   
}

#container #example {
  font-size: 10px;
}   
在上面的代码示例中,由于使用了!importantid为“example”的元素字号将被设置为14px。

如果不使用!important,第二个样式声明的代码块很自然地比第一个的权重要大,原因有二:在样式表中第二个代码块要比第一个出现的晚(即,它位列第二);第二个代码块有更大的权重(是由两个id#container #example组合而成,而不是只有一个id#example。但是因为第一个代码块里面包含了!important,所以对于字号设置来说它有更大的权重。

体(@media)
@media工作原理和在常规CSS中一样,但是,要使用Stylus的块状符号。
@media print
#header
#footer
display none
生成为:
@media print {
#header,
#footer {
display: none;
}
}

那么在css中这个@media到底是什么呢?
width 媒体属性描述了输出设备渲染区域(如可视区域的宽度或打印机纸盒的宽度)的宽度。

HTML JADE

jade语法:
Tags
默认,每行开始的第一个单词代表一个标签,可以是自定义的标签:

jade:

h1 h1 tag
custom custom tag
img(src=http://www.mamicode.com/”/images/logo.png”, alt=”“)

使用缩进能进行标签嵌套:

jade:

ul
  li Item A
    a(href=http://www.mamicode.com/"javascript:;") a link
  li Item B
  li Item C
属性必须用括号包起来,多个属性用逗号分隔,classid,可以直接用 (.) 和 (#) 而不需要放到括号内;
div.container.flow#container Content
a(class="btn", href=http://www.mamicode.com/"javascript:;", title="submit") Button
html:

<div id="container" class="container flow">Content</div><a href=http://www.mamicode.com/"javascript:;" title="submit" class="btn">Button</a>
对于 classid, 还可以省略 tag 而直接用,最终会自动生成一个 div:

jade:

.content content
#content.content.box content
html:

<div class="content">content</div>
<div id="content" class="content box">content</div>

javascript

  1. Promise 对象用于异步计算,一个 Promise表示一个现在、将来或永不可能可用的值。
  2. -那么,关于lodash的一个用法:
    .defaultsDeep(object, [sources]):This method is like .defaults except that it recursively assigns default properties.比如说:
_.defaultsDeep({ ‘a‘: { ‘b‘: 2 } }, { ‘a‘: { ‘b‘: 1, ‘c‘: 3 } });
// => { ‘a‘: { ‘b‘: 2, ‘c‘: 3 } }
  1. setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。

vuejs指令

vuej指令是vue编程中非常灵活的一个因素所在。
v-if指令可以完全根据表达式的值在DOM中生成或移除一个元素。

  • v-if

如果v-if表达式赋值为false,那么对应的元素就会从DOM中移除;否则,对应元素的一个克隆将被重新插入DOM中

<div id="example-2">
    <p v-if="greeting">Hello!</p>
</div>

<script type="text/javascript">
    var exampleVM2 = new Vue({
        el: ‘#example-2‘,
        data: {
            greeting: true
        }
    })
</script>

上面这种写法可能是最最正宗的写法,设定一个el ,创建一个vue对象。

  • v-show
    v-show指令是根据表达式的值来显示或者隐藏HTML元素。当v-show赋值为false时,元素被隐藏。查看DOM时,会发现元素上多了一个内联样式style=”display:none”。

    一般来说,v-if有更高的切换消耗,而v-show有更高的初始渲染消耗(可能稍微改变的开销并不大了)。因此,如果需要频繁的切换,则使用v-show较好;如果在运行时条件不大可能改变,则使用v-if较好。

  • v-else
    v-else就是JavaScript中的else的意思,它必须跟着v-if或者v-show使用。
  • v-model
    v-model指令用来在input、select、text、checkbox、radio等表单控件元素上创建双向数据绑定的。根据控件类型v-model**自动选取正确的方法更新元素**。

使用$index来获得相应的数组索引。
v-text指令可以更新元素的textContent.
v-html指令更新元素的innerHTML。
v-bind指令用于响应更新HTML特性,将一个或者多个attribute,或者一个组件prop动态绑定到表达式..

  • v-bind
a v-bind:href="http://www.mamicode.com/url"></a>

<!-- 缩写 -->
<a :href="url"></a>
  • 子组件的绑定
在绑定prop时,prop必须在子组件中声明。可以用修饰符指定不同的绑定类型。修饰符为:
.sync——双向绑定,只能用于prop绑定。
.once——单次绑定,只能用于prop绑定
  • v-on指令用于绑定事件监听器。事件类型由参数指定。

    如果访问原始DOM事件,可以使用$event传入方法。

    ```
    <button v-on:click="doThis(‘hello‘,$event)"></button> <!--缩写-->
    <!-- 阻止单击事件冒泡 -->
    <a v-on:click.stop="doThis"></a>
    <!-- 提交事件不再重载页面 -->
    <form v-on:submit.prevent="onSubmit"></form>
    ```
    

    那么什么是单击事件冒泡呢?

在一个对象上触发某类事件(比如单击onclick事件),如果此对象定义了此事件的处理程序,那么此事件就会调用这个处理程序,如果没有定义此事件处理程序或者事件返回true,那么这个事件会向这个对象的父级对象传播,从里到外,直至它被处理(父级对象所有同类事件都将被激活),或者它到达了对象层次的最顶层,即document对象(有些浏览器是window)

自定义指令

  • 那么怎么写自定义指令呢?
    这个问题怎么解决 比如在vue.js 1.0中 http://v1-cn.vuejs.org/guide/custom-directive.html
    除了内置指令,Vue.js 也允许注册自定义指令。自定义指令提供一种机制将数据的变化映射为 DOM 行为。

可以用 Vue.directive(id, definition) 方法注册一个全局自定义指令,它接收两个参数指令 ID 与定义对象。也可以用组件的 directives 选项注册一个局部自定义指令,例子:

<div id="demo" v-demo:hello.a.b="msg"></div>
Vue.directive(‘demo‘, {
  bind: function () {
    console.log(‘demo bound!‘)
  },
  update: function (value) {,
    this.el.innerHTML =
      ‘name - ‘       + this.name + ‘<br>‘ +
      ‘expression - ‘ + this.expression + ‘<br>‘ +
      ‘argument - ‘   + this.arg + ‘<br>‘ +
      ‘modifiers - ‘  + JSON.stringify(this.modifiers) + ‘<br>‘ +
      ‘value - ‘      + value
  }
})
var demo = new Vue({
  el: ‘#demo‘,
  data: {
    msg: ‘hello!‘
  }
})

这下可以回到我在此文开头的时候抛出来的问题了:

一个项目里面使用到一个灵活的jsoneditor,通过调研发现vuejs并没有直接的组件可以用,但是发现javascript有一个很好用的叫做jsoneditor的组件,那么我们如何在一个vue.js的工程里面加入这个jsoneditor?
虽然我首先调研得到的可用的组件是vue-json-tree-view,用法

npm install --save vue-json-tree-view
import TreeView from "vue-json-tree-view"
Vue.use(TreeView)

但是这个组件的功能还是不够完善。
顺便提一句:
Vue.use(plugin)用于安装vuejs的插件,use是Vue提供的一个全局的api,另外类似的api还有set,directive,filter等。
set:

参数:

{Object} object
{string} key
{any} value
返回值: 设置的值.

用法:

设置对象的属性。如果对象是响应式的,确保属性被创建后也是响应式的,同时触发视图更新。这个方法主要用于避开 Vue 不能检测属性被添加的限制。

注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象

这是和vue的相应式原理对应的一个api。
受现代 JavaScript 的限制(以及废弃 Object.observe),Vue 不能检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
Vue 不允许在已经创建的实例上动态
Vue.set(vm.someObject, ‘b’, 2)
您还可以使用 vm.setVue.setthis.<script type="math/tex" id="MathJax-Element-1">set 实例方法,这也是全局 Vue.set 方法的别名: this.</script>set(this.someObject,’b’,2)添加新的根级响应式属性(root-level reactive property)。然而它可以使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上
有时你想向已有对象上添加一些属性,例如使用 Object.assign() 或 _.extend() 方法来添加属性。但是,添加到对象上的新属性不会触发更新。在这种情况下可以创建一个新的对象,让它包含原对象的属性和新的属性:
// 代替 Object.assign(this.someObject, { a: 1, b: 2 })
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
- Vue 异步执行 DOM 更新

vuejs的编程原理和响应式原理

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 *
 * @param {Vue} vm
 * @param {String|Function} expOrFn
 * @param {Function} cb
 * @param {Object} options
 *                 - {Array} filters
 *                 - {Boolean} twoWay
 *                 - {Boolean} deep
 *                 - {Boolean} user
 *                 - {Boolean} sync
 *                 - {Boolean} lazy
 *                 - {Function} [preProcess]
 *                 - {Function} [postProcess]
 * @constructor
 */

watcher是用来调动callback函数的(当检测到监测的表达式的值发生变化的时候),可以选择的监控的options参数有:
twoWay
deep
lazy等
就同前面那样描述到的,这个对于$watch() 和指令来说,同样是生效的。

响应式原理就是因为指令和数据有绑定watcher对象。把一个普通的js对象传给Vue实例的data,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter.用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的
.

解决问题

update

当只需要 update 函数时,可以传入一个函数替代定义对象:

Vue.directive(‘my-directive‘, function (value) {
  // 这个函数用作 update()
})

使用自定义指令操作dom

在github上面有jsoneditor的使用方法:

   var container = document.getElementById("jsoneditor");
        var options = {};
        var editor = new JSONEditor(container, options);

        // set json
        var json = {
            "Array": [1, 2, 3],
            "Boolean": true,
            "Null": null,
            "Number": 123,
            "Object": {"a": "b", "c": "d"},
            "String": "Hello World"
        };
        editor.set(json);

但是当我在vue.js中想要引入jsoneditor却很为难。就像vue.js是数据响应式那样,vue.js并没有操作dom的能力。所以要怎么办呢?
使用自定义指令可以。它有操作dom的能力!可以使用this.el来表示上面html中的container

import Vue from ‘vue‘
import JSONEditor from "jsoneditor"

export default {
   update: function(value){
    console.log("arg:",this.arg)
    var options ={mode: this.arg ,search:true}
    var editor = new JSONEditor(**this.el**, options);

    editor.set(value.data);

  }

}

这样创建的指令jsoneditor就可以被用于vue的元素中,比如div(v-jsoneditor)。
这样,基本功能就完成了

深入用法

editor.set(json);
editor.expandAll();
还有collapse等用法。
schema

监听事件 实现双向数据绑定

遇到了一个问题,我觉得可能需要深入了解下dom的事件:
比如input click

schema验证

Ajv: Another JSON Schema Validator

The fastest JSON Schema validator for node.js and browser.

那么怎么利用ajv编程呢?
比如:
支持type参数,properties参数等。

var schema = {
        "title": "Example Schema",
        // "type": "obje",
        "properties": {
          "type": { "enum": value.schema },
          "动作": {"enum": value.schema2 },

          ‘opAction‘: {"enum":value.ops},
          // ‘constraints‘:{
          //   ‘动作‘: {"enum":value.ops},


          // }



        },

type keyword requires that the data is of certain type (or some of types). Its value can be a string (the allowed type) or an array of strings (multiple allowed types).
也就是说type可以写一个,也可以写一组。
Type can be: number, integer, string, boolean, array, object or null.

schema: { "type": ["number", "string"] }

valid: 1, 1.5, "abc", "1"

invalid: [], {}, null, true

All examples above are JSON schemas that only require data to be of certain type to be valid.

而嵌套的:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "TextLinks",
    "description": "文字链接",
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "text": {
                "type": "string",
                "title": "文字"
            },
            "href": {
                "type": "string",
                "title": "链接地址(URL)"
            }
        }
    }
}

所以其实也没有那么难的。

semantic ui

一个从css的优先级入手
另一方面从semantic ui的css定义入手。。

<td> 标签定义 HTML 表格中的标准单元格。
HTML 表格有两类单元格:
表头单元 - 包含头部信息(由 th 元素创建)
标准单元 - 包含数据(由 td 元素创建)
td 元素中的文本一般显示为正常字体且左对齐。

而在semantic ui中:


.ui.table td > .ui.ribbon.label {
  left: calc( -0.78571429em  -  1.2em );
}

.ui.table td > .ui[class*="right ribbon"].label {
  left: calc(100% +  0.78571429em  +  1.2em );
  padding-left: 0.833em;
}
.ui.table {
  width: 100%;
  background: #FFFFFF;
  margin: 1em 0em;
  border: 1px solid rgba(34, 36, 38, 0.15);
  box-shadow: none;
  border-radius: 0.28571429rem;
  text-align: left;
  color: rgba(0, 0, 0, 0.87);
  border-collapse: separate;
  border-spacing: 0px;
}

另外,transition是用来做过度效果的:

<!DOCTYPE html>
<html>
<head>
<style> 
div
{
width:100px;
height:100px;
background:blue;
transition:width 2s;
-moz-transition:width 2s; /* Firefox 4 */
-webkit-transition:width 2s; /* Safari and Chrome */
-o-transition:width 2s; /* Opera */
}

div:hover
{
width:300px;
}
</style>
</head>
<body>

<div></div>

<p>请把鼠标指针移动到蓝色的 div 元素上,就可以看到过渡效果。</p>

<p><b>注释:</b>本例在 Internet Explorer 中无效。</p>

</body>
</html>

然后:
关于head的一些属性


/* Table Content */

.ui.table th,
.ui.table td {
  -webkit-transition: background 0.1s ease, color 0.1s ease;
  transition: background 0.1s ease, color 0.1s ease;
}

/* Headers */

.ui.table thead {
  box-shadow: none;
}

.ui.table thead th {
  cursor: auto;
  background: #F9FAFB;
  text-align: inherit;
  color: rgba(0, 0, 0, 0.87);
  padding: 0.92857143em 0.78571429em;
  vertical-align: inherit;
  font-style: none;
  font-weight: bold;
  text-transform: none;
  border-bottom: 1px solid rgba(34, 36, 38, 0.1);
  border-left: none;
}

.ui.table thead tr > th:first-child {
  border-left: none;
}

.ui.table thead tr:first-child > th:first-child {
  border-radius: 0.28571429rem 0em 0em 0em;
}

.ui.table thead tr:first-child > th:last-child {
  border-radius: 0em 0.28571429rem 0em 0em;
}

.ui.table thead tr:first-child > th:only-child {
  border-radius: 0.28571429rem 0.28571429rem 0em 0em;
}

那么关于body的以一些属性呢:

/* Table Row */

.ui.table tr td {
border-top: 1px solid rgba(34, 36, 38, 0.1);
}

.ui.table tr:first-child td {
border-top: none;
}

/* Table Cells */

.ui.table td {
padding: 0.78571429em 0.78571429em;
text-align: inherit;
}
padding表示元素的内边距,感觉这个padding的值还比较重要,,
另外还需要关注的一个值是:

margin用于设置外边框:

<html>
<head>
<style type="text/css">
p.margin {margin: 2cm 4cm 3cm 4cm}
</style>
</head>

<body>

<p>这个段落没有指定外边距。</p>

<p class="margin">这个段落带有指定的外边距。这个段落带有指定的外边距。这个段落带有指定的外边距。这个段落带有指定的外边距。这个段落带有指定的外边距。</p>

<p>这个段落没有指定外边距。</p>

</body>

</html>

semantic ui的 .filed是设置了下边框的。


.ui.form .field {
  clear: both;
  margin: 0em 0em 1em;
}

.ui.form .field > label {
  display: block;
  margin: 0em 0em 0.28571429rem 0em;
  color: rgba(0, 0, 0, 0.87);
  font-size: 0.92857143em;
  font-weight: bold;
  text-transform: none;
}

所以也会导致有一些不正常的地方在下边框的地方。
这个问题可以慢慢解决。

先总结一个关于css优先级的汇总:

重样式(Multiple Styles):如果外部样式、内部样式和内联样式同时应用于同一个元素,就是使多重样式的情况。
一般情况下,优先级如下:
(外部样式)External style sheet <(内部样式)Internal style sheet <(内联样式)Inline style

解释:

1.  内联样式表的权值最高 1000;

2.  ID 选择器的权值为 100

3.  Class 类选择器的权值为 10

4.  HTML 标签选择器的权值为 1
<html>
  <head>
    <style type="text/css">
        #redP p {
             /* 权值 = 100+1=101 */
             color:#F00;  /* 红色 */
        }

        #redP .red em {
             /* 权值 = 100+10+1=111 */
             color:#00F; /* 蓝色 */

        }

        #redP p span em {
             /* 权值 = 100+1+1+1=103 */
             color:#FF0;/*黄色*/
        }
    </style>
  </head>
  <body>
     <div id="redP">
        <p class="red">red
           <span><em>em red</em></span>
        </p>
        <p>red</p>
     </div>
  </body>
</html>

semantic UI是github上面的一个开源项目,

semantic ui的特点: 50多个UI 元素(element) 3000多个CSS变量 3 Levels of variable
inheritance(3层的变量继承?)(变量继承,这个还需要花点时间去学习) Built with EM values for
responsive design(EM值?) Flexbox friendly (灵活的布局)

比如button
field inputs这些 做出的内容确实就非常漂亮 还有icons

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    前端编程之路