seho 发布的文章

前情提要:本文需要有一定的webpack,npm基础哟
前几天,日常搬砖的我突然想到,前端工程化将编码的工作变得简单,但是我们并没有深究其根本去研究它们是如何运作的,比如vue-cli是vue项目开发用的非常多的脚手架工具,最新版本3是基于webpack4来开发的,巧了,最近一直再寻找webpack训练题材的我想到了一个很好的办法,我们可以自己通过webpack4来搭建一个vue-cli项目

timg.jpg

话不多说,我们从0开始;

1. 准备一个空项目

npm init

然后根据提示设置项目名称等等,然后我们安装webpack


npm i webpack webpack-cli -s-d

安装成功后,我们需要建立一个目录【build】,在这里面我们存储3个文件,一个是webpack开发配置,一个是线上配置,一个是抽象出来的公共配置,为了我们能够将配置文件一次到位,我们需要安装很多很多的插件,我们一步一步来

2. 整合webpack的多个配置文件

1. 新建webpack.common.js

引入一些node的核心模块

const path = require("path");

通过module.exports导出一个对象:

entry: "./src/main.js",
output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "[hash]_[name].js",
},

我们把所有的文件导出到dist目录下,文件名前面加入一个hash值

一般在写vue的时候,我们引入模块不会写.js,.vue这样的后缀,因为vue-cli设置了自动搜索文件的功能,它能帮助我们去寻找对应的文件拓展名

所以我们写入一个resolve配置

 resolve: {
    // 拓展名,在引入文件的时候,不用写以下的后缀,让webpack自己去寻找
    extensions: ['.js', '.vue'],
    alias: {
        '@': path.resolve('src'),
        // 这是因为正在使用的是vue的运行时版本,而此版本中的编译器时不可用的,我们需要把它切换成运行时 + 编译需要在配置文件中添加如下代码
        'vue$': 'vue/dist/vue.esm.js'
    }
},

可以看到extensions我们配置了js和vue,这个地方我们不宜配置过多的拓展名,比如css,html,xml等等,这样会拖慢webpack打包的速度

在alias中我们配置了一个"vue$",这个配置非常重要,将解决后面的vue文件解析失败的问题,需要加这一句配置

2. 安装一大波依赖

  vue-loader
  babel-loader
  @babel/core
  style-loader
  css-loader
  postcss-loader
  file-loader
  ...

具体的非常多,我列出一个package.json,我的预装脚手架包含了axios和vue-router

  "dependencies": {
    "@babel/core": "^7.7.7",  // babel核心
    "axios": "^0.19.0", // 请求ajax库
    "copy-webpack-plugin": "^5.1.1", // 插件: 拷贝文件
    "stylus": "^0.54.7", // 博主预装的:stylus
    "stylus-loader": "^3.0.2", // 预装的stylus-loader
    "vue": "^2.6.11", // vue核心是必须的
    "vue-router": "^3.1.3", // 预装了一个vue-router
    "webpack": "^4.41.5", // webpack 核心
    "webpack-cli": "^3.3.10", // webpack脚手架
    "webpack-merge": "^4.2.2" // webpack的合并几个配置文件的包
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2", // postcss中的核心,自动加入css前缀
    "babel-loader": "^8.0.6", // babel的loader
    "clean-webpack-plugin": "^3.0.0", // 清空文件夹下的所有内容
    "css-loader": "^3.4.0", // cssloader: 分析各种依赖关系的css
    "file-loader": "^5.0.2", // 文件loader
    "html-webpack-plugin": "^3.2.0", // 自动生成html
    "postcss-loader": "^3.0.0", // postcss的loader
    "precss": "^4.0.0", // postcss的核心功能
    "style-loader": "^1.1.2", // 把css代码加入到head中
    "url-loader": "^3.0.0", // 基于file-loader的封装有更多的拓展,本文中所有配置建议使用url-loader
    "vue-loader": "^15.8.3", // 解析vue文件的loader
    "vue-template-compiler": "^2.6.11", // vue模板解释器
    "webpack-dev-server": "^3.10.1" // webpack-dev-server服务器,开发时候的必备服务器
  }


3. 配置module

module: {
    rules: [{
        test: /\.vue$/, // 处理vue文件
        loader: 'vue-loader'
    }, {
        test: /\.js$/,
        loader: "babel-loader", // 处理es6+的语法
        exclude: /node_modules/
    }, {
        test: /\.css$/, // 处理css
        use: ["style-loader", "css-loader", "postcss-loader"]
    }, {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, // 处理图片,这边尽量使用url-loader
        loader: 'file-loader',
        options: {
            name: "[name].[ext]"
        }
    }, {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, // 处理音频
        loader: 'url-loader',
        options: {
            limit: 10000
        }
    }, {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, // 处理字体
        loader: 'url-loader',
        options: {
            // 把较小的图片转换成base64的字符串内嵌在生成的js文件里
            limit: 10000,
            name:'[name].[ext]'
        }
    }, {
        test: /\.styl/, // 处理stylus的语法
        use: [
            'style-loader',
            'css-loader',
            "postcss-loader",
            'stylus-loader'
        ]
    }]
},

use下的数组配置了很多的loader,它们是从右到左去执行的

里面有几个值得注意的地方,就是postcss,我们需要额外的建立一个配置文件在根目录

postcss.config

内容:

module.exports = {
    plugins: [
      require('precss'),
      require('autoprefixer')
    ]
 }

配置了2个核心之后,我们就可以使用css写出更多自由的东西,自动帮助我们添加了兼容前缀

4. 配置html模板

我们在根目录创建一个html文件,名字叫做index.html,内容如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>A Vue.js Project</title>
</head>
<body>
    <div id="app">
    </div>
</body>
</html>

这个html文件就是我们的vue程序打包出来的html,我们可以在这里配置网站标题,最主要的是要写一个id为app的div,因为稍后我们的vue程序将挂载到这个app实例上去

接下来我们配置一下common的插件

plugins: [new HtmlWebpackPlugin({
    template: './index.html'
}), new VueLoaderPlugin(), new CopyWebpackPlugin([{
    from: path.resolve(__dirname, '../public'), // 不打包直接输出的文件
    to: '', // 打包后静态文件放置位置
    ignore: ['.*'] // 忽略规则。(这种写法表示将该文件夹下的所有文件都复制)
}])]

HtmlWebpackPlugin首先这个插件就是生成html,选址了一个我们刚刚建立好的模板即可,自动会把dist下的js加入到html中哦~~
VueLoaderPlugin则是vue解析的插件
CopyWebpackPlugin 把public下的所有文件放到dist下

我们完成了common的配置,在开发环境下的和线上环境的配置是不一样的,比如soucemap,还有treeshaking,webpack-dev-server等等

