oylp

oylp 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 someoneoylp.github.io/ 编辑
编辑

只是一个普普通通喜欢吃脂肪还长肉的人

个人动态

oylp 赞了文章 · 8月11日

webpack入门必备知识(一)

一、webpack的大致构建流程

  1. 初始化参数:在package.json读取对应的命令配置,得出最终参数
  2. 开始编译前的准备:实例化Compiler,加载所有plugin,执行对象的run方法并开始编译。接着根据配置中的entry找到所有入口文件
  3. 编译模块:从入口文件出发,调用所有loader对模块进行编译,再找到模块依赖,重复上述步骤知道所有入口文件都经过处理。在loader处理完所有模块后,得到的每个模块以及它们之间的依赖关系图
  4. 导出:根据入口和模块之间的依赖图,将代码组成一个个包含多个模块的chunk文件加入到输出列表,然后根据配置确定的输出路径和文件名,将chunk导出到项目目录中

流程图可以参考这里
webpack打包的规则是,一个入口文件对应一个bundle,该bundle包括了入口文件模块和其他依赖模块,按需加载的模块或者需要单独加载的模块则是分开打包生成其他的bundle。在这些bundle中,有一个较为特殊,就是manifest.bundle.js,被称作webpackBootstrap,它是最先加载的,负责解析webpack生成的其他bundle。

package.json

  1. npm init 初始化文件
  2. script里 可以 NODE_ENV=development 可以设置环境变量,默认是开发模式。

(我们就将NODE_ENV绑定到了process.env上,并且按以上配置,它所引入的脚本中访问到process.env.NODE_ENV。那我们为什么要设置这个?一般是用做环境判断 if(process.env.NODE_ENV ===xxx)
另外,使用cross-env可以实现设置时跨平台

  1. 安装依赖

webpack webpack-cli webpack-devserver

二、webpack总体配置

常用的配置

{
    🌺mode: development/production
    
    🌺entry:  【string | stringArray | object 可以多入口】源文件
            (如果只是string,默认打包后产物叫main.js, object的话产物为键名)
             
    🌺output: { // 输出
        path,
        
        filename: '[name].[chunkhash].js’, //  打包同步代码
        
            chunkFilename: 同上 // 打包异步代码( https://www.cnblogs.com/skychx/archive/2020/05/18/webpack-filename-chunkFilename.html)
            
            publicPath: '//7.url.cn/edu/act/' // 构建出的资源需要异步加载时,加载的cdn地址前缀(小心别写错了导致资源404)
            
            crossOriginLoading: 用于配置JSONP这个异步插入的标签的 crossorigin 值(如anonymous)
            
        library: 导出库的名字
        
            libraryTarget: 这个一般是封装第三方库时。🌰例如可以配置'commonjs2', 'umd’...
    }

    🌺module: { //【对象】如何处理模块,通过一堆loader
            noParse:不解析的部分。 // 如/moment(?!-)|node_modules\/chart\.js/, (忽略对部分没采用模块化的文件的递归解析和处理,这样做的好处是能提高构建性能)
            
        rules: [ //【对象数组】
            {
                // ****条件匹配****
                test: 正则匹配文件名或正则数组。🌰例如 [ /\.jsx?$/,/\.tsx?$/],
                
                include: 包含哪些文件(路径)或路径数组
                🌰例如 [
                                       path.resolve(__dirname, 'src'),
                                       path.resolve(__dirname, 'tests'
                                 ],
                                 
                exclude: 排除哪些文件
                🌰例如 path.resolve(__dirname, 'node_modules') ,因为node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换。)
                 
                // **** 应用规则(里面的每一项就是一个loader)****
                use: [ //【字符串数组(则由后往前执行) | 对象数组】
                            {
                                    loader: ‘babel-loader’,
                                    
                                    options:{
                                      cacheDirectory:true,
                                   },
                                    
                                   enforce: 调节这个loader的执行顺序。
                                   🌰例如'post'把该 Loader 的执行顺序放最后
                            }
                 ]
            }
                  ]
    }
    
    🌺resolve: { // 翻译为解析,如何寻找模块
        modules: //【数组】解析第三方模块时应该搜索的目录列表  
            🌰例如 [
                path.resolve(rootDir, 'src'),
                path.resolve(rootDir, 'src', 'node_modules'),
                path.resolve(rootDir, 'node_modules'),
                path.resolve(rootDir, 'src/edu_modules/ke-common/dist/es6'),
            ],

        alias: 【对象】别名
        
        extensions: 【数组】导入时自动帮你补齐列表里的后缀名去找那个文件。
                 🌰例如 ['.jsx', '.js', '.ts', '.tsx'] 
        
        mainFields: 【字符串数组】引用第三方模块是引用他的哪个版本/优先级。
                 🌰例如 ['jsnext:main', 'browser', 'main']  表示ES6, 浏览器,es5
    }
    
    🌺plugins: // 插件数组
        🌰例如 new webpack.DefubePlugin({  创建全局变量
            a: xx
        })
        
    🌺devServer: { // webpack-dev-server配置
        hot: true 模块热替换。
        (DevServer 默认的行为是在发现源代码被更新后会通过自动刷新整个页面来做到实时预览,开启模块热替换功能后将在不刷新整个页面的情况下通过用新模块替换老模块来做到实时预览。)
        
        host:ip
        
        port: 端口
        
        historyApiFallback: spa中如果刷新页面404,配置这个就可以指定404时跳去什么页面
    }
}

其他配置

{
    🌺target 构建出正对不同运行环境的代码,默认是web
    
    🌺devtool: source-map 开发调试的配置选项(🌰例如, 开发模式用cheap-module-eval-source-map, 既可以生成sourcemap耗时又最短,生产模式用hidden-source-map)
    
    🌺watch: true 监听文件更新,文件改变后重新编译。平常默认是关闭的,但是使用devServer时,默认开启。
    
    🌺externals: 不用重复再去打包的依赖.
            🌰例如,一个npm包引用的react包不应该打到包里),或者cdn引入的资源不需要打包
             {
               jquery: 'jQuery',
                react: 'React',
                'react-dom': 'ReactDOM',
                // 'react-dom/server': 'ReactDOMServer',
              },

    🌺optimization: //【对象】优化(webpack4之后不用自己配置,)
}

