Eslint忽略npm link 文件夹

今天在一个由vue-cli 生成的项目中,通过npm link 引用了另一个文件夹,项目执行起来后就开始报错: ``` error in C:/web_project/packages/contract/lib/clpContract.umd.js

Read More

Webpack 总结

[1-1 脚本加载的问题]

JavaScript只是一个脚本,它自上而下的执行。

有两种方法加载 导入 .js文件(<script src="index.js"></script>),或者在.html文件中编写JavaScript。

问题

  • 没有弹性
  • 太多脚本
  • 不可维护的脚本
  • 作用域,大小,可读性,脆弱性,整体文件。

解决方案?

  • 将每个文件视为立即执行函数表达式(IIFE),代表模块。
let outerScope = 1;
const whatever = (function(dataNowUsedInside){
  let outerScope = 4;
  return{
    someAttribute: "youwant"
  }
})(1)
console.log(outerScope); 
/**
 * console log returns "1". No inner scope leak!
 */

现在我们可以将内部作用域变量的名称与外部作用域变量的名称相同。

我们可以“安全地”组合文件,无需考虑作用域冲突!

为了将文件连接在一起,人们一直在使用诸如“make”,“grunt”,“gulp”,“broccoli”,“brunch”和“stealJS”等工具。

问题!

  • 每次都完全重新build!
  • 死代码(concat无助于跨文件使用。)
  • 很多IIFE都很慢(小模块的成本)
  • 无法动态加载(无法进行延迟加载。

【1-2 模块的历史】

如果要构建规范的Web应用程序,需要预先安装Node.js. 它不仅是我们如何获得包,如何共享包,如何运行工具,检查器,命令的基础。 Node.js是在服务端上运行的JavaScript。 它采用了V8引擎并为其引入二进制文件,为服务器端提供了运行时。 但是,如果没有DOM,如何加载JavaScript?如果Node.js中没有HTML,如何添加脚本标记?

那是CommonJS诞生的时候

[src > index.js]
const path = require("path") // used for built-in Node.js modules
const {add, subtract} = require("./math"); // used modules from another file
const sum = add(5, 5);
const difference = subtract(10, 4);
console.log(sum, difference);
-----------------------------------
[src > math.js] 
// has two named exports {add, subract}
const divideFn = require("./division");
exports.add = (first, second) => first + second;
exports.subtract = (first, second) => first - second;
exports.divide = divideFn;
-----------------------------------
[division.js] // has a default exports "divide"
module.exports = (first, second) => first/second;

我们刚刚没有使用IIFE就解决了作用域问题。 我们现在可以提取多个值,将它们分配给不同模块中的变量而不会出现作用域问题。

这也给了我们一点静态分析。 我们可以在这里准确地说出大部分时间使用了什么。

这是JavaScript开始爆炸的时候:NPM + Node.js +模块 = 大规模分发。

使用NPM,可以轻松运送您想要的任何模块。

NPM是作为包注册表创建的,以便能够将CommonJS node 模块共享为整个生态系统和注册表。

问题!

  • 没有浏览器支持 CommonJS
  • 没有动态绑定(循环引用的问题)
  • CommonJS的解析算法很慢(因为他是同步的)

解决方案?

诸如Browserify和WebMake之类的打包器被创造出来,以允许人们编写CommonJS模块, 打包它们,剥离语句,然后以在Web中工作的相同顺序执行它们。 此外,我们开始看到不同的方法,如Loader。

Browerify (Static)
RequireJS (Loader)
SystemJS (Loader)

问题!

  • 没有静态 异步/延迟 加载
  • CommonJS 臃肿: 过于动态
"CommonJS"
// loading a module
let _ = require('loadash');
// declare the module
module.exports = someValue;

问题是“require”实际上是一个可以传递到任何地方的函数。 所以,有些人滥用了CommonJS语法。他们最终会得到臃肿的打包。 CommonJS对模块格式过于动态,无法真正优化代码。

  • 延迟加载太动态
  • 尴尬的非标准语法(并非真正的模块系统)

[ 1–3. EcmaScript Modules (ESM) ]

解决方案?

经过10年的发展,ESM出现了。最初的规范被称为和谐模块规范。

您不应将其称为2015模块。 ES模块与ES2015完全分开。您可以编写ES3语法并仍然使用ESM。

ESM定义了导入,语法,导出,静态行为,模块加载,浏览器应该如何加载它。

import {uniq, forOf, bar} from 'loadash-es'
import * as utils from 'utils';
export const uniqConst = uniq([1,2,2,4]);

优点

  • 可重复使用,封装,有组织,方便。

问题!

  • Nodejs v12 正式开始支持 ESM

  • 浏览器使用ESM非常慢。

[ 1–4. Introducing Webpack ]

Webpack是一个 ** 模块打包器 **

  • 让你编写任何格式的模块(甚至混合!),为浏览器编译它们。

  • 支持静态异步捆绑

  • 拥有丰富,庞大的生态系统(当今最高性能的JavaScript发布方式)

2012年,Webpack诞生了。 2014年,当时Instagram的工程经理Pete Hunt在O’Reilly开源大会上介绍了Instagram如何将其React应用程序与Webpack捆绑。 从那时开始,Webpack生态系统爆炸了。

[ 1–5. 配置 Webpack ]

有三种方法可以使用Webpack。

方法#1:Webpack配置

Webpack配置只是一个CommonJS模块。

它只是一个具有大量属性的对象。 webpack.config.js文件“导出对象。然后,Webpack根据其定义的属性“处理该对象。

方法#2:Webpack CLI

不需要开箱即用的配置

$ webpack <entry.js> <result.js> --colors --progress
$ webpack-dev-server--port=9000

方法#3:节点API

需要注意的是,必须提供自己的统计,反馈,报告和控制台信息。所有这些都不是开箱即用的。

let webpack = require("webpack");
// returns a Compiler instance
webpack({
  // configuration object here!
}, function(err, stats){
  // ...
  // compilerCallback
  console.error(err);
});

开始使用 Webpack

[2-1 为环境构建添加NPM脚本]

Step 1

将dev脚本添加到package.json文件中:

{
  "scripts": {
    "webpack": "webpack",
    "dev": "npm run webpack -- --mode development"
}

“ – ” 语法表示将下一个参数传递给原始命令

Step 2

以开发模式启动Webpack:npm run dev

可以看到执行的是 webpack "--mode" "development"

将生产脚本添加到package.json文件中:

{
  "scripts": {
    "webpack": "webpack",
    "dev": "npm run webpack -- --mode development",
    "prod": "npm run webpack -- --mode production"
}

[ 2–2. 编写第一个模块 ]

Step 1

在src目录下编写 nav.js index.js

[src > nav.js]
export default "nav";
-----------------------------------
[src > index.js]
import nav from "./nav";
console.log(nav);

Step 2

执行 npm run prod

会发现生成了一个dist目录

- dist
   |-- main.js
- src
   |-- index.js
   |-- nav.js
- package.json

在命令行中执行 node ./dist/main.js 会得到

nav

我们创建了两个模块并放在一起。现在它的功能与我们编写模块的执行方式相同。

[ 2–5. 添加 Watch 模式 ]

替代人们运行“npm run prod”,使用Webpack的“watch mode” 添加监视。

Step 1

添加 --watch 并执行 npm run dev

"scripts": {
  "webpack": "webpack",
  "prod": "npm run webpack -- --mode production",
  "dev": "npm run webpack -- --mode development --watch"
},

Webpack核心概念

Webpack的四个核心概念:“entry”, “output”, “loaders”, 和 “plugins”。

[ 3–1. Webpack Entry ]

“Entry”是第一个加载到“启动”应用程序的javascript文件。 Webpack以此为出发点。

// webpack.config.js
module.exports = {
  entry: './browser.main.ts',
  // ...
}

[3–2. Output & Loaders ]

Step 1

第二个核心概念称为“Output”。

// webpack.config.js
module.exports = {
  entry: './browser.main.ts',
  output: {
    path: './dist',
    filename: './bundle.js'
  },
  // ...
}

在Webpack 4中,默认情况下文件名设置为main.js.

Step 2

第三个核心概念是“loaders”。这个概念使Webpack真正区别于您可能使用过的任何其他工具。

module:{
  rules: [
    { test: /\.ts$/, use: 'ts-loader' },
    { test: /\.js$/, use: 'babel-loader' },
    { test: /\.sass$/, use: 'sass-loader' }
  ]
}

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。 loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

loader 有两个核心属性:

  1. test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。

  2. use 属性,表示进行转换时,应该使用哪个 loader。

Step 3

有一些不同类型的功能可以帮助转换文件时过滤,包含,选择忽略。

module:{
  rules: [
    test: regex,
    use: (Array|String|Function),
    include: RegExp[],
    exclude: RegExp[],
    issuer: (RegExp|String)[],
    enforce: "pre"|"post"
  ]
}
  • test: 一个正则表达式,指示编译器运行加载器的文件。
  • use: 一个 array/string/返回loader对象的函数
  • enforce: 可以是 ‘pre’ 或 ‘post’,告诉Webpack在所有其他规则之前或之后运行此规则
  • include: 一个正则表达式数组,指示编译器包含哪些文件夹/文件; 将仅搜索include提供的路径
  • exclude: 一个正则表达式数组,指示编译器忽略哪些文件夹/文件

[“include”和”exclude“的例子]

module:{
  rules: [
    {
      test: /\.ts$/,
      use: [
        'awesome-typescript-loader',
        'ng2-asset-loader'
      ],
      include: /some_dir_name/,
      exclude: [/\.(spec|e2e)\.ts$/],
    }  
  ]
}

[ 3–3. Chaining Loaders ]

加载器只是一个接收源并返回新源的函数。

Step 1

加载器总是从右到左执行(从左到右的第一个传递只是为了收集元数据)。

module:{
  rules: [
    test: /\.scss$/,
    use: ['style-loader', 'css-loader', 'sass-loader']
  ]
}

计算机如下读取代码:style(css(sass()))

矩阵1

矩阵1

矩阵1

矩阵1

[3-4. Webpack Plugins]

[一个简单插件例子] 插件是ES5“类”,它实现了应用功能。编译器使用它来发出事件。

function BellOnBundlerErrorPlugin() {}
BellOnBundlerErrorPlugin.prototype.apply = function(compiler){
  if(typeof(process) !== 'undefined'){
  
    // Compiler events emitted & handled
    compiler.plugin('done', function(stats){
      if(stats.hasErrors()){
        process.stderr.write('\x07');
      }
    });
    compiler.plugin('failed', function(err){
      process.stderr.write('\x07');
    });
  }
}
module.exports = BellOnBundlerErrorPlugin;
  • 当将“apply”方法传递给Webpack时,它会将自己作为一个实例传递给这个插件以挂载到不同的事件中。
  • 此示例使用Webpack 3 API作为插件系统。但是,概念是一样的。我们插入编译器并收听“done”和“failed”事件。事件将传递数据,然后我们将根据数据的采取具体行为。

让我们将它的新实例传递给我们的配置。

// require() from node modules or Webpack or local file
let BellOnBundlerErrorPlugin = require('bell-on-error');
let webpack = require('webpack');
module.exports = {
  // ...
  plugins: [
    new BellOnBundlerErrorPlugin(),
    // Just a few of the built-in plugins
    new webpack.optimize.CommonsChunkPlugin('vendors'),
    new webpack.optimize.UglifysPlugin()
  ]
  //...
}

[如何使用插件]

  • node_modulesrequire() 插件到 config
  • config 对象的 plugins 键中添加新的插件实例
  • 提供其他信息参数

80%的Webpack源代码是由自己的插件系统组成的。 它是由您使用的完全相同的插件系统构建的。

[3–5. 向 Webpack Config 传递参数]

将 ‘mode’ 改为 ‘env.mode’

[package.json]
"scripts": {
  "webpack": "webpack",
  "prod": "npm run webpack -- --env.mode production",
  "dev": "npm run webpack -- --env.mode development --watch"
}

当将“env”标志传递给Webpack时,Webpack将获取值(在本例中为具有mode属性的对象)并将其提供给函数内部的配置。

[webpack.config.js]
module.exports = env => {
  // env 是 { mode: ‘production’ }  
  return{
    output:{
      filename: "bundle.js"
    }
  }
};

如果将 env设置为字符串也是可以的

[package.json]
"scripts": {
  "prod": "npm run webpack -- --env production"
}

在 webpack.config.js 中 env是字符串 production

此时因为 没有传递 –mode 参数,会收到警告。我们修改 webpack.config.js 以使用 env中的mode参数

[webpack.config.js]
module.exports = ( {mode}) => {
  console.log(mode);
  return{
    mode,
    output:{
      filename: "bundle.js"
    }
  }
};

[3–6. 添加 Webpack 插件]

Step 1

执行 npm install html-webpack-plugin --save-dev

Step 2

[webpack.config.js]
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = ( {mode}) => {
  console.log(mode);
  return{
    mode,
    output:{
      filename: "bundle.js"
    },
    plugins: [
      new HtmlWebpackPlugin(),
      new webpack.ProgressPlugin()
   ]
  }
};

Step 3

执行 npm run prod

- dist
  -- bundle.js
  -- index.html
- src
- package.json
- webpack.config.js

生成了 index.html

[3–8. 启动本地开发服务器]

Step 1

npm install webpack-dev-server -D

Step 2

添加webpack-dev-server脚本并将webpack-dev-server添加到dev脚本中。然后运行以下命令:npm run dev

[package.json]
"scripts": {
  "webpack-dev-server": "webpack-dev-server",
  "webpack": "webpack",
  "prod": "npm run webpack -- --env.mode production",
  "dev": "npm run webpack-dev-server -- --env.mode development --watch"
}

Step 3

Visit http://localhost:8080/. 现在我们有了一个服务可以验证我们的代码运行结果

[3–10. 拆分 Environment Config 文件]

调整结构, 安装 webpack-merge

- build
  -- presets
     -- loadPresets.js
  -- webpack.development.js
  -- webpack.production.js
- dist
  -- bundle.js
  -- index.html
- src
- package.json
- webpack.config.js

Step 1

让我们声明一个变量

// [webpack.config.js]
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const modeConfig = env => require(`./build/webpack.${env}`)(env);
module.exports = ({mode, presets} = { mode: "production", presets: [] }) => {
  console.log(mode);
  return{
    mode,
    output:{
      filename: "bundle.js"
    },
    plugins: [
      new HtmlWebpackPlugin(),
      new webpack.ProgressPlugin()
   ]
  }
};

const modefig 返回一个可以调用的函数。

Step 2

使用 webpack-merge

const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpackMerge = require("webpack-merge");
const modeConfig = env => require(`./build/webpack.${env}`)(env);
module.exports = ({mode, presets} = { mode: "production", presets: [] }) => {
  return webpackMerge(
    {
      mode,
      output: {
        filename: "bundle.js"
      },
      plugins: [
        new HtmlWebpackPlugin(),
        new webpack.ProgressPlugin()
      ]
    },
    modeConfig(mode)
  );
};

Step 3

生产模式下给打包增加hash

// [build-utils > webpack.production.js]
module.exports = () => ({
  output: {
    filename: "[chunkhash].js"
  }
});

[4. 使用插件]

[ 4–1. Using CSS with Webpack ]

import “./footer.css”; 这种引入css的方式, webpack自身是不支持的。需要使用 css-loader

// [build > webpack.development.js]
module.exports = () => ({
  module:{
    rules:[
      {
        test: /\.css$/,
        use: ["css-loader"]
      }
    ]
  }
});

css-loader 使css可以像 js模块一样被引入,但页面不会有任何效果。 所以还需要 style-loader, 它会通过插入 <style> 标签将css添加到dom中去。

// [build > webpack.development.js]
module.exports = () => ({
  module:{
    rules:[
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  }
});

[ 4–2. CSS 模块热更新 ]

Step 1

添加 --hot 标识, "dev": "npm run webpack-dev-server -- --env.mode development --hot",

之后修改样式就不需要重新加载页面了。

Step 2

我们刚刚添加了一个CSS模块。它阻断了主线程,这是因为我们依靠JavaScript来添加样式标记。

相反,我们想要做的是将其提取出来并将其放在单独的标签中。 我们可以通过添加mini-css-extract-plugin(仅兼容Webpack 4)并将其应用于我们的生产配置来实现。

// [build > webpack.production.js]
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = () => ({
  output: {
    filename: "[chunkhash].js"
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"]
      }
    ]
  },
  plugins: [ new MiniCssExtractPlugin() ]
});

不同于开发环境配置,在生产上我们使用mini-css-extract-plugin将css提取为单独的文件。

[ 4–3. File Loader & URL Loader ]

首先,让我们安装 file-loaderurl-loader。在终端运行 npm install file-loader url-loader -D

// [webpack.config.js]
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpackMerge = require("webpack-merge");
const modeConfig = env => require(`./build/webpack.${env}`)(env);
module.exports = ({mode, presets} = { mode: "production", presets: [] }) => {
  return webpackMerge(
    {
      mode,
      module: {
        rules: [
          {
            test: /\.jpe?g$/,
            use: ["url-loader"]
          }
        ]
      },
      output: {
        filename: "bundle.js"
      },
      plugins: [
        new HtmlWebpackPlugin(),
        new webpack.ProgressPlugin()
      ]
    },
    modeConfig(mode)
  );
};

url-loader:webpack的loader,用于将文件转换为base64 URI

[ 4–4. Loading Images with JavaScript ]

Step 1

添加一张 xx.jpg图片 并使用js 加载它

import imageURL from "./webpack-logo.jpg";
console.log(imageURL);

在页面上会发现有一段很长的base64

[ 4–5. URL Loader 限制文件大小]

use: [“url-loader”] 调整为 use: [{ loader: “url-loader”}]

增加选项参数

use: [{
  loader: "url-loader",
  options: {
    limit: 5000
  }
}]

[ 4–6. 实现预设 ]

Step 1

// [build > presets > loadPresets.js]
const webpackMerge = require("webpack-merge");

module.exports = env => {
  const { presets } = env;
  /** @type {string{}} */
  const mergedPresets = [].concat(...[presets]);
  const mergedConfigs = mergedPresets.map(
    presetName => require(`./presets/webpack.${presetName}`)(env) 
    // call the preset and pass env also
  );

  return webpackMerge({}, ...mergedConfigs);
};

env:我们从主配置中得到的整个env

Step 2

在webpack.config.js中添加一些额外的代码来实现合并预设

// [webpack.config.js]
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpackMerge = require("webpack-merge");
const modeConfig = env => require(`./build/webpack.${env}`)(env);
const presetConfig = require("./build/loadPresets");
module.exports = ({mode, presets} = { mode: "production", presets: [] }) => {
  return webpackMerge(
    {
      mode,
      module: {
        rules: [
          {
            test: /\.jpe?g$/,
            use: ["url-loader"]
          }
        ]
      },
      output: {
        filename: "bundle.js"
      },
      plugins: [
        new HtmlWebpackPlugin(),
        new webpack.ProgressPlugin()
      ]
    },
    modeConfig(mode),
    presetConfig({mode, presets})
  );
};

Step 3

让我们用较小的配置创建一个新的预设。我们只需要遵循相同的命名模式。

在“presets”文件夹中创建webpack.typescript.js。

- build
  -- presets
     -- webpack.typescript.js
  -- loadPresets.js
  -- webpack.development.js
  -- webpack.production.js
- package.json

在webpack.typescript.js中添加以下代码:

module.exports = () => ({
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: "ts-loader"
      }
    ]
  }
})

还需要安装 ts: npm install ts-loader typescript@next — dev

package.json 增加一个 script "prod:typescript": "npm run prod -- --env.presets typescript"

Read More

温故知新: Array

又重新翻看了一下MDN上数组的介绍,《JavaScript权威指南》第七章,《ES6标准入门》第七章。 总结一下JavaScript中数组的一些细节,主要是在工作长时间不使用而遗漏的细节。

1. delete 数组成员

delete

数组成员是可以被删除的,但不会缩短数组的长度。

2. Array.from(arrayLike[, mapFn[, thisArg]])

Array.from 有参数二 mapFn ,存在时则新数组中的每个元素都会执行该回调。

如想要生成['每月1日', ..., '每月31日']这个数组,可以使用 Array.from({length: 30}, (v,i) => 每月${i+1}日)

这里传了一个{length: 30}的对象。Array.from支持类似数组的对象。所谓类似数组的对象,本质特征就是必须有length属性。 因此,任何有length属性的对象,都可以通过Array.from生成数组。

Array.from 参数三 是 thisArg, 在mapFn执行时的this对象。效果和bind一样。

delete

Array.from 将类数组转为数组, 也可以用扩展运算符 [...args] 将类数组转为数组。 ES5中是可以用 Array.prototype.slice将类数组转为数组的

Array.isArray

用于确定传递的值是否是一个 Array。 语法是 Array.isArray(obj)

不使用 typeof 是因为 typeof [] === 'object'

还有一种老方法是 Object.prototype.toString.call(obj) === '[object Array]'; 也可以判定obj是否为数组

Array.isArray(Array.prototype); 记住:数组的原型也是一个数组

instanceof 不能检测 iframes

xArray = window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3); // [1,2,3]
Array.isArray(arr);  // true
arr instanceof Array; // false

Array.of

Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。

语法 Array.of(element0[, element1[, ...[, elementN]]])

Array.of() 和 Array 构造函数之间的区别在于处理整数参数

Array.of(7);       // [7] 
Array.of(1, 2, 3); // [1, 2, 3]

Array(7);          // [ , , , , , , ]
Array(1, 2, 3);    // [1, 2, 3]

Array.prototype.concat()

用于合并多个数组 语法 var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])

它不会改变现有数组,而是返回一个新数组。

要注意它是浅拷贝。对新数组中对象引用的修改,会影响到原数组。

Array.prototype.copyWithin()

浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。

语法 arr.copyWithin(target[, start[, end]])

三个参数都是索引位,复制内容到该位置/开始复制元素的起始位置/开始复制元素的结束位置

let numbers = [1, 2, 3, 4, 5];

numbers.copyWithin(-2);
// [1, 2, 3, 1, 2]  将自身复制,复制到倒数第二位开始。

numbers.copyWithin(0, 3);
// [4, 5, 3, 4, 5] 从第三位开始复制,复制到0开始。即 '4,5'复制后,粘贴到0开始。由于'4,5'是两位,所以'1,2'被替换了。

Array.prototype.entries()

返回一个新的Array Iterator 迭代器对象,该对象包含数组中每个索引的键/值对。 它的原型上有个next()方法,可用于与遍历迭代器取得的原数组的 [key, value]

var array1 = ['a', 'b', 'c'];

var iterator1 = array1.entries();

console.log(iterator1.next().value);
// expected output: Array [0, "a"]

console.log(iterator1.next().value);
// expected output: Array [1, "b"]

Array.prototype.every()

测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。

语法 arr.every(callback[, thisArg])

若收到一个空数组,此方法在一切情况下都会返回 true。

Array.prototype.fill()

用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。

语法 arr.fill(value[, start[, end]])

Array.prototype.filter()

创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

语法 var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])

Array.prototype.find()

返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。

语法 arr.find(callback(element[, index[, array]])[, thisArg])

Array.prototype.findIndex()

返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。

语法 arr.findIndex(callback(element[, index[, array]])[, thisArg])

Array.prototype.flat()

会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

语法 var newArray = arr.flat([depth])

depth 指定要提取嵌套数组的结构深度,默认值为 1。

该方法会移除数组中的空项

var arr1 = [1, 2, [3, 4]];
arr1.flat(); 
// [1, 2, 3, 4]

var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

//使用 Infinity 作为深度,展开任意深度的嵌套数组
arr3.flat(Infinity); 
// [1, 2, 3, 4, 5, 6]

Array.prototype.flatMap()

首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。

语法

var new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {
    // 返回新数组的元素
}[, thisArg])

返回 一个新的数组,其中每个元素都是回调函数的结果,并且结构深度 depth 值为1。

var arr1 = [1, 2, 3, 4];

arr1.map(x => [x * 2]); 
// [[2], [4], [6], [8]]

arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]

// 只会将 flatMap 中的函数返回的数组 “压平” 一层
arr1.flatMap(x => [[x * 2]]);
// [[2], [4], [6], [8]]

Array.prototype.forEach()

对数组的每个元素执行一次提供的函数。 无返回值。

语法 arr.forEach(callback(element[, index[, array]])[, thisArg])

Array.prototype.includes()

用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

对象数组不能使用includes方法来检测。

语法 arr.includes(valueToFind[, fromIndex])

Array.prototype.indexOf()

返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。

语法 arr.indexOf(valueToFind[, fromIndex])

Array.prototype.join()

将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。

语法 arr.join([separator])

Array.prototype.keys()

返回一个包含数组中每个索引键的Array Iterator(迭代器)对象。

var array1 = ['a', 'b', 'c'];
var iterator = array1.keys(); 
iterator.next() // {value: 0, done: false}  
[...iterator] // [0, 1, 2]

Array.prototype.lastIndexOf()

返回指定元素在数组中的最后一个的索引,如果不存在则返回 -1。从数组的后面向前查找,从 fromIndex 处开始。

语法 arr.lastIndexOf(searchElement[, fromIndex = arr.length - 1])

Array.prototype.map()

创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

语法

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
 // Return element for new_array 
}[, thisArg])

Array.prototype.pop()

从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。

Array.prototype.push()

将一个或多个元素添加到数组的末尾,并返回该数组的新长度。

语法: arr.push(element1, ..., elementN)

返回值是数组新的length属性

Array.prototype.reduce()

对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

语法 arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

callback接收四个参数:

  • accumulator: 累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue(见于下方)
  • currentValue: 数组中正在处理的元素
  • currentIndex: (可选)数组中正在处理的当前元素的索引。 如果提供了initialValue,则起始索引号为0,否则为1。
  • array: (可选) 调用reduce()的数组

initialValue : 作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。

所以要注意以下情况

[1,2,3,4].reduce((a,b) => a + 2 * b)
// 19 由于没有提供初始 initialValue,直接将1作为第一次执行的a。 1 + 2*2 + 2*3 + 2*4
[1,2,3,4].reduce((a,b) => a + 2 * b, 0)
// 20 提供了初始值0  0 + 2*1 + 2*2 + 2*3 + 2*4

提供初始值通常更安全

Array.prototype.reduceRight()

接受一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值, 将其结果汇总为单个返回值。

对于从左至右遍历的相似方法请参阅 Array.prototype.reduce()

Array.prototype.reverse()

reverse() 方法将数组中元素的位置颠倒,并返回该数组。该方法会改变原数组。

Array.prototype.shift()

从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。

返回值: 从数组中删除的元素; 如果数组为空则返回undefined 。

Array.prototype.slice()

返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变

语法

arr.slice();
// [0, end]

arr.slice(begin);
// [begin, end]

arr.slice(begin, end);
// [begin, end)

slice 方法可以用来将一个类数组(Array-like)对象/集合转换成一个新数组。你只需将该方法绑定到这个对象上。 一个函数中的 arguments 就是一个类数组对象的例子。

function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

Array.prototype.some()

测试是否至少有一个元素可以通过被提供的函数方法。该方法返回一个Boolean类型的值。

对于空数组上的任何条件,此方法返回false。

语法: arr.some(callback(element[, index[, array]])[, thisArg])

Array.prototype.sort()

将数组中的元素进行排序并返回排序后的数组。当不带参数调用sort()时,数组元素以字母表顺序(Unicode码点)排序。

语法: arr.sort([compareFunction(firstEl, secondEl)])

如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:

  • 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;
  • 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);
  • 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。

Array.prototype.splice()

通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。

语法 array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

返回值 : 由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。

var myFish = ["angel", "clown", "mandarin", "sturgeon"];
var removed = myFish.splice(2, 0, "drum");

// 运算后的 myFish: ["angel", "clown", "drum", "mandarin", "sturgeon"]
// 被删除的元素: [], 没有元素被删除

var myFish = ['angel', 'clown', 'drum', 'sturgeon'];
var removed = myFish.splice(2, 1, "trumpet");

// 运算后的 myFish: ["angel", "clown", "trumpet", "sturgeon"]
// 被删除的元素: ["drum"]

Array.prototype.toLocaleString()

返回一个字符串表示数组中的元素。数组中的元素将使用各自的 toLocaleString 方法转成字符串,这些字符串将使用一个特定语言环境的字符串(例如一个逗号 “,”)隔开。

var array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')];
var localeString = array1.toLocaleString('en', {timeZone: "UTC"});

console.log(localeString);
// expected output: "1,a,12/21/1997, 2:12:00 PM",
// This assumes "en" locale and UTC timezone - your results may vary

语法: arr.toLocaleString([locales[,options]]);

Array.prototype.toString()

返回一个字符串,表示指定的数组及其元素。

var array1 = [1, 2, 'a', '1a'];

console.log(array1.toString());
// expected output: "1,2,a,1a"

Array.prototype.unshift()

将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组)。

语法: arr.unshift(element1, ..., elementN)

返回值: 数组的新length

Array.prototype.values()

返回一个新的 Array Iterator(迭代器) 对象,该对象包含数组每个索引的值

let arr = ['w', 'y', 'k', 'o', 'p'];
let eArr = arr.values();
console.log(eArr.next().value); // w
console.log(eArr.next().value); // y
Read More

在威墨时的工作回忆

最近刚刚忙完一个项目,有些空闲时间,来总结一下上一份工作。 离开上一家公司已经一年多了,公司的官网一直还保留着离开时的样子。网站的humans.txt还保留在1年半以前,我的足迹也留在了上边。 我离开之后不久,我带领的前端团队陆续离职。目前还有两个妹子留在那里,听说后来也没有继续招聘前端。 公司业务没有做大,老板坚持一年多还在寻找投资,挺不容易的。

关于humans.txt

robots.txt文件告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。 而humans.txt文件则是为人类准备的,包含参加该网页设计和建立的相关人员的信息。

一种浪漫情怀的文件,如果运维在nginx里配置了屏蔽txt文件就完蛋了,vidahouse很开放,或者说作为初创公司它没有运维人员。 网站项目都是前端自行维护的,在这一年多里我接触了pm2,nginx等等。

服务端渲染

官网的第一个版本,是nodejs+express+ejs模板实现的。 我入职的时候已经做了一些,主要是页面展示,没什么交互。后来陆续加上了登录注册,个人中心,论坛等功能。 做完第一版之后我感觉不是很满意,当时也学了一点Vue.js的东西,把评论这种功能用Vue.js重写了。还是觉得不好。 整个网站被搞得乱七八糟,Vue.js jQuery BootStrap 什么都有。 2016年年底的时候,Nuxt.js出现了,我决定用Nuxt.js重做整个项目。 为什么官网要部署在Node.js上,因为我们是一家小公司,老板想做SEO,想排名靠前。 如果只是一些纯静态页面还好,当有了用户的作品详情,帖子详情这些东西之后,用了vue.js,用了vue.js就要上ssr。 而Nuxt.js是一个不错的ssr工具。

Nuxt.js

Nuxt.js是我非常喜欢的一个工具,在它0.x版本的时候就开始使用,现在已经发展到 v2.8.1了。目前他的渲染模式已经支持三种了:

  • SSR模式: 服务端渲染,传输给客户端的是基于vue.js组件的html,而不是纯javascript。vue.js官方有ssr工具,但自己实施起来很繁琐。nuxt.js提供了开箱即用的ssr,并且已经解决了常见陷阱。
  • 静态站点生成:静态站点是个热门话题。比如我的博客使用jekyll生成的。Vue-press也是一个不错的工具,我在公司内部分享时,都是采用vue-press将markdown文件转为静态站点。nuxt.js也有这个功能。
  • SPA: vue.js本身就是用于做spa单页面应用的,nuxt.js用来做spa时更像是vue-cli工具。

2017年时v0.x的nuxt.js最热门的用途就是ssr。后来换了公司不需要ssr了,也没在用过。如果以后有机会做需要ssr的站点,我还是会选择该工具。

Sitemap

vidahouse的sitemap入口在此

已经用Nuxt.js做了SSR服务端渲染为什么还需要做Sitemap呢,其实这是两个东西。蜘蛛来到了首页,他可以从首页进入内部链接,比如navbar上的一排a链接是可以进入的。 这种叫内链,用户的首页我们也希望被蜘蛛爬到,比如用户A的首页是user/a。如果从首页或者从首页到某页再到某页能有这个链接,确实可以被爬取到。 可是大多数用户的首页没有机会被推荐到首页/二级页面。而我们还希望被爬虫看到,就需要sitemap了。sitemap是向爬虫声明我们网站有哪些链接的网站地图。 由于网站的用户/帖子量可能十分巨大,都放在一个xml文件里不现实了。这时候sitemam.xml可能就变成了入口。内部声明有哪些xml压缩包。 当时vidahouse的sitemap.xml如下

矩阵1

里边包含三个压缩包,压缩包解压之后内容大概如下:

矩阵1

每一行代表一个连接,以及更新频率,更新日期,权重等信息。

使用的工具是 sitemap.js。 数据来源是服务端开发从数据库中导出的全部userid(用户id),postid(帖子id),caseid(案例id) 做了大概一个月之后,谷歌开始收录了,点击更多也能看到帖子这些了。

矩阵1

矩阵1

pm2 nginx

本地开发的时候跑项目就是 npm run start,但是这玩意一旦崩了就整个挂了。所以用了 pm2, 他可以监视node项目,发生崩溃时可以马上重启。类似的工具还有 forevernodemon,我没有对比过。

用了nodejs为什么还要用nginx,主要是为了做反向代理,可以将443端口的请求转发到你内部的8080端口等等。 还可以开启gzip,nodejs项目自己也可以做gzip。

后台管理系统

在公司期间,还做了几个后台管理系统。其实也是fork vue-element-admin这个项目做的。 当时它还没这么火,初次见到他的代码,真的很惊叹作者的代码水平,真的很高。 现在这个作者在github至少有10W的star量了。 当时他好像在华尔街日报工作,也不知道跳槽了没有。

权限管理

有后台就会有权限管理,最大的那个后台系统有十几个模块,案例管理,帖子管理,活动管理,新闻管理等等吧,如何做权限管理。 我把用户的权限设计为一个数组,比如用户A的权限为 [‘case’, ‘post’],用户B的权限是[‘news’, ‘case’]就代表了用户A可以进入案例管理、帖子管理, 用户B可以进入新闻管理、案例管理。还有一个最大的权限 [‘admin’],可以进入所有的模块。 在vue-router实例的beforeEach回调中,先判断用户权限list中是否有该模块的权限码或者是否admin。管理用户权限时就操作他的权限List就行了。

Read More

移动调试工具介绍

首先简单介绍一下Chrome开发者工具

矩阵1

  1. 设备模式 可以模拟移动设备的分辨率/dpr 定位 网络状态等。可以用于移动开发的早期阶段,移动开发完毕后还是要上真机进行调试。
  2. 元素面板 可以自由的操作DOM和CSS来迭代布局和设计页面
  3. 控制台面板 在开发期间,可以使用控制台面板记录诊断信息,或者使用它作为 shell在页面上与JavaScript交互。
  4. 源代码面板 在源代码面板中设置断点来调试 JavaScript ,或者通过Workspaces(工作区)连接本地文件来使用开发者工具的实时编辑器。
  5. 网络面板 使用网络面板了解请求和下载的资源文件并优化网页加载性能。
  6. 性能面板 使用时间轴面板可以通过记录和查看网站生命周期内发生的各种事件来提高页面的运行时性能。
  7. 内存面板 比时间轴面板提供的更多信息,可以使用“配置”面板,例如跟踪内存泄漏。
  8. 应用面板 使用资源面板检查加载的所有资源,包括IndexedDB与Web SQL数据库,本地和会话存储,cookie,应用程序缓存,图像,字体和样式表。
  9. 安全面板 使用安全面板调试混合内容问题,证书问题等等。

相关资源 Tools for Web Developers 网站上有完整的Chrome调试工具功能指引

安卓调试

前言

基础设置

要求

  • 开发计算机上已安装 Chrome 32 或更高版本。
  • 开发计算机上已安装 USB 驱动程序(如果您使用 Windows)。 确保设备管理器报告正确的 USB 驱动程序
  • 拥有一根可以将您的 Android 设备连接至开发计算机的 USB 电缆。
  • Android 4.0 或更高版本。
  • 您的 Android 设备上已安装 Chrome(Android 版)。

    第 1 步:发现您的Android设备

  • 在您的 Android 设备上,选择 设置 > 开发者选项 。 在运行 Android 4.2 及更新版本的设备上,开发者选项 默认情况下处于隐藏状态。 一般是在 设置 > 关于手机 > 基带版本 连续点击七次 后打开开发者选项。
  • 打开Chrome的 远程调试 在地址栏输入 chrome://inspect/
  • 用usb线连接手机和电脑,这时候在Chrome远程调试页应该会出现 Pending Authorization 手机屏幕上出现权限提示

矩阵1

第 2 步:从您的开发计算机调试 Android 设备上的内容。

  • 在安卓上打开Chrome
  • 新建一个tab并访问网页
  • 在电脑的调试页此时应该可以看到

矩阵1

  • 点击inspect, 和普通的Chrome调试基本一样操作了。

矩阵1

webkit内核的Webview

大前提是 android开发人员在安卓项目代码中开启了调试, 比如翼支付App安卓Debug版就有开启

要启用 WebView 调试,请在 WebView 类上调用静态方法 setWebContentsDebuggingEnabled

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    WebView.setWebContentsDebuggingEnabled(true);
}

矩阵1

提示

有时候点击inspect打开的调试页是空白的,或者等了很久显示一行404.

这是因为手机Webview版本和电脑Chrome版本不一致,它需要去 chrome-devtools-frontend网站上下载适配工具

所以你打开 SSR ,手动访问下 https://chrome-devtools-frontend.appspot.com ,再回来点击inspect 一般就可以解决。

如果在 远程调试页没有看到手机应用 这是因为有别的工具 比如 微信开发工具 电脑上360手机管家 等 占用了 ADB,需要你手动关闭他们。

针对微信/QQ的Webview调试

  • qq/微信访问 http://debugtbs.qq.com
  • 点击安装线上内核 安装成功后qq浏览器会重启
  • 点击DebugX5,进入设置页,点击 信息 Tab
  • 勾选 打开TBS内核Inspector调试功能 会再次重启qq浏览器

矩阵1

之后就可以像翼支付WebView一样调试了

QQ浏览器

访问https://x5.tencent.com/guide/debug.html下载并安装TBS Studio

矩阵1

他会主动给你安卓手机安装上QQ浏览器开发者版,之后就可以调试了

矩阵1

UC浏览器

访问https://plus.ucweb.com/download

分别下载 UC浏览器安卓开发者版 UC浏览器开发者工具

矩阵1

ios调试

mac os目前比较封闭 能直接通过usb调试的目前我就知道safari 和 safari mobile

  1. mac safari 开启开发工具 矩阵1

  2. iphone手机上 打开手机设置->Safari->高级(最下面)->Web检查器打开,JavaScript开关打开

  3. 连接电脑,手机会要求输入密码 信任电脑,确认以后就可以调试手机端的safari了

矩阵1

  1. 如果对safari的调试工具不满意怎么办

安装 ios-webkit-debug-proxyremotedebug-ios-webkit-adapter

brew update
brew unlink libimobiledevice ios-webkit-debug-proxy usbmuxd
brew uninstall --force libimobiledevice ios-webkit-debug-proxy usbmuxd
brew install --HEAD libimobiledevice
brew install --HEAD usbmuxd
brew install --HEAD ios-webkit-debug-proxy
npm install remotedebug-ios-webkit-adapter -g

执行

remotedebug_ios_webkit_adapter --port=9000

#

VConsole / Eruda

https://github.com/Tencent/vConsole

安装

<script src="path/to/vconsole.min.js"></script>
<script>
  // init vConsole
  var vConsole = new VConsole();
  console.log('Hello world');
</script>
  • 引入 vConsole 模块后,页面前端将会在右下角出现 vConsole 的悬停按钮,可展开/收起面板。
  • 千万 不要搞到生产环境上去了,会被批评的

矩阵1

vue-devtools

https://github.com/vuejs/vue-devtools

并非浏览器扩展,是一个独立运行版,可以对远端的vuex状态调试

  • 安装
  sudo npm install -g @vue/devtools

如果出现 ` permission denied, mkdir ‘/usr/local/lib/node_modules/@vue/devtools/node_modules/electron/dist’` 的错误提示,可以尝试下边的代码

    sudo npm install -g @vue/devtools --unsafe-perm=true --allow-root
  • 使用
    • 确保手机和电脑在同一个局域网
    • 在命令行输入 vue-devtools 进行启动
    • 把下边的代码加入到index.html中
       <script>
        window.__VUE_DEVTOOLS_HOST__ = '<your-local-ip>' // default: localhost
        window.__VUE_DEVTOOLS_PORT__ = '<devtools-port>' // default: 8098
      </script>
      <script src="http://<your-local-ip>:8098"></script>
      
  • 再次用移动设备访问index.html就可以看到了
  • 千万 不要搞到生产环境上去了,会被砍死的,因为用户的手机上 <script src="http://<your-local-ip>:8098"></script> 会因为超时而长时间阻塞页面加载

矩阵1

spy-debugger

网站地址 https://github.com/wuchangming/spy-debugger

包括页面调试和抓包

集成了 weinre、node-mitmproxy

流程是代理手机请求到本机,本机会劫持流量注入相关js,之后就可以调试了,容易掉线。

  • 命令行安装 spy-debugger
      sudo npm install spy-debugger -g
    
  • 手机和PC保持在同一个网络下
  • 命令行输入 spy-debugger 一般会展示下边几行内容
    ludeMacBook-Pro:~ lu$ spy-debugger
    正在启动代理
    本机在当前网络下的IP地址为:192.168.0.128
    node-mitmproxy启动端口: 9888
    浏览器打开 ---> http://127.0.0.1:55772
    
  • 设置手机的HTTP代理,代理IP地址设置为PC的IP地址,端口为spy-debugger的启动端口(默认端口:9888)。
    • Android设置代理步骤:设置 - WLAN - 长按选中网络 - 修改网络 - 高级 - 代理设置 - 手动
    • iOS设置代理步骤:设置 - 无线局域网 - 选中网络 - HTTP代理手动
  • 手机安装证书,确保命令行spy-debugger没有关闭,手机浏览器(非微信)访问 http://s.xxx 安装证书 ios新安装的证书需要手动打开证书信任
  • 用手机浏览器/App Webview 访问页面,均可被劫持。

矩阵1

由于它的原理和fiddler 一样,所以也可以用于查看抓包

可以用于欢购ios/android这种app的调试,聊胜于无吧

矩阵1

Read More

Promise组合器

ES2015引入了promises,并且支持两种组合器,静态方法Promise.allPromise.race。 目前草案中还有另外两个组合器Promise.allSettledPromise.any

名称 描述 状态
Promise.AllSettled 不会短路 提案
Promise.all 任意一项拒绝时短路 ES2015
Promise.race 任意一项解决或拒绝时短路 ES2015
Promise.any 任意一项解决时短路 提案

Promise.all

mdn文档地址

Promise.all(iterable) 方法返回一个 Promise 实例, 此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve); 如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

const promises = [
  fetch('/component-a.css'),
  fetch('/component-b.css'),
  fetch('/component-c.css'),
];
try {
  const styleResponses = await Promise.all(promises);
  enableStyles(styleResponses);
  renderNewUi();
} catch (reason) {
  displayError(reason);
}

Promise.race

mdn文档地址

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。 在你需要有一个解决时就做些事情或者是有一个拒绝时做些事情时,它会很有用。

try {
  const result = await Promise.race([
    performHeavyComputation(),
    rejectAfterTimeout(2000),
  ]);
  renderResult(result);
} catch (error) {
  renderError(error);
}

Promise.allSettled

mdn文档地址

Promise.allSettled(iterable)方法返回一个promise,一旦迭代器中的所有promise都完成了解决或拒绝,返回的 promise就会解决。

const promises = [
  fetch('/api-call-1'),
  fetch('/api-call-2'),
  fetch('/api-call-3'),
];
// Imagine some of these requests fail, and some succeed.

await Promise.allSettled(promises);
// All API calls have finished (either failed or succeeded).
removeLoadingIndicator();

Promise.any

Promise.any(iterable)方法返回一个promise,一旦迭代器中的某个promise解决,返回的 promise就会解决。当迭代器中的所有promise拒绝,返回的 promise就会拒绝。 与Promise.race相比,返回的promise解决的逻辑相同。但Promise.race相返回的promise会在迭代器中的某个promise拒绝时就拒绝。

const promises = [
  fetch('/endpoint-a').then(() => 'a'),
  fetch('/endpoint-b').then(() => 'b'),
  fetch('/endpoint-c').then(() => 'c'),
];
try {
  const first = await Promise.any(promises);
  // Any of the promises was fulfilled.
  console.log(first);
  // → e.g. 'b'
} catch (error) {
  // All of the promises were rejected.
  console.log(error);
}
Read More

使用verdaccio部署私有npm服务器

入职公司14个月了,带领兄弟们做了有甜橙借钱、分期商城、甜橙借钱H5、CEO贷、分期商城H5、信用卡代偿、甜橙借钱2.0等等多个项目。 虽然项目众多,但都属于信贷类产品,流程整体上还是授信-借钱-还钱三个步骤。其中有多代码、组件、插件可以共用。 如何避免重复工作,实现项目间共享就成了问题。

第一次尝试是在2018年年底,尝试通过 git subtree进行跨项目的代码共享,多个项目中使用git subtree推拉代码。 在任意一个项目中修改了代码后,都可以推送,其他项目拉取最新代码即可。但遗憾的是一直没解决一个问题就是速度问题和git log 混乱问题。 它总是把所在项目的全部历史记录都打包上传,尝试了一些方法也没有解决。

最近刚刚做完一个大项目,有点时间,准备尝试第二种方式。私有npm服务器。 部署一个私有npm服务器,把内部组件放在这里,然后控制权限,只有内部人员登录后可以推拉,可以保证安全。

工具选择

《深入浅出Nodejs》这本书的附录D中讲到了如何搭建局域网npm仓库,提到的项目是npm-registry-couchapp ,这个项目已经被废弃了,所以用它明显不合适了。

同事找到了一个 sinopia ,不过这个项目已经四年没有更新过了,也不太合适。

后来在谷歌上找到了verdaccio,他是从sinopia切出来分支发展起来的,更新频繁,有七千多个star,最重要的是他提供了docker安装包,很方便。 所以最后选择它。本文主要记录我在云服务器上安装它的过程。

部署环境

在百度云购买的双核4G带宽1M的云服务器,系统为 CentOS Linux release 7.6.1810, docker版本为 docker-compose version 1.23.2, build 1110ad0

部署过程

(一)创建目录和文件

└── /verdaccio 项目目录
   ├──/conf 配置目录
   │  └── config.yaml verdaccio 配置文件
   │  └── htpasswd 用户密码文件
   │
   ├──/storage 包存放目录
   │
   └── docker-compose.yml docker-compose配置文件

(二) docker-compose 配置

version: '2'

services:
  verdaccio:
    container_name: verdaccio
    image:  verdaccio/verdaccio:latest
    volumes:
      - ./conf:/verdaccio/conf:Z
      - ./storage:/verdaccio/storage:Z
    network_mode: bridge
    ports:
      - 4873:4873
    expose:
      - 4873

(三) verdaccio 配置

文档 https://verdaccio.org/docs/en/configuration

url_prefix: /verdaccio/

storage: /verdaccio/storage

auth:
  htpasswd:
    file: /verdaccio/conf/htpasswd
    max_users: -1
security:
  api:
    jwt:
      sign:
        expiresIn: 60d
        notBefore: 1
  web:
    enable: false
    sign:
      expiresIn: 7d

uplinks:
  npmjs:
    url: https://registry.npm.taobao.org/

packages:
  '@chun/*':
    access: $authenticated
    publish: $authenticated

  '@*/*':
    access: $all
    publish: $all
    proxy: npmjs

  '**':
    access: $all
    publish: $all
    proxy: npmjs

middlewares:
  audit:
    enabled: true

logs:
  - {type: stdout, format: pretty, level: trace}

这里因为我的服务器上有多个项目,需要用nginx进行反向代理,所以这里设置了url_prefix: /verdaccio/

我不希望有其他用户通过 npm adduser 进入我的私有仓库,所以我将最大用户数设置为-1,代表不可以注册。

我不需要别人通过网页看到我的仓库服务器,所以将 web enable: false

packages 这里我将我的私有项目都放在@chun这个目录下,并且只能是认证过的用户才能推拉。

(四) htpasswd 介绍

htpasswd-generator 生成 htpasswd 文件,内容是用户名:加密密码,每行代表一个用户,可以手动添加。

### (五) 使用中遇到的问题 一个是它报没有htpasswd的权限,我用 sudo chown -R 100:101 /verdaccio 修改权限还是不行。请教了一位同事后,使用 chmod 777 /verdaccio/htpasswd后解决了。

第二个问题是发布包的时候报 /verdaccio/.verdaccio-db.json 权限不足,我只好手动创建了这个文件并且通过chmod修改了权限解决了问题。

第三个问题是 web enable: false 并没有将网页关闭掉,还没有解决。

### (六) nginx上的反向代理配置

 location ~ ^/verdaccio/(.*)$ {
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-Proto $scheme;
          proxy_set_header X-NginX-Proxy true;
          proxy_pass http://127.0.0.1:4873/$1;
          proxy_redirect off;
        }

(七) 启动

docker-compose up -d 启动,这里安装的时候遇到了点问题,一直超时,后来我才想起来我在百度云的安全策略设置为屏蔽了80端口。

使用过程

创建包

先尝试创建一个包,使用npm init 即可,创建的package.json如下

{
  "name": "@chun/types",
  "main": "",
  "version": "1.0.3",
  "description": "add types for our project",
  "scope": "@chun",
  "keywords": [
    "typescript"
  ],
  "author": "luchun",
  "license": "MIT"
}

登录

npm adduser --registry https://your-npm-site.com 输入前边在htpasswd创建时输入的用户名和密码

这里遇到了一个问题,它一直说ssl验证错误,估计是因为我走的是nginx代理。这里可以 npm config set strict-ssl false 关闭全局ssl校验。 也可以在项目根目录的.npmrc中添加 strict-ssl=false。这个问题后边要解决一下

推送

npm publish --registry https://your-npm-site.com

使用

在要使用该组件的项目的.npmrc中添加一句 @chun:registry=https://your-npm-site.com/@chun这个scope下的包都会走私有仓,其他的走官方仓库。 这里因为我设置了认证用户才可以拉取,所以也需要先 npm adduser --registry https://your-npm-site.com 进行登录。

Read More

Promise.prototype.finally()

ES2018 新增额一个方法 Promise.prototype.finally()

## ```ecmascript 6 promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});