所以我们在build目录下建立

webpack.dev.js


const webpack = require("webpack"); // 引入webpack引用
const merge = require("webpack-merge");
const common = require("./webpack.common");

module.exports = merge(common,{
mode: "development",
devtool: "cheap-module-eval-source-map",
// dev模式下独有的webpack-dev-server配置
devServer: {
    contentBase: "./dist",
    open: true,
    // HMR
    hot: true,
    hotOnly: true
},
plugins: [new webpack.HotModuleReplacementPlugin()]
})
我们在开发环境做的配置并不多,首先我们的mode是development,webpack有一个自动的代码压缩,配置了webpack推荐的开发环境调试的devtool:
"cheap-module-eval-source-map",
还有在开发启动的服务器(webpack-dev-server),并且开启了HMR(热模块更新)

并且我们引入了刚刚完成的common.js的配置,利用webpack-merge这个插件,将dev的配置和common的配置进行一个合并

同理,我们建立prod

webpack.prod.js

const {
    CleanWebpackPlugin
} = require('clean-webpack-plugin');
const merge = require("webpack-merge");
const common = require("./webpack.common");
module.exports = merge(common, {
mode: "production",
entry: "./src/main.js",
// prod独有的配置  treeshaking和splitChunks
optimization: {
    // 代码分割
    splitChunks: {
        chunks: 'all'
    },
    // 配置treeshaking
    usedExports: true
},
plugins: [new CleanWebpackPlugin()]
})
在线上的配置,我们采用了基本的treeshaking和代码分割,并且CleanWebpackPlugin,在打包的时候清除掉了dist下的所有文件再进行打包,这样就能让dist下目录下保持最新的代码

配置package.json中的脚本

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"serve": "webpack-dev-server --progress --config build/webpack.dev.js",
"build": "webpack --progress --config build/webpack.prod.js"
  },

npm run serve --启动开发配置
npm run build --启动线上配置

注意注意,我们webpack配置文件中的入口写的是entry: "./src/main.js",所以需要新建src目录,并且新建一个main.js,然后尝试写一个console或者其他的js语法,试一下程序是否正常

开始配置main.js写一个vue的helloworld

如果我们的脚本能正常运行,说明我们的配置的没有问题

我们需要在src下新建一个App.vue,让App.vue成为所有组件的父容器

App.vue

<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

<style>
</style>

然后我们在src下只建立pages文件夹和router文件夹
pages: 存放vue页面的文件夹
router:存放路由的文件夹

在我们的demo中我们在pages下建立list文件夹,然后在list文件夹下建立index.vue,内容如下

<template>
  <div class="box">
    hello world
  </div>
</template>

<script>
export default {
};
</script>

<style lang="stylus" scoped>
    .box
       color red;
</style>

在router文件夹下定义index.js文件

import Vue from "vue";
import VueRouter from "vue-router";

Vue.use(VueRouter);

// 引入路由
import list from "@/pages/list/index.vue";

export default new VueRouter({
    routes: [{
        path: "/",
        component: list
    }]
})

在main.js中引入router和App.vue

import Vue from 'vue';
import router from "../src/router"
import App from './App.vue';

new Vue({
    el: '#app',
    template: '<App/>',
    components: { App },
    router
})

如果你能在浏览器中看到helloworld呈红色就说明你成功了~~

架构其他的目录

每个人有每个人写代码的习惯,我的习惯是之前和蜗牛老师学的,一直觉得这种风格非常的好

在src目录下新建文件夹: api, assets, config

api: 接口的service层
assets:存放一些icon,css,和js
config: config配置项

我们在这篇文章简单分享一下我的习惯,大家做一个参考

首先我会在asstes下的js文件夹下建立request.js,作为请求文件

request.js

import axios from "axios";
/**
 * 封装的通用请求类
 * @param {*} url 
 * @param {*} data 请求参数
 * @param {*} method 请求类型
 * @param {*} locktype 加密类型
 */

export default function request(url, data, method, locktype) {
    return new Promise((resolve, reject) => {
        axios.request({
            url,
            method,
            data,
        }).then(res => {
            if (res.data.code === 200 && res.status === 200) {
                resolve(res.data.data);
            }
        }).catch(error => {
            console.log(error);
            reject();
        })
    })
}

然后在api文件夹下的index.js中引入request

import request from "@/assets/js/request";


export function getList(url, data, method) {
    return new Promise((resolve) => {
        request(url, data, method).then(res => {
            resolve(res)
        })
    })
}

一般情况下,api下的js命名一般都是模块,比如登录模块,首页,商城等等

然后我们会在具体的vue文件中引入api/index, 将里面的方法导出来,传递参数进行请求

这样架构的好处有很多,vue只有视图逻辑&获取数据,基本不会做其他的过多数据处理,这样非常好维护,并且可以使更多的接口复用,同样的更好维护

ok,我们本次的分享到此结束,目前为止,我们的脚手架是全部跑通了的,仅仅只有引入图片,非常奇怪,不能直接引入public目录下的文件,需要import一下,但是在vue-cli3直接引入public下的是不需要编译的,所以哪位朋友解决了,就私信艾特我

微信: meng99huan 请求内容: 因卓诶 webpack脚手架引用静态资源问题

目录结构在这里:

微信截图_20200105170656.png

本篇文章的脚手架github地址,点我获取

写在前面,很久没有整理面试,笔试题了,这次准备了2个月的题,理论来讲是一天3道,一共是180道题,我们这篇文章会自动跳过一些之前说过的题,尽量挑重要的整理

src、href、link的区别是什么?

src:指的是指向资源,比如srcipt和img标签使用src来表达资源路径
href:指的是指向路径,比如a标签,表达了跳转的路径
link:指的是css中的link,用来表达层叠样式表的资源路径

这三者都不受同源限制影响

多维数组扁平化

1. arr.toString().split(",");
2. arr.flat(Infintity); // 多维转换,参数表示正无穷,写1就是只转换一维,写2转换2维......

3. return arr.reduce((prev, current) => {
    return prev.concat(Array.isArray(current) ? 递归函数(current) : current)
},[])

有用过HTML5的webSQL和IndexedDB吗?说说你对它们的理解

没有用过,但是websql是被废弃的,现在大多数都使用indexedDB,空间大,存储多,可以异步存储,支持键值对,也支持js对象

怎样把一个div居中?怎样把一个浮动元素居中?怎样把绝对定位的div居中?

margin 0 auto
transform
margin-left clac(50% - width/2)
position: absolute left: 0 top: 0 bottom 0 top 0 ; margin 0 auto
flex
grid
table

写一个方法获取图片的原始高度和宽度

用异步,new image对象,监听onload并取naturalWidth和naturalHeight即可,记得赋值src给img