三、module的常用loader及使用

3.1 babel 把ES6转换为ES5、ES3 (babel-loader)

  • 最新ES 语法:比如,箭头函数
  • 最新ES API:,比如,Promise
  • 最新ES 实例方法:比如,String.protorype.includes

参考文章:

3.1.1 babel 6部分配置(.babelrc)

modules:默认使用 "commonjs"。即将代码中的ES6的import转为require。

  • @babel/preset-es2015:按照ES6标准编译
  • @babel-stage-x: 处理尚处在提案语法的插件集合,babel@7已经不推荐使用, stage-0的功能范围最大(通常使用建议配到@babel/preset-stage-2)
  • @babel-polyfill: api只能被polyfill
  • @babel-runtime + 插件@babel-plugin-transform-runtime配套使用。提取所有页面所需的helper函数到一个包里,避免重复注入(如打包后会变成require('babel-runtime/helpers/createClass'))

🌰事例:

{
  "sourceMaps": true,
  "presets": ["es2015","stage-2","react"], // 源码使用了哪些语法特性,需要提供支持.其实是一组plugins的集合
   "plugins": [ // 插件,控制如何转换代码
    [
      "transform-runtime", // transform-runtime 默认会自动的为你使用的 ES6 API 注入 polyfill, polyfill 的注入应该交给模块使用者,因为使用者可能在其它地方已经注入了其它的 Promise polyfill 库。所以关闭该功能
      {  "polyfill": false  } 
    ]
   ],
}

3.1.2 babel 7的部分配置(babel.config.js)

简单说,大部分babel 6的依赖命名从-改成了/

  • @babel/core 必需 根据我们的配置文件转换代码
  • @babel/present-env 相当于一个处理es6+规范语法的插件集合。 根据运行环境为代码做相应的编译, 包括了所有ECMA标准(bebal-preset-esxxxx),不包含state-x一些列插件,只支持最新推出版本的JavaScript语法(stage-4),如果需要支持其他stage需要额外引入。stage0表示最没定稿的新特性

image

  • @babel/runtime + @babel/plugin-transform-runtime 配套使用。 沙箱,提取所有页面所需的helper函数到一个包里,避免重复注入

🌰事例:

{
    // "plugins": ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-destructuring"]    // 一个个插件不如使用preset-env就可以一次性全部引入
    // "presets": ["@babel/preset-env"]
    
    "presets": [
    "@babel/preset-env", 
    {
               "modules": false,
               "useBuiltIns": "entry", // 按需引入:在入口处把所有ie8以上浏览器不支持api的polyfill引入进来
                                       // 'usage',其功能更为强大,它会扫描你的代码,只有你的代码用到了哪个新的api,                        它才会引入相应的polyfill。【试验状态,谨慎使用】
               "targets": "ie >= 8" // 只有ie8以上版本浏览器不支持的语法才会被转换
    }
    '@babel/preset-react',
    '@babel/preset-typescript',
    ],
    "plugins": [
        "@babel/plugin-syntax-dynamic-import", //动态导入
        ["@babel/plugin-transform-runtime", {
            "corejs": 2
        }]                                // 所有的helper函数抽离到一个包中,由所有的文件共同引用则可以减少可观的代码量。也可以为你的代码创建一个sandboxed environment(沙箱环境),这在你编写一些类库等公共代码的时候尤其重要。
    ] 
}

3.2 TS转换成js (awesome-typescript-loader)

🌰tsconfig事例:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "baseUrl": ".",
    "paths": {
      "assets/*": ["src/assets/*"],
      "components/*": ["src/components/*"],
    },
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "noImplicitAny": true,
    "lib": ["es6", "es2017", "dom", "dom.iterable", "scripthost"],
    "target": "es5", // 编译出的代码使用ES几
    "module": "ESNext", // 编译出的代码采用的模块规范
    "jsx": "react",
    "noUnusedLocals": true,
    "typeRoots": ["node_modules/@types", "src/assets/types"],
    "sourceMap": true,
    "importHelpers": true, // 避免helper函数重复引入
    "types": ["jest", "node"]
  },
  "include": ["src/**/*"],
  "exclude": ["src/edu_modules/**/*", ".template/**/*"]
}

3.3 react解析

实现:

  • 方法1:结合babel,config里直接presets配置react
  • 方法2:结合ts,config里配置"compilerOptions": {
    "jsx": "react" // 开启 jsx ,支持 React
    }

但是需要入口文件改为.tsx,并且安装types/react @types/react-dom