finally 回调总是被执行,相比之下 `then` 回调只有在`promise`完成时执行。
`catch`回调只有在`promise`被拒绝时执行,或者`then`回调中抛出异常或返回一个被拒绝的`Promise`时才执行。也就是说 下边两个代码的效果是一样的。

```ecmascript 6
promise
.finally(() => {
    «statements»
});

```ecmascript 6 promise .then( result => { «statements» return result; }, error => { «statements» throw error; } );


在 `Typescript`中使用 `Promise.prototype.finally()`
在`tsconfig.json`中增加 `"es2018.promise"`
```js
{ "compilerOptions": 
   { // ... 
   "lib": ["dom", "es2015", "es2018.promise"] 
   } 
}
Read More

TypeScript Basic

最近的一个公司项目中,我给项目加上了TypeScript。 目前项目有三名前端,2019年将会增加更多的前端,更多的项目。 一方面我们使用ESLint控制代码风格,但是我觉得ESLint越来越不够了。所以明年所有前端项目,我都将要求使用TypeScript。

Read More

babel 7.2

今天看邮件看到 Babel 7.2 发布了,主要新增了两个功能。

Read More

optional chaining

在开发中经常遇到一个问题,安全引用对象的属性。例如:

const obj = {
  foo: {
    bar: {
      baz: 42,
    },
  },
};

多见于前后端交互的时候,很有可能foo是个null,为了使用foo,不得不像下边这么写:

    const baz = obj && obj.foo && obj.foo.bar && obj.foo.bar.baz

现在TC39提出了一个新的想法 optional chaining 安全的链式调用

### 上边的代码可以写成下边这种形式:

    const baz = obj?.foo?.bar?.baz

语法:

obj?.prop       // optional static property access
obj?.[expr]     // optional dynamic property access
func?.(...args) // optional function or method call

等同于:

a?.b                          // undefined if `a` is null/undefined, `a.b` otherwise.
a == null ? undefined : a.b

a?.[x]                        // undefined if `a` is null/undefined, `a[x]` otherwise.
a == null ? undefined : a[x]

a?.b()                        // undefined if `a` is null/undefined
a == null ? undefined : a.b() // throws a TypeError if `a.b` is not a function
                              // otherwise, evaluates to `a.b()`

a?.()                        // undefined if `a` is null/undefined
a == null ? undefined : a()  // throws a TypeError if `a` is neither null/undefined, nor a function
                             // invokes the function `a` otherwise

目前这个提案在 stage 1阶段 proposal-optional-chaining

babel有一个插件 babel-plugin-proposal-optional-chaining现在就可以体验这个功能.

能接触到这个提案,跟知乎上有个问题有关系 “如何和后端沟通,解决api接口返回多层嵌套的json,在中间的某一层为null的问题?” 通过这个问题,我才发现我之前希望后端返回一个默认值这种想法是错误的,因为它可能真的没有初始化,那就是null。 如果给我返回了一个空对象,那代表它没有初始化还是它的属性没有初始化呢?

Read More

提升 CSS 选择器技能

组合选择器 Combinator selectors

属性选择器 Attribute selectors

  • [attr] 表示带有以 attr 命名的属性的元素
  • [attr=value] 表示带有以 attr 命名的,且值为”value”的属性的元素。
  • [attr~=value] 表示带有以 attr 命名的属性的元素,并且该属性是一个以空格作为分隔的值列表,其中至少一个值为”value”。
  • [attr|=value] 表示带有以 attr 命名的属性的元素,属性值为“value”或是以“value-”为前缀(”-“为连字符,Unicode编码为U+002D)开头。
  • [attr^=value] 表示带有以 attr 命名的,且值是以”value”开头的属性的元素。
  • [attr$=value] 表示带有以 attr 命名的,且值是以”value”结尾的属性的元素。
  • [attr*=value] 表示带有以 attr 命名的,且值包含有”value”的属性的元素。
  • [attr operator value i] 在带有属性值的属性选型选择器表达式的右括号(]括号)前添加用 空格间隔开的 字母i(或I)可以忽略属性值的大小写(ASCII字符范围内的字母)
/* 带有title属性的<a>标签  */
a[title] {
  color: purple;
}

/* 链接为"https://example.org"的<a> 标签 */
a[href="https://example.org"] {
  color: green;
}

/* 链接中有"example" 的<a>标签 example.com example.org都匹配 */
a[href*="example"] {
  font-size: 2em;
}

/* 链接以".org"结尾的<a标签> */
a[href$=".org"] {
  font-style: italic;
}

伪类选择器 Pseudo-classes

CSS 伪类 是添加到选择器的关键字,指定要选择的元素的特殊状态。例如,:hover 可被用于在用户将鼠标悬停在按钮上时改变按钮的颜色。

有几十个 下边列举几个常见的

有代表UI状态的

  • :active 匹配被用户激活的元素
  • :checked 选择器表示任何处于选中状态的radio, checkbox元素中的option HTML元素 。
  • :disabled 表示任何被禁用的元素
  • :hover CSS伪类适用于用户使用指示设备虚指一个元素(没有激活它)的情况。 结构选择器
  • ul:nth-child() 接收一个值,该值将与特定的子元素相对于其父容器相匹配。
    • ul:nth-child(2) 匹配第二个子元素
    • ul:nth-child(4n) 匹配4的倍数的子元素 (4, 8, 12, …)
    • ul:nth-child(2n + 1) 匹配2的倍数的下一个位移的元素 (1, 3, 5, …)
    • ul:nth-child(3n — 1) 匹配3的倍数的前一个位移的元素 (2, 5, 8, …)
    • ul:nth-child(odd) 匹配奇数位的元素 (1, 3, 5, …)
    • ul:nth-child(even) 匹配偶数位的元素 (2, 4, 6, …)
  • :nth-last-child()ul:nth-child() 相似,但是从后向前匹配
  • :nth-of-type()ul:nth-child() 相似, 但不是匹配子元素,而是匹配符合类型的子元素
  • :nth-last-of-type() 从后向前匹配类型的子元素
  • :first-child :nth-child() 的语法糖 返回第一个子元素
  • :last-child 代表父元素的最后一个子元素
  • :only-child
  • :first-of-type
  • :last-of-type

内容选择器

  • ::first-line
  • ::first-letter
  • ::selection

感谢原文 Level up your CSS selector skills 感谢 MDN

Read More

JS工具 Platform.js 和 URI.js

一些常用的 判断浏览器功能支持/浏览器版本信息工具

Platform.js

对UserAgent判断浏览器信息的集成。 可以判断浏览器渲染引擎/生产厂家/浏览器名称/操作系统/版本。

使用示例:

// on IE10 x86 platform preview running in IE7 compatibility mode on Windows 7 64 bit edition
platform.name; // 'IE'
platform.version; // '10.0'
platform.layout; // 'Trident'
platform.os; // 'Windows Server 2008 R2 / 7 x64'
platform.description; // 'IE 10.0 x86 (platform preview; running in IE 7 mode) on Windows Server 2008 R2 / 7 x64'

// or on an iPad
platform.name; // 'Safari'
platform.version; // '5.1'
platform.product; // 'iPad'
platform.manufacturer; // 'Apple'
platform.layout; // 'WebKit'
platform.os; // 'iOS 5.0'
platform.description; // 'Safari 5.1 on Apple iPad (iOS 5.0)'

// or parsing a given UA string
var info = platform.parse('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7.2; en; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 11.52');
info.name; // 'Opera'
info.version; // '11.52'
info.layout; // 'Presto'
info.os; // 'Mac OS X 10.7.2'
info.description; // 'Opera 11.52 (identifying as Firefox 4.0) on Mac OS X 10.7.2'

##URI.js 一个操作url的js库

以前要要向search中添加一个内容,需要先判断是否有query再添加,很麻烦,如下:

var url = "http://example.org/foo?bar=baz";
var separator = url.indexOf('?') > -1 ? '&' : '?';

url += separator + encodeURIComponent("foo") + "=" + encodeURIComponent("bar");

URI.js 对URL解析编辑的功能进行了封装

var url = new URI("http://example.org/foo?bar=baz");
url.addQuery("foo", "bar");

使用示例:

// mutating URLs
URI("http://example.org/foo.html?hello=world")
  .username("rodneyrehm")
    // -> http://rodneyrehm@example.org/foo.html?hello=world
  .username("")
    // -> http://example.org/foo.html?hello=world
  .directory("bar")
    // -> http://example.org/bar/foo.html?hello=world
  .suffix("xml")
    // -> http://example.org/bar/foo.xml?hello=world
  .query("")
    // -> http://example.org/bar/foo.xml
  .tld("com")
    // -> http://example.com/bar/foo.xml
  .query({ foo: "bar", hello: ["world", "mars"] });
    // -> http://example.com/bar/foo.xml?foo=bar&hello=world&hello=mars

// cleaning things up
URI("?&foo=bar&&foo=bar&foo=baz&")
  .normalizeQuery();
    // -> ?foo=bar&foo=baz

// working with relative paths
URI("/foo/bar/baz.html")
  .relativeTo("/foo/bar/world.html");
    // -> ./baz.html

URI("/foo/bar/baz.html")
  .relativeTo("/foo/bar/sub/world.html")
    // -> ../baz.html
  .absoluteTo("/foo/bar/sub/world.html");
    // -> /foo/bar/baz.html

// URI Templates
URI.expand("/foo/{dir}/{file}", {
  dir: "bar",
  file: "world.html"
});
// -> /foo/bar/world.html
Read More

JavaScript中的深复制

在JavaScript中该如何复制一个对象?这是一个简单的问题,但答案却并不简单。

通过引用调用

JavaScript 通过传递各种值。下边是个例子:

function mutate(obj) {
  obj.a = true;
}

const obj = {a: false};
mutate(obj)
console.log(obj.a); // prints true

函数 mutate 改变了作为参数传递进来的对象。在“按值调用”的环境中,函数会传递该函数可以使用的值 - 所以是副本。 该函数对该对象所做的任何更改都不会在该函数外部可见。 但是在像 JavaScript 这样的“引用调用”环境中,函数会得到 - 引用,并且会改变实际的对象本身。 因此,最后的 console.log 将显示为 true

然而,有时候,您可能希望保留原始对象并为其他函数创建副本以便使用。

浅复制: Object.assign()

复制对象的一种方法是使用 `Object.assign()target,sources …)。 它需要任意数量的源对象,枚举它们自己的所有属性并将它们分配给目标。 如果我们使用一个新的空对象作为目标,我们基本上就做到了复制。