有使用过html5的拖放API吗?简单说一下

简单使用过,img标签默认可拖拽,其他标签需要开启拖拽开关drage=true
h5提供了一系列元素的监听事件,可以用拖拽事件来做出一些有趣的效果

有用过HTML5的WebWork吗?它主要解决了什么问题?

js是单线程语言,webwork允许我们注册一个服务,让这个服务在一个单独的线程进行处理,不会影响到主线程,大大提高了js的
复杂业务执行效率
但是在这个线程中,

  1. 必须是和主线程是同源
  2. 没有parent,window,document不能更改dom,navigator和location是可以用的
  3. woker和主线程必须通过消息进行传递
  4. 没有alert等,可以发送ajax请求
  5. 不能加载本地文件,资源必须来源于网络

说说你对同构和SSR的理解(理解,简单过一下)

同构,就是一套代码能在客户端允许也能在服务端允许
ssr就是为了爬虫的需要/框架初始化/单页面的卡顿,在service端把数据处理好返回给页面
优点:首页加载快,seo好,请求次数减少

ssr和spa不是相对概念
spa和mpa是相对概念,多页应用在以前非常火,有多个前端入口,路由后端控制等,比如php和jsp都是经典的mpa应用

说说position的absolute和fixed共同与不同点分别是什么?

都脱离了文档流
一个是可以相对父元素,一个是相对于浏览器

举例子说明javascript的变量声明提升和函数声明提升

var a = funtion a(){
    console.log(4)
}
funtion a(){
    console.log(5)
}

console.log(a()) // 4

html直接输入多个空格为什么只能显示一个空格?

该行为由css的white-space控制,默认值normal会把多个空格压缩成一个空格

HTML5如果不写<! DOCTYPE html> ,页面还会正常工作么?

可以,只不过浏览器会以自己默认的规则进行加载网页,每个浏览器的规则都有区别
所以会造成多个浏览器不兼容的情况

元素竖向的百分比设置时相对容器的高度吗?

不是,如果是的话,那么高度是相对高度,父元素根据子元素的高度会自动撑开,然后子元素
知道父元素撑开为了百分比要去弥补这个差距,会造成一个死循环

所以是相对容器的宽度不是高度

请写出唤醒拔打电话、发送邮件、发送短信的例子

<a href="tel: 110">拨号</a>
<a href="sms: 110">发短信</a>
<a href="emilto: 1018715564@qq.com">邮箱</a>

写个例子说明HTML5在移动端如何打开APP?

原理deeplink
使用a标签中的href="应用名称(问具体开发)://"

css和动画和js动画区别

1. 性能不差
2. 部分场景下优于js
3. 比较多的兼容问题

怎样禁止表单记住密码自动填充?

autocomplate="off"

请说说*{box-sizing: border-box;}的作用及好处有哪些?

怪异盒模型,会把border,padding算在盒子的宽,比较符合人类的认知

对base64的理解

上传图片可以用base64上传
url参数加密也可以用base64

html的a标签属性rel='nofollow'有什么作用

告诉爬虫不要爬链接中的内容了

Ajax请求中get和post方式有什么区别呢?分别在哪些场景下使用?

get参数内存小,因为是通过url传参,浏览器的url参数有限制
get请求参数会暴漏在用户表面
get请求可以被历史记录

get请求通常是获取一个仅读资源

怎么在IE8及以下实现HTML5的兼容?

使用这个插件html2shiv, 原理就是创建element

写一个字符串重复的repeat函数

repeat函数
使用array.join();
循环拼接变量,然后循环完毕返回
循环放入数组,输入变成字符串返回

在实际编写css中你有遇到过哪些浏览器兼容性的问题?怎么解决的?

低版本得IE的CSS特效等

postcss autoprefixer,browerlist

移动端点击事件为什么会有延迟?有哪些方法可以解决?

因为早期的浏览器规定的就是,点击一下,如果300ms之内又点了第二下会放大屏幕

那么解决办法就是不让他放大,设置meta标签,禁止缩放
还有就是使用fastclick这个库,原理:可以在点击的第一下上生成一个div并且触发点击事件然后把默认事件屏蔽掉

在a标签上的四个伪类执行顺序是什么?

link visited hover active 记忆法:lvha(lv好)

说说你对!important的理解,一般在哪些场景使用?

不建议使用,提高css样式的最高权重,非常不利用维护

写一个方法随机生成指定位数的字符串

Math.random().toString(36).substr(2); .// 生成一个随机字符串

请你解释下什么是浮动和它的工作原理是什么?同时浮动会引起什么问题?

浮动会脱离标准文档流, 浮动元素会互相紧贴,可能会造成高度塌陷

js中=、==、===三个的区别是什么?并说明它们各自的工作过程

=赋值操作
== 比较值,会隐式转换,简单比较
=== 不仅会比较值,还会比较内存地址,复杂比较

transition、animation、transform三者有什么区别?

1. 过渡:无非就是图像元素在做偏移/变化时,过渡动画能更好的让用户接受,不再生硬
2. 自定义动画:可以让开发者逐帧操控动画,可玩度更高
3. 图像转换移动: 2d,3d移动,放大缩小等等都是经典的场景

请写出如下代码运行的结果并解释为什么?[代码]

代码是这个:

var type = 'images';
var size = {width: 800, height: 600};
var format = ['jpg', 'png'];

function change(type, size, format){
    type = 'video';
    size = {width: 1024, height: 768};
    format.push('map');
}
change(type, size, format);
console.log(type, size, format);

change中的更改几个变量,都是更改的行参的值,而传递的format,push更改了原数据,所以只有数组增加了,其他都没变化

你在工作中有用到过websocket吗?用它来解决什么问题?

用到过,聊天的时候做过,兼容不太好,但是有解决方案,用于客户端和服务端进行互相推送的功能,解决了传统的推送传输耗费性能

遇到overflow: scroll不能平滑滚动怎么解决?

ios: -weibit-overflow-scorlling:touch;

举例说明数组和对象的迭代方法分别有哪些?

es5: for,forEach,some,reduce,reduceRight,map,for-in,every,filter
es6: for-of,find,findIndex
对象:
es5: for-in,Object.keys()
es6: Object.entries(); Object.values();

请快速答出此题的答案并解释:var x, y = 1; x + y = ?

NaN, y是等于1 ,x只是定义了没赋值 undefined+1=NaN

HTML5的应用程序缓存与浏览器缓存有什么不同?

浏览器缓存提供的就是单个文件的缓存,而HTML5的PWA中的离线缓存技术是整站缓存,即没有存在网络也可以访问网站,基于service-woker标准实现

说说你对BEM规范的理解,同时举例说明常见的CSS规范有哪些?

指的是css命名规范,bem认为所有的网页是一个一个块组成的,block,element,modifier(修饰语)
header-button-primary 类似于这样