🌰方法1事例:

{
  "presets": ["es2015", "stage-2", "react"],
  "plugins": [
    "react-hot-loader/babel",
    "transform-function-bind",
    "transform-class-properties",
    "transform-export-extensions",
    ],
    "env": {
      "backend": {
        "plugins": [
          [ "webpack-loaders",
            { "config": "./webpack.config.babel.js"
            , "verbose": true
            }
          ]
        ]
      }
    }
  }
}
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        user: {
          loader: 'babel-loader?cacheDirectory=true', // cacheDirectory用于缓存babel的编译结果,加快重新编译的速度
          options: {
            presets: ['@babel/preset-env'],
            plugins: [
              '@babel/plughin-transform-runtime',
              '@babel/plugin-transform-modules-commonjs'
            ]
          }
        }
      }
    ]
  }
}
  • 对于普通项目,可以直接使用preset-env配置polyfill
  • 对于类库项目,推荐使用@babel/runtime,需要注意一些实例方法的使用

3.4 样式相关的loader

loader的配置是从右往左的,且配置时可以省略“-loader”
(如 style-loader css-loader postcss-loader/sass-loader)

  • sass-loader // 解析sass为css
  • less-loader // 解析less为css
  • css-loader // 解析css。如@import,url()的导入语句 + 压缩css
  • style-loader, // 将解析后的样式嵌入js代码中。(将require引入的样式嵌入js文件中,有好处也有坏处。好处是减 少了请求数,坏处也很明显,就是当你的样式文件很大时,造成编译的js文件也很大。如果css文件较大时,可以可以用extract-text-webpack-plugin分开css文件和js,这样css和js并行加载加快加载速度)
  • postcss-loader + autoprefixer插件,可以为我们的样式添加前缀-webkit-,-ms- 提高兼容性

静态资源相关loader

  • url-loader 将图片转换为base64后注入到js和css中。(注意:会影响打包后文件大小,从而影响加载速度)
🌰事例:
{
        test: /\.png$/,
        use: [{
          loader: 'url-loader',
          options: {
            // 30KB 以下的文件采用 url-loader
            limit: 1024 * 30,
            // 否则采用 file-loader,默认值就是 file-loader 
            fallback: 'file-loader',
          }
        }]
      }
  • file-loader CSS 中导入图片的语句替换成正确的地址,并同时把文件输出到对应的位置。(比如css和js中把url相对路径的图片输出到dist文件夹,并取另一个带hash的名字)
🌰事例:
{
        test: /\.(gif|png|jpe?g|eot|woff|ttf|pdf|svg|xlsx)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[path][name].[ext]',
            },
          },
        ],
      },
  • svg可以用图片用的file-loader和url-loader, 也可以用raw-loader ,svg-inline-loader。后面两种是应用于js引入时,输出是<svg xxx>

参考文章:
https://github.com/zhengweike...

四、plugin插件

  • webpack-merge:提取公共配置,分离生产环境和开发环境的配置文件。clean-webpack-plugin:每次编译前自动清空dist目录
  • html-webpack-plugin:从html模板自动生成最终的html,它的AutoWebPlugin插件可以生成多页面应用
  • defineplugin:声明process.env.NODE_ENV的值
  • extract-text-webpack-plugin:从js提取css到css文件
  • optimize-css-assets-webpack-plugin:合并相同的css样式文件
  • css-split-webpack-plugin:拆分过大的csss文件
  • mini-css-extract-plugin:分开打包css文件
  • webpack-md5-hash:webpack自身生成文件hash值的方法是不确定的,为了确保hash值是根据文件内容生成的,可以使用该插件

五、resolve优化

  • resolve.extensions:使用require或import引入文件时可以省略后缀:
  • resolve.alias:简化引用路径:
  • resolve.mainFields:通过第三方插件的package.json中main字段(大多数第三方模块都采用这个字段)中的地址来引用文件
  • resolve.modules:限制引入第三方包的范围
  • resolve.mainFiles:在一定范围下默认优先查找的文件名

六、其他优化

  • optimization抽离公共组件代码
  • DllPlugin减少基础模块的编译次数
  • IgnorePlugin忽略某些模块打包
  • CopyWebpackPlugin生产环境中将提前构建的包同步到dist
  • uglifyjs去js代码压缩
  • web-webpack-plugin使用静态资源CDN
  • HappyPack进行多进程打包
  • webpack-bundle-analyzer打包结果进行分析

具体可以阅读《深入浅出webpack》

后记

因为webpack的知识点很多也很细,推荐阅读《深入浅出webpack》
这本书系统性,渐进性地讲了webpack的基础配置,项目实战,打包优化,插件及运行原理。如果想快速学习webpack并入门可以看看这本书,会比官网看起来更加有重点一些,但注意的是有些配置会有点过时了。
然后初学者可以多上手试试,这样印象更深刻也更能理解,我也在学习中~

查看原文

赞 1 收藏 0 评论 0

oylp 提出了问题 · 3月23日

子元素内容超过父元素不显示

父元素P中存在ABC三个元素,宽度都不确定,都是动态的。当ABC宽度加起来小于父元素时,按照ABC顺序显示
image.png
当ABC宽度加起来超过P是,按照AC顺序展示,B不显示
image.png

思路:

  1. 根据设置的样式,渲染之前计算好宽度,如果超过,渲染的时候不渲染B
  2. 渲染之后,获取DOM的宽度,根据结果设置B是否渲染