const obj = /* ... */;
const copy = Object.assign({}, obj);

但是,这仅仅是一个浅拷贝。如果我们的对象包含对象,它们将保持共享引用,这不是我们想要的:

function mutateDeepObject(obj) {
  obj.a.thing = true;
}

const obj = {a: {thing: false}};
const copy = Object.assign({}, obj);
mutateDeepObject(copy)
console.log(obj.a.thing); // prints true

另一件可能的事是 Object.assign()getter 变成简单的属性。 所以现在怎么办?原来,有几种方法可以创建对象的深层副本。

JSON.parse

创建对象副本的最古老方法之一是将对象转换为其JSON字符串表示形式,然后将其解析回对象。这感觉有点霸道,但它确实有效:

const obj = /* ... */;
const copy = JSON.parse(JSON.stringify(obj));

这里的缺点是您创建了一个临时的,可能很大的字符串,以便将其返回到解析器。 另一个缺点是这种方法无法处理循环对象。 尽管你可能会想到,但这些可以很容易地发生。 例如,当您构建树状数据结构时,节点引用其父项,并且父项又引用其自己的子项。

const x = {};
const y = {x};
x.y = y; // Cycle: x.y.x.y.x.y.x.y.x...
const copy = JSON.parse(JSON.stringify(x)); // throws!