举例说明什么是IIFEs?它有什么好处?

立即执行函数

(funtion(){
    // code something
})();

也可以这样哦!function(){}(), ~function(){}()  let a = function(){}()

节约变量,独立作用域,立即运行

简述下HTML的快捷键属性是哪个?并举例说明有什么用?

accesskey,h5支持每个元素都可以注入accesskey,搭配浏览器快捷组合键,实现快速访问的功能

这无疑推广了无障碍的发展,让更多不健全的人也能更方便使用web网页

举例说明什么是decodeURI()和encodeURI()是什么?

对内容进行解码,对内容进行加码

你有用过HTML5的Device API吗?说说它都有哪些应用场景?

没有用过,听说过,是html5的一种规范api,可以监听手机屏幕亮度,电量,振动,打电话之类的‘

js实现99乘法表

function nine(num){
for(var i = 1;i<=num;i++){//控制行
var expression = "";
for(var j = 1;j<=i;j++){//j<=i 控制每行的个数
expression +=`${j}*${i}=${i*j}`;
}
console.log(expression)
}
}
nine(9);

在新窗口打开链接的方法是什么?那怎么设置全站链接都在新窗口打开?

target="_blank";

a标签下的href =“ javascript:void(0)”实现了什么作用?说说你对javascript:void(0)的理解?

void是js的一个关键字,表示没返回值,这句代码主要是不让a链接产生跳转(默认行为)

如何理解同源策略

同源策略就是浏览器的一种安全策略,避免和不安全的资源进行通信

1. 不同协议
2. 不同域名
3. 不同端口

解决办法: cors jsonp proxy代理等

怎么让body高度自适应屏幕?为什么?

html,body{height: 100%} 把html带上是因为,如果只给body加不是真正的高自适应,因为会继承html的高度,html高度未必是100%,所以两个都要加100%
body{height: 100vh} vh不会继承父类,直接是视口的高度

js延迟加载的方式有哪些?

async defer setTimeout 动态绑定src 动态创建script标签

display有哪些值?分别说明他们的作用是什么?

table
flex
grid
inlie-block
none
block

把Script标签放在页面最底部的之前和之后有什么区别?浏览器会如何解析它们?

肯定是一个优先解析,一个等文档加载完再执行,在body前,script标签加载的js文件会阻塞html的渲染,所以一般会写在body后,或者给标签加入async 或者 defer 或者type="module"来异步加载js文件

写出几个初始化CSS的样式,并解释说明为什么要这样写

padding 0 margin 0 outline: none;
文档有默认的外边距和内边距,需要清除,还有点击按钮的时候会有点击的边框,a标签的默认样式,列表的默认样式等等

请写一个sleep(暂停)函数

function sleep(ms) {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, ms);
      });
}

说说你对CSS样式覆盖规则的理解

关键词: ICE公式 特指度
有一个概念叫做特指度,也就是平时所说的权重大小,每一个选择器都有一个权重值,id=100 class=10 标签是1,这个公式叫做ICE,
通配符最小
最高权重是important,通常在项目中蛮少使用到,因为维护性很差

判断instanceof的结果并解释原因 [代码]

代码如下:

function test(){
   return test;
}
new test() instanceof test;

instanceof 的运行原理是右边的priototype去左边的原型上去找,左边的原型链找完还没有就返回false,这段代码new出来的构造函数结果就是构造函数本身,因为有继承,所以继承了所有的
test方法的属性,所以test.protype并不在test()上存在,所以返回false

CSS的overflow属性定义溢出元素内容区的内容会如何处理呢

visble 超出将会显示元素外面
scorll 超出显示滚动条
hidden超出部分不显示
auto 内容溢出时候是scroll

请描述下null和undefined的区别是什么?这两者分别运用在什么场景?

null和undefined都不是对象
null是空
undefined 变量已定义未被赋值

一般初始化变量会初始化成null,因为undefind的情况很多,而且根据代码语义,未被定义不符合标准
因为未被定义的变量就是undefined,赋值undefined是一个多此一举的事情

解释下为什么{} + [] === 0为true?

{} 是混淆项,不参与运算,+[] === 0是true  那么{}被认定为语法块
+[] === 0 是类型转换


console.log包裹{}会被解析成一个对象,不会解析成一个语法块

在不固定高度的情况下水平垂直居中的方法?

万能定位法
定位50% 通过margin或者transform移动-50%
flex

js的函数有哪几种调用形式?

fn()
fn.apply call bind
new fn();

obj.fn();

当一个元素被设置为浮动后,它的display值变为什么呢?

变为block,设置浮动看样子是inline-block,其实是block
可以通过getComputedStyle查看属性

写出js的交集并集补集差集

/** *交集 * * @param {*} arr1 * @param {*} arr2 */function intersection (arr1, arr2) {
  return arr1.filter(v => arr2.includes(v))
}


/** *差集 * * @param {*} arr1 * @param {*} arr2 */function difference (arr1, arr2) {
  return arr1.filter(v => !arr2.includes(v))
}


/** *并集 * * @param {*} arr1 * @param {*} arr2 */function union (arr1, arr2) {
  return [...arr1, ...arr2]
}


/** *补集 * * @param {*} arr1 * @param {*} arr2 */function complement (arr1, arr2) {
  return difference(union(arr1, arr2), intersection(arr1, arr2))
}

Doctype有什么作用?你知道有多少种Doctype文档类型吗?(理解)

声明文档类型,告诉浏览器以什么标准进行解析,如果不提供,浏览器将会自动根据厂商标准去补充

一般会使用
<! doctype html>

html4则有3种:

Strict,Transitional和Frameset;

写例子说明如何给li绑定事件(ul下有1000+个li)?

利用事件委托

function handleClick(event){
    if(event.target.tagName === "LI"){
        // do something
    }
}

监听ul的点击事件即可

移动端微信页面有哪些兼容性问题及解决方案是什么?

1. rem方案,需要使用reset.js
2. video标签层级最高,解决是获取了video的第一帧图片作为预览图,替换video

说说你理解的同步和异步的区别是什么?

1.    上一个任务结束才能进行下一个任务。比如alert弹窗,不点击确定永远在哪里,阻塞了下面的所有同步js代码
2.    可以按顺序开始,但是不一定按顺序返回,比如img加载图片就是典型的异步

如何让大小不同的图片等比缩放不变形显示在固定大小的div里?写个例子

object-fit这个属性非常好用
设置宽高定位

如何让大小不同的图片等比缩放不变形显示在固定大小的div里?写个例子

读分2种,一种是看,一种是读取里面的信息