第一个就是受样式影响比较大,不好维护
第二个体验不是很好,页面可能闪动下

有没有更好的解决方案呢?

关注 2 回答 1

oylp 赞了文章 · 3月23日

十分钟了解git那些“不常用”命令

本文主要是介绍git不常用初期不太会用的命令,希望你看了能理解这些命令的使用,并在平时使用过程中一点点地刻意进行练习,逐步熟练并知道何时需要用到这些命令去解决你的问题。
( 我也在不断熟练中:D

基础命令

如果你还是刚刚接触git命令,还不清楚 仓库工作流分支提交 的童鞋可以先看下 git使用简易指南,这个应该是我初学git看的第一份且收藏至今的指南了~ 图解很清晰易懂,真10分钟入门的资料:D

然后你会发现如下基础命令将会成为你之后几乎每天都要用到的80%的命令

  • git clone git@github.com:nohosts/nohost.git 克隆远程仓库的内容到本地
  • git pull origin master 获取远程分支master并merge到当前分支
  • git branch -a 查看全部分支(远程+本地)
  • git checkout -b bugFix新建bugFix,并切换到到此分支。(如果分支已存在则去掉-b即可)
  • git status 查看当前~~~~版本状态(是否修改)
  • git add . 增加当前子目录~~~~下所有文件更改至暂存区
  • git commit -m 'xxx' 提交暂存区的修改至本地的版本库, 修改备注为xxx
  • git push 将本地版本推送到远程分支
  • git tag v1.0 dfb02e6e4f2f7b573337763e5c0013802e392818 增加v1.0的tag到某个提交上
  • git merge testBranch 合并testBranch分支至当前分支`
  • git stash 暂存本地的当前修改,将本地代码重置为HEAD状态。(如果需要取出修改,命令后加一个pop即可)
  • git log 显示提交日志(如果想每个提交信息显示在一行,可以加上--pretty=oneline)
  • git show dfb02e6e4f2f7b573337763e5c0013802e392818显示某个提交的详细内容
  • git reset --hard HEAD 将当前版本重置为HEAD

注意这两个命令的区别

git pull = fetch + merge

git pull --rebase = fetch + rebase

“不常用”命令

一、git rebase 变基

在 Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase。 在本节中我们将学习什么是“变基”,怎样使用“变基”,并将展示该操作的惊艳之处,以及指出在何种情况下你应避免使用它。——git-scm变基

说明:后面的举例每个 分支 都有不同的颜色,*前缀 表示现在所处的分支,而 commitid 都由C0、C1、C2代替每一个提交的哈希值,箭头 表示分支的继承

我们之前整合分支用的最多的就是merge了,那merge和rebase有什么区别呢?

1. merge

merge 合并两个分支时会产生一个特殊的提交记录,它有两个父节点。简单说就是:“我要把这两个父节点本身及它们所有的祖先都包含进来。”
git checkout master; git merge bugFix

下图中左、右两张图分别是执行如下代码前后的样子:

merge-1

可以看出来,红色圈圈是最主要的改变—— merge 合并分支后,会在master分支上 新增一个C4提交 ,而C4提交里面有master和bugFix代码库所有的修改。

此时的bugFix代码还没和master 同步(颜色不同),我们还需要执行如下代码:

git checkout bugFix; git merge master

merge-2

2. rebase

rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。它的优势就是可以创造更线性的提交历史。
git checkout bugFix; git rebase master

下图中左、右两张图分别是执行代码前后的样子:

变基-1

bugFix 分支里的内容通过 rebase 直接 复制 到 master 分支上。现在 bugFix 分支上的工作在 master 的最顶端,同时我们也得到了一个更 线性 的提交序列。

注意:提交记录 C3 依然存在(树上那个半透明的节点),而 C3' 是我们 rebase 到 master 分支上的 C3 的 副本(内容是一样的,只是commitid更新了)。(如果master和bugFix之间没有其他commit,rebase后commitid不会更新。如果master已经有了自己新的commit,此时rebase后commitid就会更新。)

此时master还没有和bugFix 同步(颜色不同),我们还需要执行如下代码:

git checkout master; git rebase bugFix

变基-2.png

由于bugFix继承自master,所以 Git 只是简单的把master分支的引用向前移动了一下而已。

3. rebase的延伸用法

3.1 省去切换分支即可rebase

git rebase targetBranch originBranch

表示切换到originBranch,然后执行git rebase targetBranch

3.2 修改某几次提交

git rebase -i commitid

变基-3.png
如上图标注的,传的commitid为你想修改的提交的 前一个commitid。执行命令后进入vi模式,会提示你一些操作命令(p、r、e...)你只需要在最上方修改默认的pick为你想要的操作,然后退出并wq保存即可生效。

具体操作:

  • pick 使用(啥也没变)
  • reword 使用并修改commit msg, 改后commit id也会更新
  • edit 使用并编辑commit时的文件
    编辑后git add . 然后git commit —amend还可以更新最新的commit msg。 git rebase —continue 把后面的内容加进来并解决冲突, 最后提交。最新的commit id也更新
  • squash 合并commit
    选择最新的commit去合并,然后continue发现这一次和上一次的commit msg都有,你可以删除只留下想要的也可以进行修改 然后 continue和push。如果你不删的话会发现全部文本行都组成了一个多行的commit msg
    如果commit再往前已经没有了 就不能再squash,否则会报错( error: cannot 'squash' without a previous commit )。然后 git rebase --edit-todo 可以继续vi编辑
  • fixup 合并commit到前面而且commit,commit msg也没了
  • drop 删除某个commit

便于理解,补充一个例子,主要是 rebase -i 做了 r,e,s,d操作后产生的结果
image

注意
如果想要恢复这一次rebase操作,则可以执行 git rebase —abort
如果想完全恢复本地分支到远程的状态,可以执行 git reset --hard origin/bugFix ,或者你可以 git reflog 找到对应提交记录回滚,但是有点麻烦

4. rebase需要谨慎使用

当你要改写的commit history还没有被提交到远仓库的时候,也就是说,还没有与他人共享之前,commit history是你私人所有的,那么想怎么改写都可以。

而一旦被提交到远程后,这时如果再改写history,那么势必和他人的history长的就不一样了。git push 的时候,git会比较commit history,如果不一致,commit动作会被拒绝,唯一的办法就是带上 -f 参数,强制要求commit,这时git会以committer的history覆写远程分支,从而完成代码的提交。虽然代码提交上去了,但是这样可能会造成别人工作成果的丢失,所以使用 -f 参数要慎重。

所以,在不用 -f 的前提下,想维持树的整洁,方法就是:在 git push 之前,先 git fetch,再 git rebase

4. 总结

  1. 无论是通过变基,还是通过三方合并,整合的最终结果所指向的快照始终是一样的,只不过提交历史不同罢了。 变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起
  2. 在你自己的分支(非他人共享)的分支进行rebase是可以的,但是如果在公共分支rebase修改提交需要谨慎——最好是先 fetch、再 rebase、最后 push

二、git cherry-pick 选择

cherry-pick 可以将提交树上任何地方的提交记录取过来追加到 HEAD 上(只要不是 HEAD 上游的提交就没问题)。`
git checkout master; git cherry-pick C2

下图中左、右两张图分别是执行代码前后的样子:
是不是有点眼熟:D 没错 这个和rebase的效果蛮像的,这两个命令都可以实现复制提交~

cherry-pick.png

三、git reset VS revert 回滚

git revert HEAD是用一次新的commit来回滚之前的commit,git reset 是直接向上移动分支,删除一些commit看上去像从未提交一样。这两者看似达到的效果是一样的,其实完全不同。
git reset HEAD~1 
git revert HEAD

如下所见,图1是初始状态(需要撤回 C2 提交),图2和3 是从图1分别执行 resetrevert 后的结果:
image.png

  1. reset
    执行后,master 分支移回到了 C1;现在我们的本地代码库根本就不知道有 C2 这个提交了
  2. revert
    执行后,在我们要撤销的提交记录 C2 后面多了一个新提交C2',而C2'引入了更改—— 这些更改是用来撤销C2这个提交的。也就是说C2'的状态与C1是相同的。

注意

  • 如果你已经push到线上代码库, reset 删除指定commit以后, 你git push可能导致很多冲突.但是revert 并不会。
  • 如果此回退的分支合并主干分支时,reset 恢复部分的代码依然会出现在历史分支里,但是revert 方向提交的commit 并不会出现在历史分支里。
# 事例
reset后的123 merge了12345 还是12345
revert后的12345(-3) merge了12345 是12345(-3)

四、HEAD^n 和 HEAD~n 相对引用

HEAD 是一个对当前检出记录的符号引用 —— 也就是指向你正在其基础上进行工作的提交记录。HEAD 总是指向当前分支上最近一次提交记录
(如果想看 HEAD 指向,可以通过 cat .git/HEAD 查看, 如果 HEAD 指向的是一个引用,还可以用 git symbolic-ref HEAD 查看它的指向。)

1. 基础使用

  • 使用 ^ 表示向上移动 1 个提交记录。
    n表示第n个父提交,不填默认是1(正上方)
  • 使用 ~<num> 向上移动多个提交记录
    如 ~3

注意:操作符还支持链式操,如HEAD^2~3^

2. 延伸用法
移动分支
可以直接使用 -f 选项让分支指向另一个提交。例如下面的命令会将 master 分支强制指向 HEAD 的第 3 级父提交。

git branch -f master HEAD~3

这次主要就总结了这几种“不常用”git命令,希望大家和我都可以多多练习,让他变成你需要时就可以自如使用的“常用”命令!:D

墙裂推荐一个可视化的git练习网站,很易懂好用~

推荐git系列文章

查看原文

赞 4 收藏 1 评论 0

oylp 发布了文章 · 3月20日

vertical-aline与line-height

基线

baseline:字符x的底部

x-height: 字母x的高度,vertical-aline设置为middle的时候,对齐的是baseline往上1/2的x-height,所以vertical-aline设置为middle不是整正的居中对齐

WechatIMG7.png

line-height

1.内容高度

思考:未设置height的时候,可视高度由什么决定?

.box {
    line-height: 0;
}
.box1 {
    line-height: 12px;
}
.box2 {
    line-height: 12px;
  font-size: 0;
}
<div class="box">
  内容内容
</div>

结果: 内容高度

元素的高度是由line-height撑起来

思考:对于非替换元素,line-height会影响元素高度吗?

.box {
    line-height: 0;
}
.box1 {
    line-height: 120px;
}
<div class="box">
     <img data-original=''>
</div>

结果:替换元素内容高度

2.行距

两行文字之间的间隙
290596268.png

4.line-height属性值

normal:

默认是normal,与字体、浏览器有关,在1.2左右

长度值:

带单位的,直接按照这个值进行计算

数值

最终的计算值是和当前 font-size 相乘后的值

百分比:

最终的计算值是和当前 font-size 相乘后的值。

父元素设置了百分比,子元素继承的不是百分比,而是父元素计算后的值

vertical-align

只能应用于内联元 素以及 display 值为 table-cell 的元素。

1.baseline

基线对齐

2.数值

相对基线上下移动

问题

1.不同字号文字在一起时,内容高度

box { 
  line-height: 32px; 
}
.box > span { 
  font-size: 24px;
}
<div class="box">
    x<span>文字x</span>
</div>

290870080.png

文字默认是基线对齐,不同大小文字的基线对齐的时候,会发生上下唯一,如果唯一距离够大,就会产生意料之外的高度

clip.png

2.思考:box高度是多少

.box { 
  line-height: 32px; 
}
.box > span { 
  font-size: 24px; 
}
<div class="box">
 <span>文字</span>
</div> 

结果:会比32px大一些

290842543.png

这是因为在 HTML5 文档模式下,每一个“行框盒 子”的前面都有一个宽度为0的空白节点 - 幽灵空白元素,内联特性表现和普通字符相同

等同于如下:

290801298.png
由于空白节点的字体大小和元素内字体大小不同,导致发生位移,使元素高度撑高

幽灵空白节点还会带来很多问题

如图片高度问题:
290739517.png
290765559.png

​ ​

块级元素高度总是会比图片高度大一些,这是因为存在幽灵空白元素,由于vertical-aline默认是baseline,导致空白元素撑高了块级元素。

查看原文

赞 4 收藏 3 评论 0

oylp 收藏了文章 · 2019-10-11

移动端滚动穿透问题完美解决方案

问题

众所周知,移动端当有 fixed 遮罩背景和弹出层时,在屏幕上滑动能够滑动背景下面的内容,这就是著名的滚动穿透问题

之前搜索了一圈,找到下面两种方案
<!-- more -->

css 之 overflow: hidden

.modal-open {
  &, body {
    overflow: hidden;
    height: 100%;
  }
}

页面弹出层上将 .modal-open 添加到 html 上,禁用 html 和 body 的滚动条
但是这个方案有两个缺点:

  • 由于 html 和 body的滚动条都被禁用,弹出层后页面的滚动位置会丢失,需要用 js 来还原

  • 页面的背景还是能够有滚的动的效果

js 之 touchmove + preventDefault

modal.addEventListener('touchmove', function(e) {
  e.preventDefault();
}, false);

这样用 js 阻止滚动后看起来效果不错了,但是也有一个缺点:

  • 弹出层里不能有其它需要滚动的内容(如大段文字需要固定高度,显示滚动条也会被阻止)

上面两个方案都有缺点,今天用英文关键字 google 了一下,才发现原来还有更好的方案

解决方案 position: fixed

body.modal-open {
    position: fixed;
    width: 100%;
}

如果只是上面的 css,滚动条的位置同样会丢失
所以如果需要保持滚动条的位置需要用 js 保存滚动条位置关闭的时候还原滚动位置

/**
  * ModalHelper helpers resolve the modal scrolling issue on mobile devices
  * https://github.com/twbs/bootstrap/issues/15852
  * requires document.scrollingElement polyfill https://uedsky.com/demo/src/polyfills/document.scrollingElement.js
  */
var ModalHelper = (function(bodyCls) {
  var scrollTop;
  return {
    afterOpen: function() {
      scrollTop = document.scrollingElement.scrollTop;
      document.body.classList.add(bodyCls);
      document.body.style.top = -scrollTop + 'px';
    },
    beforeClose: function() {
      document.body.classList.remove(bodyCls);
      // scrollTop lost after set position:fixed, restore it back.
      document.scrollingElement.scrollTop = scrollTop;
    }
  };
})('modal-open');

这样上面3个缺点都解决了,至此滚动穿透就完美解决

完整的示例

document.scrollingElement

因为浏览器获取和设置 scrollTop 存在兼容性,为了简化上面的示例,我直接使用了 document.scrollingElement 这个新标准,对于不支持的浏览器我写了个 polyfill document.scrollingElement.js

参考

原文地址:https://uedsky.com/2016-06/mobile-modal-scroll/
获取最佳阅读体验并参与讨论,请访问原文

查看原文

oylp 赞了回答 · 2019-04-22

prop传递函数与emit调用,使用场景有什么区别

假设你使用的react, 那么通过props传递的话,你可以传递这些东西:

  1. 数据
  2. 组件(函数)

特点就是数据的流动是从父组件到子组件,即数据流动是单向。然而如果使用的是event bus来进行操作,那么你的数据流动可能从父组件到子组件,也可能从子组件到父组件。而且,event bus的操作也是同步的,而非异步。

关注 5 回答 4

oylp 赞了回答 · 2019-04-22

prop传递函数与emit调用,使用场景有什么区别

这个只是两种不同策略

但是 props 传递有几个缺点,

  1. 必须传入,否则子组件调用会有问题
  2. 子组件多了一个依赖项,不是很舒服
  3. 跨级传递太麻烦

相比如事件机制

  1. 不一定要传入,监不监听都无所谓
  2. 子组件只要发出事件即可,可以说是完全解耦
  3. 只要支持事件广播,或是实现 event bus 即可使用

关注 5 回答 4

oylp 发布了文章 · 2019-03-29

跟着element学习写组件

如何使用vue写一个组件库

组件以插件的形式引入使用,当然,也可以直接在页面引入组件文件,两者按需使用。

安装插件:

import Button from './oyButton';
Button.install = function (Vue) {
    Vue.component(Button.name, Button);
}
export default Button;

vue.install源码:

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    # /*检测该插件是否已经被安装*/
    if (plugin.installed) {
      return
    }
    const args = toArray(arguments, 1)
    args.unshift(this)
    if (typeof plugin.install === 'function') {
    #   /*install执行插件安装*/
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    plugin.installed = true
    return this
  }
}