此外,诸如Maps, Sets, RegExps, Dates, ArrayBuffers和其他内置类型的东西在序列化时会丢失。成为 String类型。

结构化克隆

结构化克隆是一种现有的算法,用于将值从一个领域转移到另一个领域。 例如,只要调用postMessage将消息发送到其他窗口或WebWorker,就会使用它。 关于结构化克隆的好处在于它处理循环对象并支持大量的内置类型。 问题在于,在编写本文时算法不会直接暴露,只能作为其他API的一部分。我想我们必须看看有哪些…

MessageChannel

正如我所说的,无论何时调用postMessage,都会使用结构化克隆算法。我们可以创建一个MessageChannel并发送消息。在接收端,消息包含我们原始数据对象的结构化克隆。

function structuralClone(obj) {
  return new Promise(resolve => {
    const {port1, port2} = new MessageChannel();
    port2.onmessage = ev => resolve(ev.data);
    port1.postMessage(obj);
  });
}

const obj = /* ... */;
const clone = await structuralClone(obj);

这种方法的缺点是它是异步的。这并不是什么大问题,但有时您需要一种同步方式来深度复制对象。

History API

如果您曾经使用 history.pushState() 构建SPA,那么您知道可以提供一个状态对象来保存URL。 事实证明,这个状态对象在结构上被克隆 - 同步。我们必须小心,不要混淆可能使用状态对象的任何程序逻辑,所以我们需要在完成克隆后恢复原始状态。 为了防止发生任何事件,请使用 history.replaceState() 而不是 history.pushState()