看:chorme有自带的pdf预览插件,直接src
读:读pdf,二进制数据,根据pdf的规则去解析(pdf是开源格式),然后再根据解析出来的通过html或者canvas去渲染(难点),然后pdf中的图片可以使用二进制提取然后bloburl
做为链接
pdf.js的原理

行内元素、块级元素、空(void)元素分别有哪些?

    1. span a input img b strong select button em
    2. div video selection header footer article h1-h6 table form
    3. img input br hr

准确说出'1,2,3,4'.split()的结果是什么(包括类型和值)?

["1,2,3,4"] 如果split没传参数,会返回调用值为第一个数的数组

进程与线程有什么区别?JS的单线程带来哪些好处?

一个程序必定包含一个进程,一个进程必定包含一个线程
一个进程必定包含一个线程

进程之间不能共享内存,而线程可以

js最早是面向浏览器的脚本语言,开发者在对dom做处理时,如果是多个线程,引擎就不知道时哪个线程做了操作,这样的程序会很紊乱
因此js是要和用户实时交互的,所以是单线程

在一些复杂业务中,我们也可以使用web-woker来做计算处理,但是它不能操作dom

请列举出多种减少页面加载时间的方法(答案来源于github某网友)

• 缓存利用: 缓存 Ajax,使用 CDN、外部 JavaScript 和 css 文件缓存,添加 Expires 头,在服务器端配置 Etag,减少 DNS 查找等。
• 请求数量.合并样式和脚本,使用 css 图片精灵,初始首屏之外的图片资源按需加载,静态资源延迟加载。
• 请求带宽:压缩文件,开启 GZIP 。
• css 代码:避免使用 css 表达式、高级选择器、通配选择器 。
• JavaScript 代码:用散列表来优化查找,少用全局变量,用 innerHTML 代替 DOM 操作,减少 DOM 操作次数,优化 JavaScript 性能,用 setTimeout 避免页面失去响应,缓 存 DOM 节点查找的结果,避免使用 with (with 会创建自己的作用域, 增加作用域链的 长度),多个变量声明合并。
• HTML 代码:避免图片和 iFrame 等 src 属性为空 。 src 属性为空,会重新加载当前页面 , 影响速度和效率 , 尽量避免在 HTML 标签中写 Style 属性。

怎么实现移动端的边框0.5px?

伪元素+缩放,宽高是原dom的百分之200,然后缩小.5可以实现
兼容高分辨率的屏幕

border.css的原理

写个方法,找出指定字符串中重复最多的字符及其长度

const str = "啦啦啦哈哈哈哈";
let result = {}; // 存放全部的结果集的数组

function test1(box = []){
// 转换数组
let array = str.split("");
array.forEach(v => {
if(typeof result[v] === "undefined"){
// 添加对应的属性
result[v] = 1;
}else{
result[v] ++;
}
})
}

test1();
console.log(result);

网站的TDK该怎么设置?它有什么作用?

TDK: 标题 & 介绍 & 关键词
seo的最重要的三要素,网站如果设置好这三要素将会提升被蜘蛛抓到更多的内容,有更多的曝光

请描述下js的原型和原型链的理解以及它们之间的关系

js的继承就是原型的继承
每一个对象都有原型,而这个原型分为隐式原型和显式原型
比如Function的prototype,指向的就是函数的原型,而原型中的 constructor即构造函数指的就是函数本身
对象的原型是_proto_即隐式原型,指的是创建该对象的函数的原型

原型是原型链的一部分,每一个对象都有原型,都会有共同的属性,如果直到原型是null,说明是最顶层的原型,那原型链即如此

请描述下什么是原型模式?它主要运用在哪些场景?(理解)

原型模式是一种设计模式,就是创建一个共享的原型实例,通过拷贝这些原型创建新的对象,也就是创建一个对象作为另一个对象的Prototype属性。

优点:用于创建重复的对象,可以提升性能。
结合优点,可以运用在以下几种场景:
1、类初始化需要消耗很多资源
2、有多个调用者调用且每个调用者会修改其值,相当于保存一份原有的对象拷贝相同对象进行修改,即保护性拷贝
3、通过new对象时需要很多繁琐的准备或访问权限

请详细描述AJAX的工作原理(答案来源于github网友)

AJAX是用于网页和服务器进行异步通信的技术。
基本原理是,通过XMLHttpRequest向服务器发送异步请求,获得服务器返回的数据,利用js更新页面。
其核心功能在于XMLHttpRequest对象。
创建一个ajax的步骤大致可以分为以下几步

  • 创建XHMHttpRequest对象
  • 打开链接 (指定请求类型,需要请求数据在服务器的地址,是否异步i请求)
  • 向服务器发送请求(get类型直接发送请求,post类型需要设置请求头)
  • 接收服务器的响应数据(需根据XMLHttpRequest的readyState属性判定调用哪个回调函数)
  • 更新页面

HTML如何创建分区响应图?

什么是分区相应图:

一张图片,可以把它分割成多个区域,点击图片的不同区域就会跳转到不同的url,这就是分区相应图
这种功能开发中应该很常见,尤其是APP中的引导页,当只有一张整素材的时候,需要前端确定用户点击了某处的按钮,我称之为幽灵按钮
但是通过分区相应图可以很快的实现这种效果

通过map和area实现

<p><img src="blue.png" usemap="#myMap" alt="Blue Map"/>3 </p>4 
<map name="myMap">5   
 <area href="area1.html" shape="rect" coords="5,8,30,32" alt="area1" />6    
 <area href="area2.html" shape="rect" coords="34,8,50,32" alt="area2" />7   
 <area href="other.html" shape="default" alt="default"/>8 
</map>

使用js编写一个红绿灯的程序:

function sleep (t) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve()
    }, t)
  })
}


/** * 循环显示红绿灯 * @param {number} green 绿灯显示毫秒数 * @param {number} yellow 黄灯显示毫秒数 * @param         {number} red 红灯显示毫秒数 */async function light (green = 15000, yellow = 3000, red = 10000) {
  let status = 'green'
  while (true) {
    await sleep(green).then(() => {
      status = 'yellow'
      console.log(status)
    })
    await sleep(yellow).then(() => {
      status = 'red'
      console.log(status)
    })
    await sleep(red).then(() => {
      status = 'green'
      console.log(status)
    })
  }
}


light(3000, 1000, 1000)


timg.jpg

从技术手段上提高打包速度

跟上技术的迭代(升级node,npm或者yarn),因为webpack是基于node的,node的升级对webpack性能也会有提升,npm中很多的依赖包存在升级,那么对打包的速度也是有提高的
在尽可能少的模块上应用loader,loader的使用会对更多的文件去解析,所以在配置文件中,比如要把node_modules中的文件进行排除,因为可能依赖项已经被打包过,再打包一遍会更浪费事件
关于plugins的使用,尽量使用社区/官方推荐或者非常流行的插件,因为通常这类插件的稳定性和易用性得到了大多数人的认可,而第三方公司或者私人的插件可能会造成对项目优化不友好的地方
所以尽量避免使用plugins