通过源码可知,vue不会重复安装同一个插件。以第一次安装为准

现在,可以在代码中使用组件啦~

<oy-button>我是按钮按钮</oy-button>

以上,是一个非常简单的组件库实现。
现在来看看element组件库是如何实现的。

element组件项目结构

这里重点说下packages目录和src目录

|-- packages  # 组件源码目录
    |-- button # button组件目录,一个组件一个文件,方便管理
        |-- src # 组件实现代码
            |-- button-group.vue  
            |-- button.vue
        |-- index.js # 组件入口文件
|-- src
    |--directives # 实现滚轮优化,鼠标点击优化
    |--locale # 国际化
    |--mixins # 公用逻辑代码
    |--transitions # 样式过度效果
    |--utils # 工具类包
    |--index.js # 源码入口文件

整个目录结构非常清晰。

button模块解析

button模块目录,有一个index.js作为模块入口

import ElButton from './src/button';

ElButton.install = function(Vue) {
  Vue.component(ElButton.name, ElButton);
};
export default ElButton;

在index.js文件中,对组件进行拓展,添加Install方法。

element组件入口文件解析

import Button from '../packages/button/index.js';
const components = [Button]

# 定义一个install方法
const install = function(Vue, opts = {}) {
  locale.use(opts.locale);
  locale.i18n(opts.i18n);

# 将所有的功能模块进行注册。
  components.map(component => {
    Vue.component(component.name, component);
  });

# 注册插件
  Vue.use(Loading.directive);

  const ELEMENT = {};
  ELEMENT.size = opts.size || '';
 # 绑定Vue实例方法
  Vue.prototype.$message = Message;
};