function structuralClone(obj) {
  const oldState = history.state;
  history.replaceState(obj, document.title);
  const copy = history.state;
  history.replaceState(oldState, document.title);
  return copy;
}

const obj = /* ... */;
const clone = structuralClone(obj);

再一次,为了复制一个对象而使用浏览器的引擎感觉有点过分,但是你必须做一些事情。此外,Safari会在30秒内将调用replaceState的次数限制为100次。

Notification API

通知API。通知有一个与它们相关的数据对象被克隆。

function structuralClone(obj) {
  return new Notification('', {data: obj, silent: true}).data;
}

const obj = /* ... */;
const clone = structuralClone(obj);
Read More

前端面试问题 - other

如何实现浏览器多个标签页之间的通信

前端由哪三层构成? 分别是什么? 作用是什么?

浏览器内核由哪些?

哪些工具可以用来测试代码的性能?

同源策略是什么?

Read More

前端面试问题 - JavaScript

解释事件委托

事件委托是一种涉及将事件侦听器添加到父元素而不是将它们添加到后代元素的技术。每当后代元素的触发事件由于冒泡到达绑定DOM,侦听器就会触发。 这种技术的好处是:

  • 内存占用减少,因为父元素只需要一个单独的处理程序,而不必为每个后代附加事件处理程序。
  • 没有必要从已删除的元素中解除绑定处理程序,并将新事件绑定到新元素。

解释JavaScript 中的 this 是如何工作的

this没有简单的解释;它是JavaScript中最令人困惑的概念之一。简单解释是this值取决于函数的调用方式。

  • 如果在调用函数时使用 new关键字,则函数内部this是一个全新的对象。
  • 如果applycallbind用于调用/创建一个函数,则函数内部this是作为参数传入的对象。
  • 如果一个函数作为一个方法被调用,比如obj.method() - this是该函数是作为其属性的对象。
  • 如果一个函数被调用为一个自由函数调用,意味着它在没有上述任何条件的情况下被调用,那么this是全局对象。在浏览器中,它是window对象。如果在严格模式下('use strict'),this将是undefined 的而不是全局对象。
  • 如果适用上述多个规则,则较高的规则将胜出,并将设置this值。
  • 如果函数是ES2015箭头函数,它将忽略上面的所有规则,并在创建时接收其作用域中的this值。

解释原型继承是如何工作的

这是一个非常常见的JavaScript面试问题。所有JavaScript对象都有一个 prototype 属性,即对另一个对象的引用。 当在对象上访问属性时,如果在该对象上找不到该属性,JavaScript引擎将查看该对象的原型和原型的原型等等,直到它找到在其中一个原型上定义的属性,或者直到它到达原型链的末端。

请解释为什么以下内容不能用作IIFE:function foo(){} ();.需要修改哪些内容才能使其成为IIFE?

IIFE代表立即调用函数表达式。JavaScript解析器读取 function foo(){ }(); 作为函数 function foo(){ } (); 其中前者是一个函数声明,后者(一对括号)是尝试调用函数,但没有指定名称,因此它会引发Uncaught SyntaxError: Unexpected token ).

这里有两种方法可以修复它,包括添加更多的括号:(function foo(){ })()(function foo(){ }())。 这些函数不会在全局范围中公开,如果您不需要在本体内引用它们,甚至可以省略它的名称。

变量值之间有什么区别:nullundefined 或未声明?你将如何去检查这些状态?

将值指定给之前未使用varletconst创建的标识符时,会创建未声明的变量。未声明的变量将在当前范围之外的全局范围内定义。在严格模式下,当您尝试分配给未声明的变量时,会引发ReferenceError。 就像全局变量不好一样,未声明的变量很糟糕。不惜一切代价避免它们!要检查它们,请将其用法放在try / catch块中。

function foo() {
  x = 1; // Throws a ReferenceError in strict mode
}

foo();
console.log(x); // 1

undefined 的变量是已声明的变量,但未分配值。它是undefined类型的。 如果函数没有返回任何值,而执行的结果被分配给一个变量,变量的值也是undefined。 要检查它,请使用严格等于(===)运算符或typeof来比较'undefined'字符串。 请注意,您不应该使用抽象相等运算符来检查,因为如果值为null,它也会返回true。

var foo;
console.log(foo); // undefined
console.log(foo === undefined); // true
console.log(typeof foo === 'undefined'); // true

console.log(foo == null); // true. Wrong, don't use this to check!

function bar() {}
var baz = bar();
console.log(baz); // undefined

一个变量为null将被显式分配给null值。它没有表示任何价值,并且与已经明确分配的意义上的undefined不同。 要检查null,只需使用严格的相等运算符进行比较。请注意,如上所述,您不应该使用抽象相等运算符(==)来检查,因为如果值未定义,它也会返回true

var foo = null;
console.log(foo === null); // true

console.log(foo == undefined); // true. Wrong, don't use this to check!

作为一种个人习惯,我从不会将我的变量不声明或未分配。如果我不打算使用它,我会在声明后明确地给它们分配null

什么是封闭,以及如何/为什么要使用封闭?

闭包是函数和声明该函数的词法环境的组合。词汇“词法”一词指的是词汇范围界定使用源代码中声明变量的位置来确定变量可用的位置。 即使在外部函数返回后,闭包也是可以访问外部(封闭)函数的变量作用域链的函数。

你能描述.forEach循环和.map()循环之间的主要区别,以及为什么你会选择一个和另一个?

为了理解两者之间的差异,我们来看看每个函数的作用。 forEach

  • 遍历数组中的元素
  • 为每个元素执行回调
  • 不返回值 ```javascript const a = [1, 2, 3]; const doubled = a.forEach((num, index) => { // Do something with num and/or index. });

// doubled = undefined

`map`
* 遍历数组中的元素
* 通过调用每个元素上的函数将每个元素“映射”到一个新元素,从而创建一个新数组。
```javascript
const a = [1, 2, 3];
const doubled = a.map(num => {
  return num * 2;
});

// doubled = [2, 4, 6]