通过resolve配置来提升打包速度

webpack中的resolve可以帮助我们引入第三方模块或者组件的时候,可以能够更方便的引入,比如通常在写vue的时候

import commonHeader from "../components/commonHeader"

不用指定其组件的后缀“vue”就可以找到,那么vue-cli是如何做到的呢,webpack配置就是如下:

resolve: {
    extensions: ["vue", "js", "jsx"]
}

可以通过这样简单配置,如果在目录下,没有找到对应的文件,那么就会再次遍历去寻找配置项指定后缀文件,但是这样的配置如果我们配置的过多的话,比如像这样

["jpg", "png", "json"]

一股脑地全部配上,这样就会在webpack打包中增加压力,延缓打包速度(因为要多次遍历查询)

所以一般的解决方案是:“只配置逻辑文件,如jsx,js,vue”等

如果我们引入配置的时候,引入的路径是这样的

import common from "./common/"

会发现,当组件的名称如果叫index.**时,这句代码是正确的,如果是其他名称,就‘

会报错,是因为webpack的默认项会把index当作主文件,那么我们也可以通过这样的配置进行更改:

mainFiles: ["index", "common"]

resolve中也还有其他常用的配置

比如alias(别名),比如我要这样引入

import common from "shenhao"

我们可以设置一个别名

alias: {
    shenhao: path.resolve(__dirname, ../src/common)
}

然后我们就可以使用alias进行引入,感觉通过这样的描述会感觉这个属性非常鸡肋,其实并不然,比如非常复杂的目录结构,要引入一个包要写很长的路径

如果写过vue的开发者来讲,@符号就表示src下的根目录,那么这个@就是一个别名

但是为了webpack的打包性能,尽量不要使用过多的配置alias和上面的extensions,配置项目中最常用的即可哟

使用DllPlugin提高打包速度

我们尝试使用一个babel打包一个react的程序,如图:

import React, { Component } from 'react';
import ReactDom from 'react-dom';
import _ from 'lodash';


class App extends Component {
    render() {
        return (
            <div>
                <div>{_.join([1,2,3,4,5], "...")}</div>
            </div>
        )
    }
}


let haha = document.createElement("div");
document.body.appendChild(haha);
ReactDom.render(<App />, haha);

经过多次打包,速度平均稳定在

Image.png

那么问题来了,我们每一次打包都会分析react,react-dom等库,我们可以尝试把这些库只分析一次,之后的打包读这些分析过的文件即可

首先我们需要额外的分析这些第三方库

新建webpack.dll.js

const path = require("path");


module.exports = {
    mode: "production",
    entry: {
        vendors: ["react", "react-dom", "lodash"]
    },
    output: {
        path: path.resolve(__dirname, 'dll'),
        filename: '[name].dll.js',
        library: "[name]"
    }
}

我们向外暴漏一下全局变量,然后我们则要让打包后的index.html去引入这个第三方库编译后的js
ps: 要让打包后的index.html自动引入,则需要add-asset-html-webpack-plugin这个插件,做一个简单配置即可(必须)

但是我们在main.js中的引入

根本引入的不是我们编译好的js文件,还是引入的是node_modules中的

  1. 让main.js引入的文件映射到编译过的第三方库文件中

首先配置一下,webpack.dll.js中我们增加一句代码

plugins: [new webpack.DllPlugin({
    name: "[name]",
    path: path.resolve(__dirname, "dll/[name].manifest.json")
})]

引入了webpack之后,使用了webpack中的DllPlugin模块,生成的分析文件的name就是暴漏的全局变量,path就是分析文件的地址

我们将使用全局变量+分析文件来做映射,让代码中的import这样的语句,映射到我们分析的js文件中,而不是引入node_modules中

所以在webpack.config.js中加入

new webpack.DllReferencePlugin({
   manifest: path.resolve(__dirname, "dll/vendors.manifest.json")
})

这边我们传递参数的内容就是生成出来的映射文件,这个时候我们重新打包会发现,打包速度会少200-300毫秒(根据电脑性能不同)

流程梳理:

将不会变更的库进行单独打包,并且暴漏一个全局变量(核心)
打包之后使用DllPlugin进行分析,生成一个映射文件,条件是必须有一个全局变量
在打包main.js中,webpack会对引入的模块进行查询,而common.js使用了DllReferencePlugin这个插件,会在对应的映射文件中查询如果对应的单独库js文件存在引入的模块,那么main.js将直接把单独库js文件中的对应的内容为自己所用,就不会在去node_modules中去寻找模块,通过这一系列,将减少打包时间

那么在大型项目中,需要拆分很多很多的dll.js和manifest.json,那么如何做到“智能的”让webpack使用DllReferencePlugin和add-asset-html-webpack-plugin呢,在看这个问题之前,我们来尝试一下传统方法

不智能的做法:

entry: {
        vendors: ["lodash"],
        react: ["react", "react-dom"]
    },

我们假设要生成2个分析文件

那么我们在webpack.config.js需要这样做

plugins: [new HtmlWebpackPlugin({
        filename: 'index.html'
    }), new AddAssetsHtmlPlugin({
        filepath: path.resolve(__dirname, "dll/vendors.dll.js")
    }),new AddAssetsHtmlPlugin({
        filepath: path.resolve(__dirname, "dll/react.dll.js")
    }), new webpack.DllReferencePlugin({
        manifest: path.resolve(__dirname, "dll/vendors.manifest.json")
    }),new webpack.DllReferencePlugin({
        manifest: path.resolve(__dirname, "dll/react.manifest.json")
    })]

居然要配置2遍插件

每个被分析的js都要暴漏一个全局变量,这个之前也说过,需要把每一个文件添加到html中,有了全局变量之后,才能让webpack去用json去查询模块

那么我们假象一下,一个项目如果需要规定上10个,上100个的入口模块,那么就要配置200次(100次添加js到html,100次查询json依赖文件)

这显然是不可取的,所以我们可以用相对智能的方式去做哟,需要用到node一点东西哟;

智能的做法:

思路1: 在运行之前,肯定是解析过了dll.js的,所以运行webpack.config.js之前,项目中应该会有100个.dll.js , 100个.manifest.json
我们抽象一下webpack.config.js中的plugins,定义一个数组

const fs = require("fs");
const plugins = []; // 抽象plugins数组
// 读取dll目录下的数据
let readFileList = fs.readdirSync(path.resolve(__dirname, "dll"));
console.log(readFileList);

// 打印结果:
[ 'react.dll.js',
  'react.manifest.json',
  'vendors.dll.js',
  'vendors.manifest.json' ]