if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}
# 最后,将所有功能模块和install方法一起导出。
# 这样当引入element-ui时,便可以使用vue.use(element-ui)进行注册,即将所有的功能组件进行全局注册。
module.exports = {
  version: '2.3.8',
  locale: locale.use,
  i18n: locale.i18n,
  install,
  Button,
}
module.exports.default = module.exports;

我写的组件与elemnet组件有什么不同

代码实现

1.html语义化

element组件实现时,html基本实现了语义化标签。

  1. 这样在无CSS样子时也容易阅读,便于阅读维护和理解。
  2. 便于浏览器、搜索引擎解析。 利于爬虫标记、利于SEO

标记组件。
Badge 标记组件部分源码:

<!-- sup标签语义:上标文本 -->
<transition name="el-zoom-in-center">
    <sup
    v-show="!hidden && (content || content === 0 || isDot)"
    v-text="content"
    class="el-badge__content"
    :class="{ 'is-fixed': $slots.default, 'is-dot': isDot }">
    </sup>
</transition>

ps: 自己写代码都是div span

2.兼容 v-model

element组件基本都兼容了v-model绑定值,组件使用起来更加舒适~
兼容v-model需要做一下几点:

  1. props中要定义value属性。
  2. 数据变化后,通过事件触发父组件更新数据,同时传递变更后的值。