.forEach.map()之间的主要区别在于.map()返回一个新数组。如果您需要结果,但不希望改变原始数组,则.map()是明确的选择。如果你只需要迭代一个数组,forEach是一个不错的选择。

匿名函数的典型用例是什么?

它们可以用于IIFE来封装局部范围内的一些代码,以便在其中声明的变量不会泄漏到全局范围。

(function() {
  // Some code here.
})();

作为一次使用的回调,不需要在其他地方使用。当处理程序在调用它们的代码内部进行定义时,代码看起来更具自包含性和可读性,而不必搜索其他位置来查找函数体。

setTimeout(function() {
  console.log('Hello world!');
}, 1000);

函数式编程结构或Lodash的参数(类似于回调函数)。

const arr = [1, 2, 3];
const double = arr.map(function(el) {
  return el * 2;
});
console.log(double); // [2, 4, 6]

##宿主对象和原生对象有什么区别? 原生对象是由ECMAScript规范定义的JavaScript语言的一部分,如StringMathRegExpObjectFunction等。 宿主对象由运行时环境(浏览器或Node)提供,例如windowXMLHTTPRequest等。

区别:function Person(){}var person = Person()var person = new Person()

这个问题很含糊。我最好的猜测是它在JavaScript中询问构造函数。从技术上讲,function Person(){}只是一个正常的函数声明。惯例是使用首字母大写的函数作为构造函数。 var person = Person()调用Person作为函数,而不是作为构造函数。如果该函数旨在用作构造函数,则这样调用是常见的错误。通常情况下,构造函数不返回任何东西,因此像调用普通函数一样的调用构造函数将返回undefined var person = new Person()将使用new操作符创建 Person 对象的一个实例,并继承Person.prototype。另一种方法是使用Object.create,比如:Object.create(Person.prototype)

function Person(name) {
  this.name = name;
}

var person = Person('John');
console.log(person); // undefined
console.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined

var person = new Person('John');
console.log(person); // Person { name: "John" }
console.log(person.name); // "john"

.call.apply有什么区别?

.call.apply都用于调用函数,第一个参数将用作函数中的值。 不过,.call需要用逗号分隔的参数作为下一个参数,而.apply需要将参数数组作为下一个参数。

function add(a, b) {
  return a + b;
}

console.log(add.call(null, 1, 2)); // 3
console.log(add.apply(null, [1, 2])); // 3

解释 Function.prototype.bind

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

fun.bind(thisArg[, arg1[, arg2[, ...]]])

什么时候使用document.write()

document.write()将一个文本字符串写入由document.open()打开的文档流中。当document.write()在页面加载后执行时,它将调用document.open(),它清除整个文档(<head><body>被删除!),并用字符串中给定的参数值替换内容。因此它通常被认为是危险的并且容易被误用。

功能检测,功能推断和使用UA字符串有什么区别?

特征检测 功能检测包括确定浏览器是否支持某个代码块,以及是否运行不同的代码(取决于它是否执行),以便浏览器始终可以提供工作体验,而不会在某些浏览器中出现崩溃/错误。 例如:

if ('geolocation' in navigator) {
  // Can use navigator.geolocation
} else {
  // Handle lack of feature
}

Modernizr是处理特征检测的优秀库。

特征推断 特征推断检查功能就像特征检测一样,但使用另一个功能,因为它假设它也会存在,例如:

if (document.getElementsByTagName) {
  element = document.getElementById(id);
}

UA字符串 这是一个浏览器报告的字符串,它允许网络协议对等方识别请求软件用户代理的应用程序类型,操作系统,软件供应商或软件版本。 它可以通过navigator.userAgent访问。 然而,这个字符串很难解析并且可能被欺骗。 例如,Chrome报告Chrome和Safari。 因此,要检测Safari,您必须检查Safari字符串以及是否存在Chrome字符串。 避免这种方法。

尽可能详细地解释Ajax

Ajax(asynchronous JavaScript and XML)是一组使用客户端上的许多Web技术创建异步Web应用程序的Web开发技术。 借助Ajax,Web应用程序可以异步\向服务器发送数据和从服务器检索数据,而不会干扰现有页面的显示和行为。 通过将数据交换层与表示层分离,Ajax允许网页和扩展Web应用程序动态更改内容,而无需重新加载整个页面。 实际上,现代实现通常将JSON替换为XML。 XMLHttpRequest API通常用于异步通信,近来有新的fetch API。

使用Ajax有什么优点和缺点?

优点

  • 交互性更好。来自服务器的新内容可以动态更改,无需重新加载整个页面。
  • 减少与服务器的连接,因为脚本和样式表只需要被请求一次。
  • 状态可以维护在一个页面上。 JavaScript变量和DOM状态将持续存在,因为主容器页面未被重新加载。
  • 基本上大部分SPA的优点 缺点
  • 动态网页很难收藏
  • 如果JavaScript已在浏览器中被禁用,则不起作用
  • 有些网络爬虫不执行JavaScript,也不会看到JavaScript加载的内容
  • 基本上大部分SPA的缺点

解释JSONP是如何工作的(以及它如何不是真正的Ajax)

JSONP(带填充的JSON)是一种通常用于绕过Web浏览器中的跨域策略的方法,因为不允许从当前页面到跨域的Ajax请求。 JSONP通过<script>标记向一个跨域的请求发送请求,通常使用回调查询参数,例如:https://example.com?callback=printData。 然后服务器将数据包装在一个名为printData的函数中并将其返回给客户端。

<!-- https://mydomain.com -->
<script>
function printData(data) {
  console.log(`My name is ${data.name}!`);
}
</script>

<script src="https://example.com?callback=printData"></script>

// File loaded from https://example.com?callback=printData
printData({ name: 'Yang Shun' });

客户端必须在其全局范围内具有printData函数,并且在收到来自跨域的响应时,该函数将由客户端执行。 JSONP可能不安全,并具有一些安全隐患。 由于JSONP真的是JavaScript,它可以完成JavaScript所能做的一切,因此您需要信任JSONP数据的提供者。 现在,CORS是推荐的方法,JSONP被视为hack。

解释 “提升”

提升是用于解释代码中变量声明行为的术语。 使用var关键字声明或初始化的变量将声明“提升”到当前作用域的顶部。 但是,只有声明会被提升,赋值(如果有的话)将保持原样。 我们用几个例子来解释一下。

// var declarations are hoisted.
console.log(foo); // undefined
var foo = 1;
console.log(foo); // 1

// let/const declarations are NOT hoisted.
console.log(bar); // ReferenceError: bar is not defined
let bar = 2;
console.log(bar); // 2

函数表达式的body将会被提升,以变量声明的形式写入的函数只有声明被提升。

// Function Declaration
console.log(foo); // [Function: foo]
foo(); // 'FOOOOO'
function foo() {
  console.log('FOOOOO');
}
console.log(foo); // [Function: foo]

// Function Expression
console.log(bar); // undefined
bar(); // Uncaught TypeError: bar is not a function
var bar = function() {
  console.log('BARRRR');
};
console.log(bar); // [Function: bar]

描述事件冒泡

当一个事件在DOM元素上触发时,如果附加了一个监听器,它将尝试处理事件,然后事件冒泡到其父代并发生同样的事情。 这种冒泡发生在元素的祖先到文档的整个过程中。 事件冒泡是事件委托的机制。

“attribute”和“property”之间有什么区别?

attribute 在HTML标记中定义,但 property 在DOM上定义。 为了说明这种差异,假设我们在HTML中有这个文本字段:<input type =“text”value =“Hello”>

const input = document.querySelector('input');
console.log(input.getAttribute('value')); // Hello
console.log(input.value); // Hello

但是将你想输入框中输入 World ,他将成为

console.log(input.getAttribute('value')); // Hello
console.log(input.value); // Hello World!

文档load事件和文档DOMContentLoaded事件之间的区别?

DOMContentLoaded事件在初始HTML文档被完全加载和解析时触发,而不等待样式表,图像和子帧完成加载。 windowload 事件仅在DOM和所有相关资源和资产已加载后才会触发。

===== 有什么区别?

== 是抽象的相等运算符,而 === 是严格的相等运算符。在进行任何必要的类型转换后,==运算符将进行比较。===运算符不会进行类型转换,所以如果两个值不是相同类型===将简单地返回false。当使用==时,可能发生意外的事情,例如:

1 == '1'; // true
1 == [1]; // true
1 == true; // true
0 == ''; // true
0 == '0'; // true
0 == false; // true

我的建议是绝对不要使用==运算符,除非为了便于与nullundefined进行比较,其中如果a为nullundefined,则a == null将返回true

var a = null;
console.log(a == null); // true
console.log(a == undefined); // true

解释关于JavaScript的同源策略

同源策略阻止JavaScript通过域边界发出请求。 源被定义为URI方案,主机名和端口号的组合。 此策略可防止一个页面上的恶意脚本通过该页面的文档对象模型访问另一个网页上的敏感数据。

实现以下功能 duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

function duplicate(arr) {
  return arr.concat(arr);
}

duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

为什么称为三元表达式,“三元”这个词表示什么?

“三元”表示三个,三元表达式接受三个操作数,测试条件,“then”表达式和“else”表达式。 三元表达式不是特定于JavaScript,我不确定它为什么甚至在这个列表中。

什么是 "use strict"; ?使用它有什么优点和缺点?

"use strict"; 是用于对整个脚本或单个函数启用严格模式的语句。严格模式是加入JavaScript受限变体的一种方式。

优点:

  • 使它无法意外地创建全局变量
  • 将拼写错转成异常
  • 试图删除不可删除的属性会抛出错误(在之前尝试没有任何效果)
  • 要求函数参数名称是唯一的
  • 在全局范围内this是未定义的
  • 捕获了一些常见的编码错误,抛出异常
  • 禁用混淆或糟糕的功能 缺点:
  • 许多开发人员可能习惯使用禁用的功能
  • 无法再访问 function.callerfunction.arguments
  • 以不同严格模式编写的脚本串联可能会导致问题

创建一个for循环迭代到100,同时输出3的倍数的“fizz”,5的倍数“buzz”和3和5倍数的“fizzbuzz”。

for (let i = 1; i <= 100; i++) {
  let f = i % 3 == 0,
    b = i % 5 == 0;
  console.log(f ? (b ? 'FizzBuzz' : 'Fizz') : b ? 'Buzz' : i);
}

为什么总的来说,把网站的全局范围保持原样并且永远不要碰它是一个好主意?

每个脚本都可以访问全局范围,如果每个人都使用全局名称空间来定义自己的变量,那么肯定会碰撞。使用模块模式(IIFE)将变量封装在本地命名空间中。

为什么你会使用类似load事件?这个事件有缺点吗?你知道其他的选择吗?你为什么要使用这些选项?

load 事件在文档加载过程结束时触发。此时,文档中的所有对象都位于DOM中,并且所有图像,脚本,链接和子帧都已完成加载。

DOM事件DOMContentLoaded将在页面的DOM构建完成后触发,但不要等待其他资源完成加载。在某些情况下,如果在初始化之前不需要装入整个页面,则这是首选。

解释一个单页面应用程序是什么以及如何使他SEO友好。

现在,Web开发人员将他们构建的产品称为Web应用程序,而不是网站。 虽然这两个术语之间没有严格的区别,但网络应用程序往往具有高度的交互性和动态性,允许用户执行操作并接收他们的操作响应。 传统上,浏览器从服务器接收HTML并呈现它。 当用户导航到另一个URL时,需要整页刷新,并且服务器会为新页面发送新的新HTML。 这被称为服务器端渲染。 但是在现代SPA中,客户端渲染被用来代替服务端渲染。 浏览器从服务器加载初始页面,以及整个应用程序所需的脚本(框架,库,应用代码)和样式表。 当用户导航到其他页面时,不会触发页面刷新。 该页面的URL通过HTML5 History API进行更新。 新页面所需的新数据(通常采用JSON格式)由浏览器通过AJAX请求检索到服务器。 然后,SPA通过JavaScript来动态更新页面,这些JavaScript在初始页面加载时已经下载。 这种模式类似于原生移动应用程序的工作方式。

好处:

  • 感觉响应更快,并且由于非全页面刷新,用户看不到页面导航之间的闪光。
  • 对服务器提出的HTTP请求数量较少,因为不必为每次页面加载重新下载相同的资产。
  • 将客户和服务器之间的关注清晰分开; 您可以轻松地为不同平台(例如手机,聊天机器人,智能手表)建立新客户端,而无需修改服务器代码。 只要API合同没有中断,您也可以独立修改客户端和服务器上的技术堆栈

缺点:

  • 由于加载了多个页面所需的框架,应用程序代码和资源,导致初始页面负载更重。
  • 在你的服务器上还有一个额外的步骤,即配置它将所有请求路由到一个入口点,并允许客户端路由从那里接管。
  • SPA依赖于JavaScript来呈现内容,但并非所有搜索引擎都在抓取过程中执行JavaScript,并且他们可能会在您的页面上看到空的内容。 这无意中伤害了您的应用的搜索引擎优化(SEO)。 然而,大多数情况下,当您构建应用程序时,搜索引擎优化并不是最重要的因素,因为并非所有内容都需要通过搜索引擎进行索引。 为了克服这个问题,您可以在服务器端呈现您的应用,或者使用Prerender等服务来“在浏览器中呈现您的JavaScript,保存静态HTML并将其返回给抓取工具”。

使用Promise而不是回调有什么优点和缺点?

优点

  • 避免可可读性差的的回调地狱
  • 使用.then()可以轻松编写可读的顺序异步代码。
  • 使用Promise.all()编写并行异步代码很容易。 缺点
  • 稍微复杂的代码(有争议的)
  • 在不支持ES2015的旧浏览器中,您需要加载一个polyfill才能使用它。

你用什么语言构造迭代对象属性和数组项目?