循环这个数组

const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
    if(/.*\.dll.js/.test(file)) {
        plugins.push(new AddAssetHtmlWebpackPlugin({
            filepath: path.resolve(__dirname, '../dll', file)
        }) )
    }
    if(/.*\.manifest.json/.test(file)) {
        new webpack.DllReferencePlugin({
            manifest: path.resolve(__dirname, "dll", file)
        }) )
    }
})

判断循环的当前file名称是不是dll.js,如果是dll.js,就在数组中添加对应的AddAssetHtmlWebpackPlugin 插件,反之就DllReferencePlugin 添加这个插件

大功告成,所以每当有一个新的入口时候,只需要

entry: {
        vendors: ["lodash"],
        react: ["react", "react-dom"]
    },

在webpack.dll.js随意配置即可, 但是每次打包之前,必须要先生成dll.js和manifest.json文件

控制包文件大小

在日常写代码的时候,一些引入的模块,没有用到,那么可以借助treeshaking和codesplit一些代码分割的插件,来控制包大小

多线程打包

webpack基于node的,是单线程进行打包,我们可以在单页应用使用thread-loader来对单页应用进行多线程进行打包,多页应用则使用parallel-webpack进行打包,还有一个是happypack

合理使用soucemap

合理选用适合项目的soucemap模式,过细的soucemap会增大打包压力,所以根据调整soucemap也可以调优

结合stats

一些第三方的网站可以分析webpack的打包文件,分析出打包的文件,哪个文件大,哪个文件慢,从而达到目的性的调优

开发环境内存编译

在开发环境时,使用的webpack-dev-server就是通过内存存储打包后的文件,所以内存的读写一定比硬盘读写快,所以使用内存编译的方式,打包速度也会加快

开发环境无用插件剔除

开发环境下,不需要过多的插件进行压缩等等,所以剔除无用插件也可以达到调优目的;

这段时间一直没有更新文章,实在是有点忙,所以以后基本都会每周周更,文章其实就是自己学习的一个成果展示,并不是什么科普文章哟

今天我们来聊聊reduce,之前呀我们在写业务的时候,循环非常喜欢forEach, for in,map等循环api,但是reduce和这些有什么区别呢,其实reduce循环和刚刚提到这几个循环作用都是一样,但是适用于场景也不一样,它能干其他循环干的了的,也能干其他循环干不了的,还有一个很好的优点就是:很装逼~~, 而且reduce毕竟是高阶函数,在公司中合适的用到它,也能适当提示代码质量。

我喜欢用例子去解决一个知识点,所以我们从例子出发

let arr = [1, 2, 3, 4, 5];
let result1 = arr.reduce((prev, current, index, arr) => {
  console.log(prev, current, index, arr);
  return prev + current;
});
console.log("最后结果:" + result1);
这是一个数组值求和的例子,result1就是结果

微信截图_20191110204719.png

下来描述一下reduce的用法: reduce为数组提供遍历方法,每一次遍历都会执行一次回调函数;

prev: 上一次回调函数返回的值
current: 当前遍历的数组元素
index: 循环当前索引
arr: 调用reduce函数的数组

reduce方法除了回调函数的四个参数,还有一个参数叫做initialValue,字面意思: 初始化值,那么设置这个初始化值有什么用呢?

上面的那个图片例子中的第三个参数分别是1,2,3,4,只循环了4次,执行了4次回调函数,这并不符合常理,但是如果我们把initialValue设置成0,会如何呢?

微信截图_20191110211155.png

设置了初始值,我们就从0开始了,可见如果不设置初始值,reduce会自动跳过第一个直接进行第二次循环;

那么数组为空,我们看会造成什么?

微信截图_20191110212059.png

浏览器会提示我们当数组为空,如果不设置初始值会报错,所以我们在循环的时候,为了代码严谨和安全:

务必要设置initialValue


reduce:简单场景

  1. 数组求和

     let arr = [1,2,3,4];
     let sum = arr.ruduce((pre,cur) => pre*cur);
    

reduce: 高级场景

  1. 统计数组中的元素出现的次数

     let arr = ["昊儿", "王航", "范冰冰", "王航"];
     let result1 = arr.reduce((prev, current, index, arr) => {
       if (current in prev) {
        // 如果存在,就加一
        prev[current]++;
       } else {
        prev[current] = 1;
       }
       return prev;
     }, {});
    
     console.log(result1);
    

结果:

微信截图_20191110214157.png

  1. 数组去重复

     let arr = ["昊儿", "王航", "范冰冰", "王航"];
     let result1 = arr.reduce((prev, current, index, arr) => {
       if (!prev.includes(current)) {
     // 如果不存在
     prev.push(current);
       }
       return prev;
     }, []);
     console.log(result1);
    

微信截图_20191110214645.png

  1. 二维数组转换为一维数组

     let arr = ["昊儿", "王航", "范冰冰", "王航", [1, 2, 3]];
     let result1 = arr.reduce((prev, current, index, arr) => {
       return prev.concat(current);
     }, []);
     console.log(result1);
    

微信截图_20191110215557.png

  1. 多维数组转换为一维数组

     let arr = ["昊儿", "王航", "范冰冰", "王航", [1, 2, 3, [2, 3, 4]]];
    
     let test = function(cur) {
       return cur.reduce((prev, current, index, arr) => {
     return prev.concat(Array.isArray(current) ? test(current) : current);
       }, []);
     };
     console.log(test(arr));
    

看完这些例子,我会说:真香...哈哈哈
timg.jpg

今天是周天,本来周五晚上就想写这一篇总结,但是每周分享的题总结还没结束,只能结束之后新写这一个文章。
其实,昨天在逛github的时候给我推荐了几个不错的仓库博客,类似于《一周攻克一个前端难点》,我也会有时间去学习,并且把学习到的干货分享到博客上来。

关于这边《ECMAScript 6 入门》,我没有买这个书而是看阮一峰老师的电子开源书籍,周五看了第一章关于let和const,然后加深了我对于作用域,死区的理解,然后在书中也有非常多的demo例子,这些例子在笔试中还是高频率的出现的,一些前端开发者在面试(入行不深的程序员)选择google出来的答案,背下来....

但是这非常不实用,因为你不了解这种题如何做,在工作中如何避免bug。
在新专栏的开始之前,我想多说几句,和大家分享一下我的学习方法,我也是入行不深,仅仅只有半年多而已,但是写代码即将2年多,
我学习前端的方法也非常简单,项目实战(有利于能接触到更多的场景)-》刷题(渣渣程序员只能从语言角度上刷题,涉及不到算法数据结构的题)-》笔记和博客(每天总结笔记,每一段时间总结博客)要养成良好的习惯,不管多忙,在地铁和公交车上一定要把笔记写完,随手复习,工作中可以翻阅;