(如text元素使用input事件来改变value属性 和 checkbox使用的change事件来改变check属性)

input组件源码:

  export default {
    props: {
        # 定义value
        value: [String, Number],
    },
    methods: {
        handleInput(event) {
            if (this.isOnComposition) return;
            const value = event.target.value;
            # 变更数据以后通过input去更新父组件数据
            this.$emit('input', value);
            this.setCurrentValue(value);
        },
    }
  }

3.组件之间传递数据

vue中,存在几种组件之间数据传递的方案:

  1. props
  2. attrs
  3. provide / inject
  4. this.$parent/$this.$children

在日常开发中,父子组件之间数据传递用到比较多的方案是props。当组件层次比较深,就使用attrs来透传数据:

<el-select
    v-model="selectValue"
    v-bind="$attrs"
    v-on="$listeners">
    <template v-if="label && keyValue">
       <el-option 
            v-for="(item, index) in selectList"
            :key="index"
            :label="item[label]"
            :value="item[keyValue]"></el-option> 
    </template>
</el-select>

element组件,在父子组件传递数据也是使用props,但是当组件层次比较深,或者不清楚组件层次时,使用的是:provide / inject

inject: {
    elForm: {
    default: ''
    },
    elFormItem: {
    default: ''
    }
},

关于provide / inject:

“这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效” --vue文档

简单来说,就是父组件通过provide来提供变量,子组件通过inject来引用变量。
vue的inject源码:

# src/core/instance/inject.js
export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

provide是向下传递数据,先获取provide内容,然后传递给vm._provided设置成全局数据。inject会根据选项的 key 数组一层层向上遍历,拿到结果。