对象

  • for循环 - for(var property in obj){console.log(property); }。但是,这也会遍历其继承的属性,并且在使用它之前将添加obj.hasOwnProperty(property)检查。
  • Object.keys() - Object.keys(obj).forEach(function(property){...})Object.keys()是一个静态方法,它将列出您传递它的所有对象的枚举属性。
  • Object.getOwnPropertyNames() - Object.getOwnPropertyNames(obj).forEach(function(property){...})Object.getOwnPropertyNames()是一个静态方法,它将列出您传递它的对象的所有可枚举和不可枚举的属性。 数组
  • for循环 - for(var i = 0; i <arr.length; i ++)。 这里的常见错误是var在函数范围内,而不是块范围,大多数时候你想要块范围的迭代变量。 ES2015引入了具有块范围的let,建议使用它。 所以这变成:for(let i = 0; i <arr.length; i ++)
  • forEach - arr.forEach(function(el,index){...})。 这个构造有时可能更方便,因为如果你需要的只是数组元素,你不必使用索引。 还有一些方法可以让你尽早终止迭代。

大多数情况下,我更喜欢.forEach方法,但这取决于你想要做什么。 for循环允许更多的灵活性,例如使用break或者每次循环多次递增迭代器过早地终止循环。

什么是事件循环?调用堆栈和任务队列之间有什么区别?

事件循环是一个单线程循环,用于监视调用堆栈并检查是否有任何工作要在任务队列中完成。 如果调用堆栈为空并且任务队列中有回调函数,则将函数出队并推送到调用堆栈中执行。

解释函数 foo(){}var foo = function(){} 之间 foo 用法的区别。

前者是函数声明,后者是函数表达式。关键的区别在于函数声明有它的主体,但函数表达式的主体不是(他们具有与变量相同的提升行为)。

// 函数声明
foo(); // 'FOOOOO'
function foo() {
  console.log('FOOOOO');
}
// 函数表达式 
foo(); // Uncaught TypeError: foo is not a function
var foo = function() {
  console.log('FOOOOO');
};

使用letvarconst创建的变量之间有什么区别?

使用var关键字声明的变量作用于创建它们的函数,或者在任何函数之外创建的全局对象。 let和const是块范围的,这意味着它们只能在最近的一组花括号中使用(函数,if-else块或for-loop)。 var允许变量被提升,这意味着它们可以在声明之前在代码中被引用。 let和const不会允许这个,而是抛出一个错误。 用var重新声明一个变量不会抛出错误,但’let’和’const’会发生。 let和const的区别在于let允许重新赋值变量的值,而const不赋值。

什么是高阶函数的定义?

高阶函数是将一个或多个函数作为参数的任何函数,它用于对某些数据进行操作,和/或返回函数作为结果。 高阶函数是为了抽象一些重复执行的操作。 这个典型的例子是map,它将一个数组和一个函数作为参数。 map然后使用该函数转换数组中的每个项目,并返回一个包含转换数据的新数组。 JavaScript中的其他流行示例包括forEach,filter和reduce。 高阶函数不仅操作数组,从另一个函数返回函数的用例很多。 Array.prototype.bind是JavaScript中的一个例子。

javascript 的 typeof 返回哪些数据类型?

强制类型转换的方法 和 隐式类型转化的方法

解释一下数组方法 pop push unshift shift

ajax 的时候 get 和 post 的区别

ajax的时候 如何解析 json数据

事件委托是什么?

闭包是什么?有什么特性,对页面有什么影响?

对this的理解?

nodejs的应用场景?

js的二分算法实现?

如何定义vue-router的动态路由 参数的获取

vuex是什么 哪些场景适合?

vue-router是什么? 哪些场景适用?

vue的生命周期?

vue组件的定义与传参?

Read More

前端面试问题 - CSS

“resetting” 和 “normalizing” CSS 的区别? 你会选择哪一个?

  • Resetting 重置是为了去除元素上的所有默认浏览器样式。例如对于所有元素的marginpaddingfont-size重置为相同。 你将不得不为常见的元素重新声明样式。
  • Normalizing 规范化保留了有用的默认样式,而不是“移除所有样式”。它还纠正了常见浏览器的错误。

CSS 隐藏元素的几种方法?

// 使用 opacity 透明度属性
.hide {
  opacity: 0;
}
// 设置 Visibility 可见性
hide {
   visibility: hidden;
}
// 使用 display  显示性
.hide {
   display: none;
}
// 使用固定定位移出屏幕
.hide {
   position: absolute;
   left: -9999px;
}
// 如果不考虑子元素 可以设置宽高为0
 .hide {
       width: 0;
       height: 0;
    }

CSS清除浮动的几种方法?

1.直接给元素加上 clear:both

    main {float: left}
    aside {float: right}
    footer {clear: both}

2.添加一个空的div <div style="clear: both;"></div>

3.给父元素加上 overflow: hidden 父元素将扩展以包含浮动元素,从而有效地清除后续元素

4

.clearfix:after { 
   content: "."; 
   visibility: hidden; 
   display: block; 
   height: 0; 
   clear: both;
}

描述一下 float 以及它是如何工作的?

float 是一个 css 定位属性。 浮动元素仍然是页面流的一部分,并且会影响其他元素的位置(例如,文本将在浮动元素周围流动), 与position:absolute元素不同,它们从页面流中移除。

CSS clear 属性可以用于位于 left/right/都有 浮动元素之下。

如果父元素只包含浮动元素,则其高度将被折叠为无。可以通过清除容器中浮动元素之后但在容器关闭之前的浮动来修复它。

.clearfix hack使用一个聪明的CSS伪选择器(:after)来清除浮动。 不用在父级上设置overflow ,而是对其应用额外的类clearfix。然后应用这个CSS:

.clearfix:after {
  content: ' ';
  visibility: hidden;
  display: block;
  height: 0;
  clear: both;
}

或者,将overflow:autooverflow:hidden属性赋予父元素, 这将在子元素内部建立一个新的块格式上下文,并将扩展为包含其子元素。

描述z-index以及如何形成堆叠上下文

CSS中的z-index属性控制重叠元素的垂直叠加顺序。z-index仅影响position 属性值不是static 的元素。

如果没有任何z-index值,元素按照它们出现在DOM中的顺序堆叠(在相同层次结构层级中最低的一个出现在最上面)。 具有非静态定位的元素(及其子元素)将始终显示在具有默认静态定位的元素的顶部,而不管HTML层次结构如何。

堆叠上下文是包含一组图层的元素。在本地堆栈上下文中,其子元素的z-index值是相对于该元素而不是文档根目录设置的。 在该上下文之外的层(即本地堆栈上下文的同级元素)不能位于其内的层之间。 如果元素B位于元素A的顶部,则即使元素C具有比元素B更高的 z-index ,元素A的子元素C也永远不会高于元素B.

每个堆叠上下文都是自包含的 - 在堆叠元素的内容之后,整个元素将按照父堆叠上下文的堆叠顺序进行考虑。 少数CSS属性会触发新的堆叠上下文,如opacity小于1,filter 不是0,并且transform 不是none

描述块格式化上下文(BFC)及其工作原理。

块格式化上下文(Block Formatting Context,BFC) 是Web页面的可视化CSS渲染的部分, 是块级盒布局发生的区域,也是浮动元素与其他元素交互的区域。 浮动,绝对定位的元素,inline-blocks, table-cells, table-captionoverflow 不是visible的元素(除非该值已传播到视口)会建立新的块格式上下文。 创建块格式化上下文的方式如下:

  • 根元素或其它包含它的元素
  • 浮动元素 (元素的 float 不是 none)
  • 绝对定位元素 (元素的 position 为 absolute 或 fixed)
  • 内联块元素 (元素具有 display: inline-block)
  • 表格单元格 (元素具有 display: table-cell,HTML表格单元格默认属性)
  • 表格标题 (元素具有 display: table-caption, HTML表格标题默认属性)
  • 匿名表格元素 (元素具有 display: table, table-row, table-row-group, table-header-group, table-footer-group [分别是HTML tables, table rows, table bodies, table headers and table footers的默认属性],或 inline-table )
  • overflow 值不为 visible 的块元素,

在BFC中,每个盒子的左外边缘都与包含块的左边缘接触(用于从右到左格式化,右边缘接触)。

块格式化上下文对于定位 (参见 float) 与清除浮动 (参见 clear) 很重要。 定位和清除浮动的样式规则只适用于处于同一块格式化上下文内的元素。 浮动不会影响其它块格式化上下文中元素的布局,并且清除浮动只能清除同一块格式化上下文中在它前面的元素的浮动。 外边距合并也只发生在属于同一块格式化上下文的块级元素之间。

解释CSS精灵,以及如何在页面或网站上实现它们。

CSS精灵将多个图像合并为一个较大的图像。它是图标常用的技术。 如何实现它:

  1. 使用一个将多个图像打包成一个的精灵生成器,并为其生成适当的CSS。
  2. 每个图像都有一个相应的CSS类,其中定义了background-imagebackground-positionbackground-size属性。
  3. 要使用该图像,请将相应的类添加到您的元素中。

优点:

  • 减少多个图像的HTTP请求数量(每个spritesheet只需要一个请求)。但对于HTTP2,加载多个图像不再是个问题。
  • 提前下载直到需要时才会下载的资产,例如只出现在以下情况的图像:悬停伪状态。闪烁不会被看到。

你将如何解决浏览器特定的样式问题?

  • 在确定问题和有问题的浏览器后,使用单独的样式表,仅在使用特定浏览器时才加载。尽管这种技术需要服务器端渲染。
  • 使用类似Bootstrap的库已经为您处理了这些样式问题。
  • 使用 autoprefixer 自动添加供应商前缀到您的代码。
  • 使用重置CSS或Normalize.css。

你如何为功能受限的浏览器提供页面?你使用什么技术/流程?

  • 优雅降级 - 为现代浏览器构建应用程序的做法,同时确保它在旧版浏览器中保持功能。
  • 渐进式增强 - 构建用户体验基础级应用程序的实践,但在浏览器支持时添加增强功能。
  • 使用caniuse.com检查功能支持。
  • 用于自动供应商前缀插入的Autoprefixer。
  • 使用Modernizr进行功能检测。

你有没有使用网格系统,如果有的话,你喜欢什么?

我喜欢基于浮动的网格系统,因为它仍然支持其他现有系统(flex,grid)中最多的浏览器支持。 它已经在Bootstrap中使用了很多年,并且已经被证明是可行的。

描述伪元素并讨论它们的用途。

CSS伪元素是一个添加到选择器的关键字,可以让您选择所选元素的特定部分。

  • 它们可以用于装饰(:first-line, :first-letter)或将元素添加到标记(content: ...),而无需修改标记(:before, :after)。 :first-line:first-letter 可以用来装饰文本。

  • 在如上所示的.clearfix hack中使用,用clear:both来添加零空间元素。
  • 工具提示中的三角箭头使用:beforeafter之后。鼓励分离关注点,因为三角形被视为样式的一部分,而不是真正的DOM。如果不使用额外的HTML元素,只用CSS样式绘制三角形是不太可能的。

解释你对盒子模型的理解,以及如何告诉浏览器在不同的盒子模型中呈现你的布局。

CSS框模型描述了为文档树中的元素生成的矩形框,并根据可视化格式模型进行布局。 每个盒子都有一个内容区域(例如文本,图像等)以及可选的周围paddingbordermargin 区域。

CSS框模型负责计算:

  • 块元素占用多少空间。
  • 边框和/或边距是否重叠或折叠。
  • 一个盒子的尺寸。

盒子模型有以下规则:

  • 块元素的尺寸通过width, height, padding, bordermargin 来计算。
  • 如果未指定height,则块元素将与其包含的内容一样高,并加上padding(除非有浮动)。
  • 如果未指定width,则非浮动块元素将展开以适合其父辈减去padding的宽度。
  • 元素的height由内容的height计算。
  • 元素的width由内容的width计算。
  • 默认情况下,paddingborder 不是元素宽度和高度的一部分。

* {box-sizing:border-box; }是做什么的呢?它的优点是什么?

  • 默认情况下,元素具有box-sizing:content-box应用,并且只有内容大小被计入。
  • box-sizing: border-box 改变元素widthheight 的计算方式,borderpadding 也包含在计算中。
  • 现在通过内容的高度+垂直填充+垂直边框宽度来计算元素的高度。
  • 现在通过内容的宽度+水平填充+水平边框宽度来计算元素的宽度。

inlineinline-block 之间有什么区别?

  • block inline-block inline  
    尺寸 填充其父容器的宽度 取决于内容 取决于内容
    定位 开始一个新行,并且不允许旁边的HTML元素(除非添加浮点数) 与其他内容一起流动,并允许旁边的其他元素 与其他内容一起流动,并允许旁边的其他元素
    可以指定宽度和高度 可以 可以 不可以, 将忽略
    可以vertical-align对齐 No Yes No
    marginpadding 每一边都会遵守 每一边都会遵守 只有水平两边起作用。如果指定了垂直边,也不会影响布局。垂直空间取决于line-height,即使 borderpadding 出现在内容周围。
    浮动 - - 变得像块元素,您可以设置垂直 marginpadding

relative,fixed,absolute 和 static 位置元素之间有什么区别?

  • static - 默认位置;元素将像往常一样流入页面。 toprightbottomleftz-index属性不适用。
  • relative - 该元素的位置相对于其自身进行调整,而不会改变布局(并且因此为未定位的元素留下间隙)。
  • absolute - 该元素从页面流中移除,并定位在相对于其最接近定位祖先的指定位置(如果有的话),或相对于初始包含块。绝对放置的盒子可以有边距,并且不会与其他边缘合并。这些元素不会影响其他元素的位置。
  • fixed - 该元素从页面流中移除并位于相对于视口的指定位置,并且在滚动时不移动。
  • sticky - 粘滞定位是相对和固定定位的混合体。该元素被视为相对定位,直到它超过指定的阈值,此时它被视为固定位置。

    简要说一下CSS 的元素分类?

Read More

前端面试问题 - HTML

DOCTYPE 有什么作用?

DOCTYPE 是 “document type” 的简写。它是HTML中用来区分标准模式和严格模式的声明。 它的存在告诉浏览器以标准模式呈现网页。只需在页面开始处添加 <!DOCTYPE html> 即可。

data- 属性的优点?

在JavaScript框架流行之前,前端开发人员使用 data- 属性在DOM上存储储本身的额外数据, 它旨在将自定义数据存储到页面或应用程序,为此没有更多适当的属性或元素。 现在,不鼓励使用 data- 属性。 原因之一是用户可以通过在浏览器中使用审查元素轻松修改数据属性。 数据模型最好存储在JavaScript本身中,并通过数据绑定(通过库或框架)与DOM保持更新。