正题开始............

es6是前端开发者必须掌握的一个知识点,不管是培训结构还是某些大学的前端课程,传授的es6都相对非常浅,没有去认真的一篇一篇去消化巩固,作者也是这样的,所以现在开始我们将一起一步一步精读阮一峰老师的这本非常棒的书籍,把es6完全消化,能够让你在工作中面试中杀出一条血路。

u=3907368649,2159858588&fm=26&gp=0.jpg

首先我们来快速了解一下let;

Let

我们尝试写这样一段代码:


{
    let a = 1;
    var b = 10;
}

console.log(a); // Uncaught ReferenceError: a is not defined
console.log(b); // 10

这个例子说明了let只会在声明它的代码块中生效,而var则相反

我们一起再看一个例子:

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[1](); // 10

这个循环中的i是一个全局变量,每一次循环全局的变量i都会发生改变,所以尽管它循环100次,数组中console.log中的i永远指向全局变量即100,最后一次循环的赋值;

但是如果把上面的var换成let就不一样,let只在自己的块中存在,所以每次循环都会重新let一次i变量,所以每次输出的console.log中的i指的是当前循环到的i

但是你肯定会说:

既然是重新let一次i,那么每次它应该都是0呀,为什么还能记忆上次的i值呢?这不科学...

那是因为js引擎会记住上一次i的值,为下次循环做运算,这一部分是js帮助开发者做好的。

js的for循环还有一个特别之处:设置循环变量的是一个父作用域,而循环体是一个子作用域

for (let i = 0; i < 10; i++) {
    let i = '123';
    console.log(i) // 123
}

正确运行,并且成功赋值打印新的值;

Let - 关于变量提升

js中的var是存在变量提升的,什么是变量提升呢?

console.log(i); // undefined
var i = "I love you"

在变量未被附值的时候,js解析代码会变成下面这个样子:

var i;
console.log(i);
i = "I love you"

但是let并非这样,它没有所谓的变量提升,只要在声明前使用,则会报错;


console.log(a); // Uncaught ReferenceError: a is not defined
let a = "yes";

所以var会让变量提前声明,而let不会提前声明,如果声明前用到了它,就会报错Uncaught ReferenceError;

Let - 暂时性死区

文章的开头我们降到了死区这个概念,之前呀,我也不太懂这个死区,以为是一个很难的概念,没想到和let有关系,非常简单;

var a = "123";
{
    a = "125";
    let a = "456";
}

ES6明确规定,let和const声明了一个变量,那么会和当前作用域块进行绑定,如果在声明前使用到了变量,那么就会报错;
这在代码中我们称之为 "暂时性死区"

那么如果我们不向变量赋值会不会报错呢? 事实证明typeof也不是一个正确的操作


typeof x; // Uncaught ReferenceError
let x;

如果我们检测一个不存在的变量

typeof notfound; // undefined

所以说这也意味着我们编写代码的时候要在声明之后使用它;

在阮一峰老师的文章中也提到了不常见的死区的案例:

function test(x = y, y = 2){

}

test();

x的默认值是还未声明的y,所以会报错;

var x = x;  // 不报错
let x = x; // 报错

其实上面这个代码,之前就解释过了,var出来的变量即已对变量进行初始化,所以等号右边的x就是undefined
而let出来的变量没有变量提升,然后又在变量x声明之前使用到了x,这显然是错误的,因为之前使用的就是死区;

因此死区的触发有很多,检测,赋值,使用, 等等等;

Let - 不允许同时声明在同作用域中

{
    var a = 1;
    let a = 1;
}

或者

{
    let a = 1;
    let a = 1;
}

又或者

funtion test(arg){
    let arg = "123";
}

这些都会报错,但是在不同作用域中是可以的

funtion test1(arg){
    {
        let arg = "12";
    }
}

it is ok!!

关于块级作用域

面试中经常会被问到作用域, 全局作用域,函数作用域,在es6中新的概念就是块级作用域,之前面试过其他的前端开发者谈到块级作用域解决了什么问题,和let,const的关系,他们也是没有一个很准确的答案;

这边用阮一峰老师的2个demo例子:


var temp = new Date();
function test(){
    console.log(temp);
    if(true){
        var temp = "hello"
    }
}

test(); // undefined

上面这个例子之所以输入未定义,是var变量会造成变量提升,即var temp; 内层的temp替换掉了new Date的那个变量,提升到了函数作用域的顶级;

还有一种场景就是,我们在用循环的时候,使用var,导致循环结束var的变量依然存在,造成变量泄露;

事实上,let和const就是块级作用域,因为我们知道在前面的内容讲到let只能在当前作用域下使用,和其他的作用域不相干,这就是形成了块级作用域;

let a = 1;
if(true){
    let a = "1111"
}
console.log(a); // 1

ES6允许我们块级作用域嵌套

{{{{{
    console.log(1)
}}}}}

即内层的可以读取到外层同名变量,外层不能读取内层的同名变量;

块级作用域和函数声明

ES5规定,块级作用域里面不能写函数声明语句,但是浏览器为了兼容以前的旧代码,没有遵守这个规定;
但是ES6明确规定,块级作用域下可以写函数声明,但是函数声明类似于let,在作用域之外不能使用;

看一下下面这个demo:

function f() { console.log('I am outside!'); }
(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();
}());

如果是在ES5中写这个函数,那么最终会输出 "I am inside!" ,因为会变量提升提前到函数顶部,所以会输出inside
但是如果在ES6中声明,理论来讲会输出outside,因为es6规定了函数声明相当于let,只在自己作用域生效,不会影响其他作用域,也没有变量提升,但是如果在ES6中就会输出undefined;

为什么呢?说好的规定呢?说好的let呢?

微信截图_20191027154441.png

原来浏览器又有自己实现的一套方法,输出undefined的原因还是被提升了

var f = undefined;

所以我们在作用域中写函数,不要用函数声明,要使用函数表达式,这样最稳妥...

const

ES6新增的常量,我喜欢叫它常量,因为它不可改变;

const a = 1;
a = 12; // TypeError: Assignment to constant variable.

且不能这样写

const a;

不能像var和let一样先定义再赋值;

const和let一样,只能在声明的块级作用域有效
const和let一样,也有死区
const和let一样,也不能重复声明

const的本质其实并不是const的变量值不能被改变,而是指针(地址)是固定的;

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
---------------《ECMAScript 6 入门》

所以我们可以改变const的数据结构

const props = {};
props.name = "1";
props.age = "12";
props = "12"; // 报错



结束咯,喜欢这篇总结,就收藏起来吧~~
原文阮一峰老师地址: http://es6.ruanyifeng.com/#docs/let