provide 相对于props,实现了跨层级提供数据。需要注意的是provide不是响应式的。

方法解释适用场景
props用于接收来自父组件的数据父子组件之间传递数据
provide以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效替代嵌套过深的props,可以理解为一个bus,但只做父组件通知子组件的单向传递的一个属性
attrs包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)父组件传向子组件传的,子组件没有通过prop接受的数据都会放在$attrs中
parent/child获取父/子组件实例

4.组件通信

emit/props传递函数

两者都是通知父组件执行事件的方法,但是有一定的区别:

  1. emit执行的是异步方法,props传递的函数在子组件中执行作为同步函数的形式执行的。
  2. emit无法返回函数结果,props传递的函数可以返回函数结果。
发布订阅

对于组件嵌套过深,element自己实现了一个简易版的发布订阅方式:

function broadcast(componentName, eventName, params) {
    #    组件名称,事件名称,参数
    #  当前组件下的子组件循环
  this.$children.forEach(child => {
    #    获取组件名称
    var name = child.$options.componentName;
    # 如果组件名称和要触发的事件组件名称相同
    if (name === componentName) {
      # 当前子组件,调用$emit方法
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      # 如果没有相等,那就继续查找当前子组件的子组件
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}

组件设计

1.扁平化参数

  1. 传入的参数尽量设计简单点,避免复杂的对象。过于复杂的数据,在watch或者update的情况下,影响性能
  2. 扁平化的props也可以更好的更新数据,重置数据。其次,复杂的数据变更,外部可能会监听不到数据变化。
  3. 如果定义传入的传入数据是一个对象,那组件内部就要做大量的工作,来判断外部擦混入的对象的属性值是否正确,并找出需要的数据内容,增加了组件工作量,也不便组件的后续维护。

2.良好的api接口设计

  1. 保持组件外部提供接口的精简,不要过于泛滥的提供接口。
  2. 组件可定制,如果常量变为 props 能应对更多的情况,那么就可以作为 props从父组件引入。原有的常量可作为默认值。
    按钮组件的样式存在默认样式,但是可以通过type传入类型,定制button组件样式,使组件可以适用更多场景。
export default {
    name: 'ElButton',

    props: {
      type: {
        type: String,
        default: 'default'
      },
    },
  };

3.可扩展性

组件在使用过程中,会不断的优化添加功能,但是组件的内部变更不能影响组件的使用,这就需要组件有很好的扩展性,在一开始,能够提供足够比较友好的接口。

如何实现?
  1. 预留“锚点”

在组件中预留一些“插槽”,使用组件的时候,可以再“插槽”中注入自定义的内容,从而改变组件渲染结果。element组件库在这方面做得很好。
input组件部分源码:

<div>
    <template v-if="type !== 'textarea'">
      <!-- 前置元素 -->
      <div class="el-input-group__prepend" v-if="$slots.prepend">
        <slot name="prepend"></slot>
      </div>
      <input>
       <!-- 前置内容 -->
      <span class="el-input__prefix" v-if="$slots.prefix || prefixIcon" :style="prefixOffset">
        <slot name="prefix"></slot>
      </span>
       <!-- 后置内容 -->
      <span>
        <span class="el-input__suffix-inner">
          <template v-if="!showClear">
            <slot name="suffix"></slot>
          </template>
        </span>
      </span>
       <!-- 后置元素 -->
      <div class="el-input-group__append" v-if="$slots.append">
        <slot name="append"></slot>
      </div>
    </template>
  </div>

Input组件预留了四个“插槽”,允许使用者在前后位置都可以插入内容。

  1. 提供丰富的钩子函数,使用者在数据变化时,能对数据进行相应处理

element组件提供了丰富的钩子函数:

focus() {
    (this.$refs.input || this.$refs.textarea).focus();
},
blur() {
    (this.$refs.input || this.$refs.textarea).blur();
},

4.错误处理

组件要能接受一定的错误使用,能针对可预知的错误使用进行处理。

  1. 给props属性设置多个数据类型,同时保证传入和传出的数据类型相同。
  2. 如果组件中,某个字段是父组件一定要传入的,需要把props属性的require设置为true。
  3. 给重要的prop属性设置默认数据。
  4. 兜底:数据展示或者使用父组件传入内容之前,要先判断数据是否存在。
focus() {
    # 先判断this.$refs.input是否存在,才进行接下来操作,避免数据为空报错情况。
    (this.$refs.input || this.$refs.textarea).focus();
}
查看原文

赞 16 收藏 15 评论 1

oylp 关注了问题 · 2019-03-26

prop传递函数与emit调用,使用场景有什么区别

父组件通过props传递函数给子组件,与子组件通过emit调用父组件方法,除了props传递的是同步执行,emit是异步执行,还有什么区别吗?两者使用场景有什么区别?

关注 5 回答 4

oylp 提出了问题 · 2019-03-26

prop传递函数与emit调用,使用场景有什么区别

父组件通过props传递函数给子组件,与子组件通过emit调用父组件方法,除了props传递的是同步执行,emit是异步执行,还有什么区别吗?两者使用场景有什么区别?

关注 5 回答 4

认证与成就

  • 获得 84 次点赞
  • 获得 19 枚徽章 获得 1 枚金徽章, 获得 2 枚银徽章, 获得 16 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-12-25
个人主页被 841 人浏览