将HTML5视为开放的网络平台。什么是HTML5的基石?

  • 语义化 - 允许您更准确地描述您的内容。
  • 连接 - 允许您以创新方式与服务器通信。
  • 离线和存储 - 允许网页在客户端本地存储数据并更有效地离线运行。
  • 多媒体 - video 和 audio 现在是web 的一级公民
  • 性能和集成 - 提供更高的速度优化和更好的计算机硬件使用。
  • 设备访问 - 允许使用各种输入和输出设备。
  • 样式 - 可以创作更复杂的主题。

cookies sessionStorage 和 localStorage 的区别?

cookie:

  • 最大 4K
  • 可以设置过期时间
  • 会随着请求在浏览器-服务器之间传递(服务器通过 Set-Cookie header 设置)
  • 有域关联 可以设置 http-only 使客户端无法读取/修改 sessionStorage:
  • 最大 2.5MB (各浏览器不同)
  • 不会随请求发送到服务器
  • 关闭一个使用sessionStorage中的tab/ 新建一个tab / 退去浏览器 ,都会丢失其中的数据 localStorage:
  • 最大 2.5MB (各浏览器不同)
  • 不会随请求发送到服务器 没有过期时间
  • 关闭tab/浏览器 也不会丢失

<script>, <script async> and <script defer> 之间的区别

普通的 <script> 标记将阻止页面的呈现,并且该页面将不会继续加载直到脚本结束。

<script async>将异步运行脚本,这意味着它不会阻止渲染,但会在脚本可用后立即运行。 这通常用于CDN文件或其他不改变页面结构的文件。

<script defer> 将延迟脚本在页面完成解析之后和onload事件之前运行。

<link>放在头上是规范的一部分。除此之外,放置在顶部允许页面逐步呈现,这提高了用户体验。 还可以防止无样式内容的闪烁,从而在解析页面的其余部分时为用户提供一些内容。

由于JavaScript在默认情况下阻止了渲染,并且DOM和CSSOM构造也可能被延迟,所以通常最好将脚本保留在页面的底部。 例外情况是,如果您异步获取脚本,或者至少将它们推迟到页面末尾。

什么是渐进渲染?

渐进式渲染是用于提高网页性能(尤其是提高感知加载时间)以尽可能快地呈现内容以供显示的技术。 过去在宽带互联网之前的日子里它更普遍,但它在现代发展中仍然有用,因为移动数据连接变得越来越流行(并且不可靠)! 这种技术的例子: 这种技术的例子:

  • 延迟加载图像 - 页面上的图像不会一次全部加载。当用户滚动到显示图像的页面部分时,JavaScript将用于加载图像。
  • 确定可见内容的优先级(或折叠渲染) - 仅包含将在用户浏览器中呈现的页面数量所需的最小CSS /内容/脚本,以便尽可能快地显示 您可以使用延迟脚本或监听DOMContentLoaded / load事件来加载其他资源和内容。
  • 异步HTML片段 - 当页面在后端构建时,将HTML的一部分刷新到浏览器。

为什么你会在图片标签中使用srcset属性?解释浏览器在评估此属性内容时使用的过程。

响应式图片

<img srcset="elva-fairy-320w.jpg 320w,
             elva-fairy-480w.jpg 480w,
             elva-fairy-800w.jpg 800w"
     sizes="(max-width: 320px) 280px,
            (max-width: 480px) 440px,
            800px"
     src="elva-fairy-800w.jpg" alt="Elva dressed as a fairy">

srcset 包含 文件名 空格 图像的固有尺寸
sizes 包括 一个媒体条件 空格 当条件为真时, 图片将填充的宽度。定义了一组媒体条件(例如屏幕宽度)并且指明当某些媒体条件为真时,什么样的图片尺寸是最佳选择—我们在之前已经讨论了一些提示。

Read More

Web Animation API - 在 JavaScript 中释放 CSS keyframes 的力量

如果你曾经使用过CSS3关键帧动画,那么你可能会感受到这个功能受到的严重阻碍。 一方面,CSS关键帧可以让你使用纯CSS创建复杂的动画,也存在一个问题 - 所有的事情都必须在 CSS 内部预先声明。 jQuery 中的方法之一是 animate() 方法,它可以快速设置元素上的动画,而无需在 CSS 和 JavaScript 之间来回切换。

Read More

使用FLIP技术设置动画布局

直观且易于理解的用户界面才是最有效的。 动画在这方面起着重要的作用 - 正如 Nick Babich 所说 animation brings user interfaces to life。 然而, 增加有意义的过渡和微观互动往往是事后的, 或者是 “好的”, 如果时间允许的话。 通常,我们使用的 web 应用只是简单的从一个视图”跳”到另一个视图,而不会给用户时间来处理当前上下文中发生的事情。

Read More

使用 ffmpeg 转换 mp4 为 gif 文件

在 Web 中尽量使用 mp4 而不是 gif ,因为 gif 文件要比 mp4 大很多。 但还是会有一些需求 或者是 变态产品经理 要求把 mp4 转为 gif 使用。 mmfpeg 是一个优秀的格式转化工具

Read More

现在更不需要jQuery了

自从2006年jQuery发布以来,DOM和原生浏览器API都有了飞跃性的提高。 自2013年以来,人们一直在撰写“You Might Not Need jQuery”的文章(请参阅此网站和此项目)。 本文并不想重复以前的内容,但自从上一版“You Might Not Need jQuery”文章,您可能已经偶然发现了浏览器上的一点点改变。 浏览器继续实现新的API,从无框架开发中解脱出来,其中许多API直接从jQuery中复制。

Read More

Vue.js中的 Transition 和 Animation

Animations & transitions. 将您的网站或网络应用程序带入生活,吸引用户探索。 动画和过渡是UX和UI设计的组成部分。 但是,他们很容易出错。 在复杂情况下,如处理处理列表。依赖原生JavaScript和CSS的时候,几乎不可能实现出来。 每当我问后台开发人员为什么他们不喜欢这个前台时,他们的反应通常都是“…动画”的一部分。

Read More

你不知道的 (S)CSS 最佳实践

即使您现在使用流行的框架(如React,Angular或Vue.js)构建应用程序,您仍然需要为其添加一些样式。 根据您使用的技术,您需要以特定的方式编写您的样式。 例如对于React,由于它是组件性质,最好使用 CSS modules 编写样式。 如果你想使用最新的 CSS 功能,使用 CSSNext 是明智之举。 不要忘记很好的预处理器,比如 Sass 或者 LESS。你可能会想-这么多工具,我打赌每一个的写作风格是不同的。你是对的,但基础是一样的。

Read More

网页检查离线状态

并不是网站的每个部分都可以离线工作, 有些内容不可以被缓存,有的部分需要与服务器交互。 离线优化网站应当适应连接的变化。

Read More

Animating your hero header

如果你曾经访问过一些网站,迎接你的是一张大图或者视频,并且有大标题在上边。 你遇到的正是”Hero Header”,这是一种常见的介绍网站的形式。 它可以是一个向用户展示你的网站是做什么的的好机会。

Read More

被动事件监听

读了微软Edge团队的一篇文章 讲了现代浏览器对滚动的优化,即把滚动从主线程移动到了后台线程。文中有一个展示,主线程被js弄卡顿时,页面一样可以滚动。不过我在mac上尝试的 时候,只有safari可以,chrome和firefox都卡住了,囧。估计在windows上firefox和chrome实现了吧。

Read More

二月份学习

$.now() 获得距离1970年1月1日的毫秒数。

ECMAscript 1.0 中提供的方法 new Date().getTime(); ECMAscript 5.1 中提供的方法 Date.now();

$.parseJSON() 发布于2010年,IE8(支持JSON)出现的次年,针对IE6 IE7 ,所以现在没必要使用了。

Read More

向一个只读文件写入内容

今天在 AWS 上安装 MongoDB, 按照官网的指南 在用 vi 编辑 mongodb-org-3.4.repo 这个文件时遇到了一点问题。这是一个敏感位置的文件,我没用 sudo打开。 保存的时候 用 wq! 强制保存报错。

Read More

point-events

记得有个同事问过我,怎么让一个图片浮在另一个图片上边,然后不影响下边图片的点击事件。 当时我的想法是事件冒泡之类的,但可惜两个图片只能是平级的,而且是以z-index的形式浮在上边的。 后来的做法是判断点击位置,解决了。 今天看到知乎上一个相关的问题,才发现当时太幼稚了。 其实只要对后者加上 point=events: none,就可以取消它的事件并穿透给后边的元素。

Read More

Linux screen & two remote git

一直在命令行中跑 node ,当关闭bash之后,任务就会停止。今天在发布项目到 aws时我就考虑,一旦我关闭了ssh ,不就停止了吗。 找了一下,linux 有个 screen 命令,只要在 npm前加个 screen ,就会新开一个screen,并且只要任务没结束,它就不会被关闭。 当再次登录到aws后,执行 screen -ls 就可以显示所有正在运行的screen, 执行 screen -r 加上要切换到的screen 端口,就可以切换过去。


Read More

mac 下升级 Emacs

最近在学习使用 mac 下的 emacs ,OS X 自带的版本是 22, 我就想升级一下。但是这种预装的软件升级是很麻烦的,参考了下Emacs 网站上的这篇文章

Read More

在git中设置代理

最近在git中提交代码的时候总是遇到 443 Timeout 的问题。谷歌了一下找到了设置代理的方法

Read More

关于 LocalStorage 的一些思考

今天又看了一下 LocalStorage ,LocalStorage 是 HTML5 提供的客户端存储数据的新方法。它主要有五个方法:

  • LocalStorage.clear() 不接收参数,无返回值。作用是将所有保存的数据清理。
  • LocalStorage.getItem(keyName) 接收一个键名 返回键名的值 ,键和值都是String,确切的说,都是 DOMString
  • LocalStorage.key(key) 接收一个 integer 型参数,返回LocalStorage中第 n(key)条数据的值。LocalStorage中数据的排序是按被添加的顺序排列的。这个方法并不可靠。
  • LocalStorage.removeItem(keyName); 接收一个键名,无返回值。作用是将该条数据从LocalStorage中移除。
  • LocalStorage.setItem(keyName, keyValue);接收键名和键值,无返回值。将数据保存到本地LocalStorage。
Read More

介绍 Google Resizer

google design 近日推出了一款resizer,可以在线辅助查看站点的响应式设计,可以认为是chrome开发者工具的一个专版吧。 看一下我们网站首页的效果,看起来还不错。

在google上还有一篇介绍它的文章 (地址)(https://design.google.com/articles/introducing-resizer/) 官方的简介是:“一个互动查看器可以通过desktop,mobile和table查看并测试数字产品是如何响应 Material Design的”。说到Material Design ,google一直在大力推广这种统一设计,并开发了一个css框架,MDL,我曾把它的英文站点翻译成中文站 http://getmdl.tech/getmdl/

Read More

HTML 中的电话链接

<a href="tel:1-562-867-5309">1-562-867-5309</a>

事实上,即使没有tel ,浏览器也会自行判断是否为电话的,所以有时候我们需要禁用浏览器的判断

Read More

今天的一道算法题

今天的一道算法题

今天做了一道算法题,要求是从数组中找到最先能够相加等于结果的两个值,如果有多个,则选择最先的两个。例子

Read More

又一个HTML5 Boilerplate 选择

为什么要使用HTML5 Boilerplate

在我处初学html的时候和早期做项目的时候,是怎么初始化一个项目的呢?一直都是比较随意,用emmet 的!就完事了。等遇到了问题再去解决,比如需要reset,就加上reset.css,遇到jquery就加上jquey.js。后来发现有些工作很重复,就自己写个模板放下一些常用的引用文件,新项目的时候就直接开始。其实这就是一种模板,只是内容不完全而已。后来我遇到了HTML5 Boilerplate这个项目,简直一见如果,关于他的作者来历什么的就不说了,它在github上有近3万个star就足以说明一切。它的优势主要是包含了很多基础内容,对ie什么的也都有准备。

boy 又一个选择

今天在网上看到了boy,遂npm看了一下,确实不错,虽然内容没有HTML5 Boilerplate那么丰富,但它的主页也说明了,这是一个轻量级的,兼容ie6的Boilerplate。那么它对ie6都做了哪些呢

###box-sizing

我个人觉得,理解不理解box-sizing,是一个菜鸟CSSer和一个初级CSSer的分界线啊,box-sizing带来的好处是毋庸置疑的。以及为什么bootstrap中为何用padding来做栅栏的边界,都是因为它好用。但令人遗憾的是ie6,7都不支持这个属性。boy它就引用了这样一个htc文件,可以使ie67支持box-sizing,这真是一个好消息。

css3选择器

这个功能我个人倒不是很看好,觉得并不重要,它只是增强了jquery等的选择器。

我希望有的功能

background-sizing 属性

这个大家都知道老旧浏览器不能设置background-sizing属性,对于相应式有多麻烦,网上有流传的一些htc,我觉得作者选择一个优秀的整合进来会更好。

##其他的选择

我推荐另一款Boilerplate Initializr这个工具你可以自定义选择的内容比较多,比如bootstrap等,也很优秀。

Read More

关于回调函数的进一步理解

在以前的学习中,我知道了回调是一种更合理的异步执行方式,是作为中级程序员必须掌握的技能。 今天我在读《javascript patterns》时获取到了更进一步的知识

Read More

Trigger-Native-Event

使用jQuery 触发原生事件

这篇文章,主要目的是练习使用markdown语法。 昨天做项目的时候遇到了一点小问题,我在页面上引用了百度分享的插件,我想点击页面上的其他位置的一些按钮的时候也能够触发分享。一开始我使用了jquery的trigger方法,不管我是触发这个按钮还是他的父元素子元素上的click事件都没有效果。 忽然想到以前在做一个d3的项目时候也遇到过类似的情况。可能是这个按钮上的事件并不是jquery事件。 所以我使用了 $(".btn)[0].click()这种方法去触发原生的事件,果然有用。

Read More