生成npm包

生成npm包

将写好的插件生成npm包的方法也简单,分为以下几步:

1、登录npm官网,注册一个npm账号

前往npm官网注册一个npm账号: 导航到 http://npmjs.com 并单击注册。

2、制作一个npm的包

将写好的插件生成一个简单的npm包:

创建一个空文件夹,名称即为你的npm包的名称,注意名称不要与现有的npm包重复;
    mkdir npmDemo
切到该文件夹下,输入‘npm init'命令生成一个package.json文件,内部信息自定义;
将写好的插件js文件拷到该文件夹下,重命名为index.js;
在根目录下创建README.md文件,添加关于包的介绍信息(非必需);

3、将包上传或更新到 npm

发布创建好的包:

1. 若第一次发布包,执行‘ npm adduser'命令,输入前面注册好的npm账号,密码和邮箱;
2. 若不是第一次发布包,执行‘ npm login'命令进行登录,同样输入npm账号,密码和邮箱;
3.  登录成功以后,在刚才的文件夹下,执行‘npm publish'命令,即可发布该npm包;
4.  若要更新之前的包,只需要更改版本号,再执行‘npm publish'命令即可;
至此一个简单的vue插件就生成了一个npm包了,以后使用就不需要加载js文件,可以直接通过npm的方式引入了,其它的小伙伴也可以一起愉快的玩耍了

windows indexedDB

创建数据库

var request = window.indexedDB.open("huapisong", 1);
request.onerror = function(event) {
    console.log('数据库打开报错');
}
var db;
request.onsuccess = function(event) {
    db = request.result;
    console.log('数据库打开成功');
    //   插入数据
    add({name: 'name3', id: '3'})

}
request.onupgradeneeded = function(event) {
    db = event.target.result;
    console.log("数据库升级成功");
    //     创建表
    var objectStore;
    if (!db.objectStoreNames.contains('book')) {
        objectStore = db.createObjectStore('book', {
            keyPath: 'id'
        });
    //    建索引
    objectStore.createIndex('name', 'name', { unique: true });
    }
    var objectStore2 = db.createObjectStore(
    'book2',
    { autoIncrement: true }
    );
}
// 插入数据
function add(book) {
    var request = db.transaction(['book'], 'readwrite') //新建事务,readwrite, readonly(默认), versionchange
        .objectStore('book') //拿到IDBObjectStore 对象
        .add({  // 插入记录
        id: book.id,
        name: book.name
    });
    request.onsuccess = function(event) {
        console.log('数据写入成功');
    }
    request.onerror = function(event) {
        console.log('数据写入失败');
    }
    request.onabort = function(event) {
        console.log('事务回滚');
    }
}
// 关闭
// db.close();
// db 删除
// window.indexedDB.deleteDatabase('huapisong');

webpack vue-cli 启动编译慢

场景:webpack vue-cli 启动编译慢

# 耗时时间
npm install speed-measure-webpack-plugin --save-dev
# 配置 vue.config.js
module.exports = {
    chainWebpack: (config) => {
        config.plugin('xcTime').use(SpeedMeasurePlugin);
    })
};
# 耗时详情
    DONE  Compiled successfully in 132420ms

    SMP  ?
    General output time took 2 mins, 12.39 secs   

    SMP  ?  Loaders
    @dcloudio/vue-cli-plugin-uni, and
    css-loader, and
    @dcloudio/vue-cli-plugin-uni, and
    @dcloudio/vue-cli-plugin-uni, and
    postcss-loader, and
    @dcloudio/vue-cli-plugin-uni, and
    @dcloudio/vue-cli-plugin-uni, and
    @dcloudio/vue-cli-plugin-uni, and
    unplugin took 1 min, 43.082 secs
    module count = 266
    modules with no loaders took 1 min, 34.62 secs
    module count = 871
    @dcloudio/vue-cli-plugin-uni, and
    @dcloudio/vue-cli-plugin-uni, and
    @dcloudio/vue-cli-plugin-uni, and
    eslint-loader, and
    unplugin took 1 min, 34.5 secs
    module count = 272
    babel-loader, and
    @dcloudio/vue-cli-plugin-uni, and
    @dcloudio/vue-cli-plugin-uni, and
    eslint-loader, and
    unplugin took 53.99 secs
    module count = 195
    url-loader took 36.37 secs
    module count = 77
    babel-loader, and
    ts-loader, and
    @dcloudio/vue-cli-plugin-uni, and
    eslint-loader, and
    unplugin took 32.92 secs
    module count = 15
    babel-loader, and
    @dcloudio/vue-cli-plugin-uni took 14.12 secs
    module count = 51
    @dcloudio/vue-cli-plugin-uni, and
    @dcloudio/vue-cli-plugin-uni, and
    css-loader, and
    @dcloudio/vue-cli-plugin-uni, and
    postcss-loader, and
    @dcloudio/vue-cli-plugin-uni, and
    @dcloudio/vue-cli-plugin-uni, and
    @dcloudio/vue-cli-plugin-uni, and
    @dcloudio/vue-cli-plugin-uni took 10.3 secs
    module count = 12
    @dcloudio/vue-cli-plugin-uni, and
    @dcloudio/vue-cli-plugin-uni took 6.16 secs

thread-loader的使用

# 多核编译
npm install thread-loader --save-dev
# 配置 vue.config.js

    const ThreadLoader = require('thread-loader');
    const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');

    const os = require('os');
    // cpu核数
    const threads = os.cpus().length;

    const options = {
    // 产生的 worker 的数量,默认是 (cpu 核心数 - 1),或者,
    // 在 require('os').cpus() 是 undefined 时回退至 1
    workers: threads - 1,

    // 一个 worker 进程中并行执行工作的数量
    // 默认为 20
    workerParallelJobs: 50,

    // 额外的 node.js 参数
    workerNodeArgs: ['--max-old-space-size=1024'],

    // 允许重新生成一个僵死的 work 池
    // 这个过程会降低整体编译速度
    // 并且开发环境应该设置为 false
    poolRespawn: false,

    // 闲置时定时删除 worker 进程
    // 默认为 500(ms)
    // 可以设置为无穷大,这样在监视模式(--watch)下可以保持 worker 持续存在
    poolTimeout: 2000,

    // 池分配给 worker 的工作数量
    // 默认为 200
    // 降低这个数值会降低总体的效率,但是会提升工作分布更均一
    poolParallelJobs: 50,

    // 池的名称
    // 可以修改名称来创建其余选项都一样的池
    name: 'my-pool',
    };
    ThreadLoader.warmup(options, [
    // 加载模块
    // 可以是任意模块,例如
    'babel-loader',
    'ts-loader',
    'css-loader',
    'eslint-loader',
    'url-loader',
    'postcss-loader',
    ]);
    module.exports = {
        configureWebpack: {
            devtool: process.env.DODE_ENV === 'production' ? 'source-map' : 'eval-cheap-source-map',
        },
        chainWebpack: (config) => {
        [
            // 'mjs',
            // 'vue', // error: compiler.parseComponent is not a function
            // 'images', // error: this.emitFile is not a function
            'svg',
            'media',
            'fonts',
            // 'pug',
            // 'css',
            // 'postcss',
            // 'scss',
            // 'sass',
            // 'less',
            // 'stylus',
            // 'js', // error: Cannot read property 'options' of undefined
            'eslint',
            'ts', // error: Cannot read property 'options' of undefined
            'tsx',
            // 'jsx',
            ].forEach((rule) => {
            // 多核编译
            const originUse = config.module.rule(rule).toConfig().use;
            if (originUse) {
                // 2、添加 loader
                const newLoader = [
                { loader: 'thread-loader', options },
                { loader: 'cache-loader' },
                // ...originUse,
                ...originUse.map((item) => {
                    if (item.loader.includes('ts-loader')) {
                    item.options.happyPackMode = true;
                    item.options.transpileOnly = true;
                    return item;
                    }
                    return item;
                }),
                ];
                // originUse.unshift(newLoader);
                // 3、清空原来的 loader
                config.module.rule(rule).uses.clear();
                // 4、重新设置新的 loader
                config.module.rule(rule).merge({ use: newLoader });
            }
            });
            config.plugin('xcTime').use(SpeedMeasurePlugin);

            // production only
            config.when(process.env.NODE_ENV === 'production', (config_) => {
            // terser-webpack-plugin
            config_.optimization.minimizer('terser').tap((args) => {
                args[0].parallel = threads > 1; // 多线程压缩js
                args[0].cache = true;
                args[0].terserOptions.nameCache = 'terser-cache';
                args[0].terserOptions.compress.warnings = true;
                args[0].terserOptions.compress.drop_debugger = true;
                args[0].terserOptions.compress.drop_console = true;
                return args;
            });
            });
        })
    };

优化后耗时

# 提升30s, 只做了 'eslint-loader', 'url-loader' 多线程编译优化

DONE  Compiled successfully in 102831ms  

#  .vue .js/ts 报错大概和其它冲突(没有搞定),这部分优化后速度应该大幅度提升
'vue', // error: compiler.parseComponent is not a function
'js', // error: Cannot read property 'options' of undefined
'ts', // error: Cannot read property 'options' of undefined

cache 优化,首次编译启动慢一点,后面差量编译

# 耗时
DONE  Compiled successfully in 4011ms    


# 详细
1. npm install hard-source-webpack-plugin
2. vue.config.js 

 const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

 module.exports = {
   configureWebpack: (config) => {
     config.devtool =
       process.env.DODE_ENV === 'production'
         ? 'cheap-module-source-map'
         : 'cheap-module-eval-source-map';
     config.module.noParse =
       /^(vu|vue-router|vuex|vuex-router-sync|lodash|echarts|axios|element-ui)$/;
   },
   chainWebpack: (config) => {
     config.when(process.env.NODE_ENV !== 'production', (config_) => {
       config_.plugin('hard-source').use(
         new HardSourceWebpackPlugin({
           root: process.cwd(),
           directories: [],
           environmentHash: {
             root: process.cwd(),
             directories: [],
             files: ['package.json', 'yarn.lock', 'package-lock.json'],
           },
         }),
       );
       config_.plugin('hard-source-exclude').use(
         new HardSourceWebpackPlugin.ExcludeModulePlugin([
           {
             test: /mini-css-extract-plugin[\\/]dist[\\/]loader/,
           },
         ]),
       );
     });
   },
 };

terser-webpack-plugin的使用:删除注释和console

场景:扫描漏洞的时候,nodel_modules中有的插件的版本注释会视为漏洞,需要删除这些注释

注意:webpack4中用到terser-webpack-plugin压缩插件,不能使用最新的版本,而是<terser-webpack-plugin@4.x>版本;webpack5对应的使用<terser-webpack-plugin@5.x>版本,否则会报错误:Error: TypeError: Cannot read property ‘javascript‘ of undefined。如下图所示:

image

terser-webpack-plugin的使用

①安装插件(我用的webpack4)

npm install webpack@4.46.0

②vue.config.js中的配置

const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
configureWebpack: {
    optimization: {
    minimize: process.env.NODE_ENV === 'production'?true:false,//为了不影响dev时的构建速度
    minimizer: [
        new TerserPlugin({
        parallel: true,//使用多进程并发运行以提高构建速度 Boolean|Number 默认值: true  
        terserOptions: {
            compress: {
            drop_console: true,//移除所有console相关代码;
            drop_debugger: true,//移除自动断点功能;
            pure_funcs: ["console.log", "console.error"],//配置移除指定的指令,如console.log,alert等
            },
            format: {
            comments: false,//删除注释
            },
        },
        extractComments: false,//是否将注释剥离到单独的文件中
        })
    ]
    }
},

}

node mysql2

mysql2 驱动程序连接 MySQL

要连接 Node.js 和 MySQL,您需要使用 Node.js 的 MySQL 驱动程序。Node.js 的 MySQL 驱动程序非常流行,有几个选项可供选择,例如 mysql,mysql2,和 Sequelize。

以下是使用 mysql2 驱动程序连接 MySQL 的示例代码:

安装 mysql2 驱动程序

npm install mysql2
创建连接
const mysql = require(‘mysql2’);

const connection = mysql.createConnection({
host: ‘localhost’,
user: ‘root’,
password: ‘yourpassword’,
database: ‘yourdatabase’
});

connection.connect(function(err) {
if (err) throw err;
console.log(“Connected!”);
});
在上面的示例中,我们使用 createConnection() 方法创建一个连接对象,并传递数据库的连接参数。然后我们使用 connect() 方法连接到数据库。如果连接成功,我们会在控制台中看到 Connected! 的消息。

执行 SQL 查询

connection.query(‘SELECT * FROM users’, function(err, results) {
if (err) throw err;
console.log(results);
});
在上面的示例中,我们使用 query() 方法执行 SQL 查询。我们传递查询字符串作为第一个参数,回调函数作为第二个参数。如果查询成功,我们会在控制台中看到结果。

关闭连接

connection.end(function(err) {
if (err) throw err;
console.log(“Connection closed.”);
});
在上面的示例中,我们使用 end() 方法关闭连接。如果关闭成功,我们会在控制台中看到 Connection closed. 的消息。

请注意,以上代码仅用于示例目的。在实际应用中,您应该使用连接池来管理连接,而不是每次都创建新的连接。这将提高应用程序的性能和可伸缩性。

mysql2 驱动程序创建连接池

连接池是一种重用数据库连接而不是创建新连接的技术,它通常用于优化数据库性能。在 Node.js 中,您可以使用 mysql 或 mysql2 驱动程序创建连接池。

以下是使用 mysql2 驱动程序创建连接池的示例代码:

安装mysql2 驱动程序

npm install mysql2

创建连接池

const mysql = require(‘mysql2’);

const pool = mysql.createPool({
connectionLimit: 10,
host: ‘localhost’,
user: ‘root’,
password: ‘yourpassword’,
database: ‘yourdatabase’
});
在上面的示例中,我们使用 createPool() 方法创建连接池对象,并传递连接参数。我们还设置了 connectionLimit 属性,这表示连接池中可用连接的最大数量。

执行SQL 查询

pool.query(‘SELECT * FROM users’, function(err, results) {
if (err) throw err;
console.log(results);
});
在上面的示例中,我们使用 query() 方法执行 SQL 查询。我们传递查询字符串作为第一个参数,回调函数作为第二个参数。如果查询成功,我们会在控制台中看到结果。

关闭连接池

pool.end(function(err) {
if (err) throw err;
console.log(“Connection pool closed.”);
});
在上面的示例中,我们使用 end() 方法关闭连接池。如果关闭成功,我们会在控制台中看到 Connection pool closed. 的消息。

请注意,使用连接池的主要优点是可以重用连接,从而减少创建新连接的开销。此外,连接池还可以帮助您管理连接,例如在连接空闲时关闭连接,以避免占用过多资源。

前端学习

前端学习

一、HTML/CSS3 部分

前端概念总览、vscode开发工具使用、markdown文档工具使用、浏览器平台环境、标签基础、DTD与META

语义化、选择器、权重与优先级、匹配规则、浮动与清除、BFC、类名组合规则、SEO前端技巧、定位、叠加技巧、

伪类、伪元素、盒子模型、代码规范、性能优化、渲染原理、CSS Sprites 、 iconfont字体图标、布局思路、布局定式

css3动画、 css3选择器、 css3过渡、flex布局、背景渐变、css3实用动效、3D模块、css3变量、em/rem/vw

二、JavaScript 部分

JavaScript概念、基础认知、发展历史、应用范围、优缺点。

ECMAScript

语言概览、语句语法、数据类型、运算符、隐式转换、运算规则、控制流程、作用域、作用域链、作用域解析、js解释引擎、字符串属性方法、for of/for in

数组属性方法、对象、广义对象概念、对象特性、存储机制、深拷贝、对象实际应用、工厂模式、构造函数、原型、原型链、原型指向、封装、

多种继承、包装对象、this、this指向、js数据处理、垃圾GC、闭包原理与应用、debug方法论、浏览器控制台工具应用、面向对象开发、函数式开发、

纯函数、递归函数、回调函数、组合函数、缓存函数、柯里化函数、偏函数、函数防抖与节流、高级函数、IIFE函数、模块化、

数学对象、高级数组方法(reduce、filter、map、every、some、forEach、find、findIndex、include)等、

高级对象方法(entires、defineProperty、freeze、fromEntries、assign、keys、create、hasOwnProperty)等

this通过bind、call、apply指向偏移、同步与异步、闭包模块化、数据形变处理、性能处理

DOM/BOM

DOM对象与方法、节点与遍历树、DOM选择器、DOM节点增删改查、DOM样式操作、DOM位置获取与偏移、DOM对象与标签区别、DOM虚拟化、DOM事件、

Event对象、定时器与监听器、事件委托、事件代理、事件分流、事件冒泡、默认行为、事件捕获、防抖、节流、滚动、键盘行为、事件降频、行为锁机制、

拖拽模组、碰撞检测、距离判断、三角函数、方向判断、运动框架、弹性运动、重力加速度、DOM树、CSSOM树、渲染解析、加载、回流、重绘、浏览器线程与阻塞。

BOM深入、window对象、navigator、history、screen、location、spa概念、本地传参、日期对象使用、日期格式化封装、随机封装、DOM分片容器

JS业务应用扩展

JSON对象、数据解析、JSON方法、模板渲染、缓存懒加载、正则表达式RegExp、元字符、修饰符、正向预查、贪婪模式、replace方法、分组与不分组、test、match与exec

正则库封装、表单校验、图片预存懒加载、自定义封装滚动组件、多重轮播(循环、视差、旋转木马)、选项卡切换、楼梯导航、吸顶导航、拖拽导航 、

JavaScript编码风格指南、console应用技巧

ECMAScript5 — ECMAScript9扩展

版本解读、应用环境、let、const、解构赋值、箭头函数、对象解构、reset参数解构、class、set、get、extends、super、static、private实现、symbol、promise、proxy代理拦截、async/await

对象新增API、数组新增API、字符串新增API、原型属性、对象修饰、代理拦截、链式询问、空值合并运算符、泡泡龙面向对象游戏开发

三、HTML5 / 移动端开发 部分

HTML5标签与API

HTML5概念、定义、新增特性、DTD对比、新增H5标签、新增语义化H5标签、H5表单

H5拖拽事件 video、audio、fullScreen、FileReader文件流、Blob对象、sessionStorage、localStorage、

网络状态、页面生命周期、网页状态监听、地理信息与定位、canvas画布、worker多线程、Notifications桌面通知

播放器开发 canvas交互特效开发

移动端适配/事件

移动端适配概念、尺寸与分辨率原理、PPI与DPI计算、DIP与DPR设备像素概念、视口viewport概念、布局视口、视觉视口、理想视口

视口适配方案(Meta viewport)缩放设置 、screen窗口大小API、媒体查询@media、rem适配、vw vh适配

移动端适配方案、flexible rem方案、 vh vw方案 、复合方案、高清适配方案、移动端字体缩放解决、1px边框问题解决、半像素线

图片高清适配、image-set、srcset、js拼接URL技巧、IPhonex刘海屏适配、安全区域、fit与env

Meta权限管理、兼容优化、IOS头部底部闪屏回退输入框问题、输入法空格问题、默认首字母大写问题、滑动卡顿问题等

终端样式美化与访问判断

Touch事件组封装、click延迟处理、FastClick使用、禁用缩放设置理想视口

常用类库/工具

jquery: 元素选择、增删改差、事件处理、位置获取、动画过渡、高级方法、特效应用

zepto:移动端的jquery、DOM操作、移动端事件、click延迟处理、touch事件组

基础UI库: layerUI 、easyUI、bootStrap、UI模块化、UI库使用技巧、文档解读、案例调试、表单系统

iScroll、touchjs等移动端事件库使用

swiper:移动端网站触摸滑动、轮播图插件、模块化应用、源码解读

art-template: 模板引擎 模板语法、渲染方法、原生模板引擎实现

CSS预处理:sass、less使用 stylus深入学习 、预处理语法、css编程、 Minxin 、预处理函数、继承、嵌套

vscode高级插件与配置 liveserver sass

模板化应用 、预处理应用

date-picker移动端原生组件开发

需求分析、业务规则、技术栈选型、文件输出规则、技术分析

MVC模型概念与设计、业务逻辑分类

view层适配方案 原型布局搭建 模板化生成

工具函数开发、面向对象式业务设计 功能类日期对象开发

交互业务封装 模块对接 优化设计 面向对象高级程序设计

四、Nodejs与工程化开发 部分

Node原生基础

Node基础概念、NPM命令、V8引擎概念、JavaScript模块化 CMD AMD UMD、EventLoop事件循环、同步异步、 阻塞非阻塞 、任务队列、宏任务、微任务、

原生Node模块、核心模块与require、module、触发器events与path模块、fs模块、Buffer对象、URL模块、HTTP模块 、stream流

Node原生封装HTTP与文件服务器

Node框架

express框架使用、中间件、核心对象、路由配置、模板引擎、数据脚本、Router核心、CORS配置、错误处理、校验封装、PM2管理、nodemoon管理

MORGAN日志处理、查询正则捕获、中间件开发、bodyParse等第三方中间件使用 、文件流上传下载、文件流切分

webpack工程化构建

工程化概念、工程化工具介绍、webpack基础知识、webpack配置与运行、plugin插件、loader使用、入口出口配置、模块modules、依赖处理、dev Server

target 、文件切分、build打包、热处理、package管理、性能优化、测试处理 、第三方插件使用、babel等

前后端数据交互

HTTP通信交互原理、请求响应、MIME、URI地址、报文解析、端口、报文流、测试监听抓包、TCP/IP握手机制、网络业务模型

代理、缓存、网关、隧道等概念、method与请求头分类

基础XMLHTTPRequest对象方法属性、方法、ajax异步交互 、同源策略原理

原生js封装ajax交互、 jquery的ajax封装交互、jsonp跨域处理、cors跨域处理、携带cookie与跨域处理、代理服务器跨域处理、

cookie验证与通信 、axios库的使用 代理、封装、拦截、合并、二次封装

通信加密策略、数据对称加密、数据非对称加密、RSA加密实践、MD5加密实践

登录注册与鉴权机制、Authenticity认证、Token认证、JWT认证、session状态管理、 oauth认证

RESTful接口设计

RESTful设计原则、状态码设计与规范、服务器响应规则与格式、前后端接口协议制定、传统RESTful改进

API设计规范、 资源定位、JSON模式、合约说明、资源标识符、缓存友好API、HTTP头管理、文档定义、松耦合原则、一致性、重用、稳定性原则

数据库存储

MongoDB基础概念、NoSQL与SQL、CAP定理、RDBMS、MongoDB语法、指令、监控与GUI、MongoDb驱动、Mongoose应用

数据库创建、集合增删改查、文档增删改查、数据库查询、高级查询操作、通道查询、多条件查询、反向筛选、索引处理、aggregate聚合管道、

validation验证、population联表、middleware中间件处理、查询ERROR类二次封装

博客后台项目实战

数据库设计、服务端路由搭建、接口设计与API管理、通信交互管道封装、注册登录、路由导航、前端路由管理、权限管理、用户管理、分类管理、标签管理

、文章管理、模块化功能封装、校验库封装、promise链式行为封装、

异步模块处理、SPA单页面应用初级实现、原生集中行为控制器、模板化+模块化+预处理+工程化实现前端页面

组件化封装

组件化基础概念、组件设计原则、组件封装、组件耦合与解耦、工程化+模板化+模块化实现原生组件化功能、

五、Vue框架部分

vue基础

vue开发准备、工具与插件、mvc mvp mvvm设计理念、原生js实现MVVM核心框架、vue设计模式、生命周期、命令与属性、组件化

数据驱动与双向绑定、虚拟DOM概念原理、模板语法、条件与列表、数组监测、计算属性、事件处理、修饰与表单、监听器、过滤器、

methods watch computed filter对比、组件化开发、动态组件、组件通信、高级组件通信、插槽、vue动画 、实例对象、set、get、nextTick、vue插件

vue工程化

Babel、webpack配置vue、vue单文件组件、vue-cli、工程平台处理、postCss、animate、core、socket、lodash工具、vue-cli插件、preset、静态资源处理、

环境变量与膜还是切换、工程化部署、工程构建

vue Router

vue路由概念、嵌套路由、路由视图、动态路由、路由匹配、编程式导航、history、hash模式管理、命名视图、重定向与别名、导航守卫、路由元信息、滚动与懒加载、导航故障处理

vuex

vuex核心概念、EventBus对比、状态管理模式、Store、state、getter、mutation、action、moudule

vuex辅助函数与API功能、vuex环境选择、vuex模块化处理规则与技巧、对象风格提交方式、模块重用等

vue项目实战

vue博客项目实战 PC + 移动端、 elementUI、vantUI、websocket聊天室、虚拟人偶、vueScroll、store、qs、nprogress、editor、

文章分类、词云效果、spa单页面应用、移动PC无缝切换、完整注册登录、vue加密鉴权、http封装管理、错误处理封装、vuex核心状态管理、本地存储状态更新、令牌处理、

文章富文本编辑、点赞、收藏、访问、评论、组件封装、组件复用、组件混合、高级组件开发、复合性组件通信、vue中处理原生DOM行为、vue常见错误处理、

重置vue数据响应、个人信息修改、头像上传、通知组件封装、postcss应用、项目打包优化

能够把这些知识点掌握,你就是一名优秀的前端工程师了。

关于Java 23种设计模式

java的设计模式大体上分为三大类

创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。
结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

设计模式遵循的原则有6个

1、开闭原则(Open Close Principle)

对扩展开放,对修改关闭。

2、里氏代换原则(Liskov Substitution Principle)

只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。

3、依赖倒转原则(Dependence Inversion Principle)

这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

使用多个隔离的借口来降低耦合度。

5、迪米特法则(最少知道原则)(Demeter Principle)

一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

原则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。

创建型模式

1. 工厂模式(Factory)

工厂模式:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。如:如何创建及如何向客户端提供。

常用的工厂模式是静态工厂,利用static方法,作为一种类似于常见的工具类Utils等辅助效果,一般情况下工厂类不需要实例化。

interface food{}

class A implements food{}
class B implements food{}
class C implements food{}

public class StaticFactory {

    private StaticFactory(){}

    public static food getA(){  return new A(); }
    public static food getB(){  return new B(); }
    public static food getC(){  return new C(); }
}

class Client{
    //客户端代码只需要将相应的参数传入即可得到对象
    //用户不需要了解工厂类内部的逻辑。
    public void get(String name){
        food x = null ;
        if ( name.equals("A")) {
            x = StaticFactory.getA();
        }else if ( name.equals("B")){
            x = StaticFactory.getB();
        }else {
            x = StaticFactory.getC();
        }
    }
}

2. 建造者模式(Builder)

建造模式:将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。建造模式使得产品内部表象可以独立的变化,客户不必知道产品内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。

在了解之前,先假设有一个问题,我们需要创建一个学生对象,属性有name,number,class,sex,age,school等属性,如果每一个属性都可以为空,也就是说我们可以只用一个name,也可以用一个school,name,或者一个class,number,或者其他任意的赋值来创建一个学生对象,这时该怎么构造?

难道我们写6个1个输入的构造函数,15个2个输入的构造函数…….吗?这个时候就需要用到Builder模式了。给个例子,大家肯定一看就懂:

public class Builder {

    static class Student{
        String name = null ;
        int number = -1 ;
        String sex = null ;
        int age = -1 ;
        String school = null ;

        //构建器,利用构建器作为参数来构建Student对象
        static class StudentBuilder{
            String name = null ;
            int number = -1 ;
            String sex = null ;
            int age = -1 ;
            String school = null ;
            public StudentBuilder setName(String name) {
                this.name = name;
                return  this ;
            }

            public StudentBuilder setNumber(int number) {
                this.number = number;
                return  this ;
            }

            public StudentBuilder setSex(String sex) {
                this.sex = sex;
                return  this ;
            }

            public StudentBuilder setAge(int age) {
                this.age = age;
                return  this ;
            }

            public StudentBuilder setSchool(String school) {
                this.school = school;
                return  this ;
            }
            public Student build() {
                return new Student(this);
            }
        }

        public Student(StudentBuilder builder){
            this.age = builder.age;
            this.name = builder.name;
            this.number = builder.number;
            this.school = builder.school ;
            this.sex = builder.sex ;
        }
    }

    public static void main( String[] args ){
        Student a = new Student.StudentBuilder().setAge(13).setName("LiHua").build();
        Student b = new Student.StudentBuilder().setSchool("sc").setSex("Male").setName("ZhangSan").build();
    }
}

3、工厂模式(Factory Method)

工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。

一个基础接口定义了功能,每个实现接口的子类就是产品,然后定义一个工厂接口,实现了工厂接口的就是工厂,这时候,接口编程的优点就出现了,我们可以新增产品类(只需要实现产品接口),只需要同时新增一个工厂类,客户端就可以轻松调用新产品的代码。

实例代码:

interface food{}

class A implements food{}
class B implements food{}

interface produce{ food get();}

class FactoryForA implements produce{
    @Override
    public food get() {
        return new A();
    }
}
class FactoryForB implements produce{
    @Override
    public food get() {
        return new B();
    }
}
public class AbstractFactory {
    public void ClientCode(String name){
        food x= new FactoryForA().get();
        x = new FactoryForB().get();
    }
}

4、prototype

原始模型模式:通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象。原始模型模式允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。缺点是每一个类都必须配备一个克隆方法。

浅拷贝
public class Prototype implements Cloneable{

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected Object clone()   {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }finally {
            return null;
        }
    }

    public static void main ( String[] args){
        Prototype pro = new Prototype();
        Prototype pro1 = (Prototype)pro.clone();
    }
}

深拷贝

public class Professor implements Cloneable {

    private String name;
    private Integer age;

    public Professor(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Object clone() {

        Object o = null;
        try {
            o = super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        return o;
    }

}

Student类:
public class Student implements Cloneable {

    private String name;
    private Integer age;
    private Professor professor;

    public Student(String name, Integer age, Professor professor) {
        this.name = name;
        this.age = age;
        this.professor = professor;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Professor getProfessor() {
        return professor;
    }

    public void setProfessor(Professor professor) {
        this.professor = professor;
    }

    public Object clone() {

        Student o = null;
        try {
            o = (Student) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        o.professor = (Professor) professor.clone();
        return o;
    }
}

测试:
public class Demo01 {

    public static void main(String[] args) {

        Professor p = new Professor("wangwu", 50);
        Student s1 = new Student("xiaoming", 20, p);
        Student s2 = (Student) s1.clone();
        s2.getProfessor().setName("xiaowangwu");
        s2.getProfessor().setAge(40);
        System.out.println("name=" + s1.getProfessor().getName()
                + "," + "age=" + s1.getProfessor().getAge());
        System.out.println("name=" + s2.getProfessor().getName()
                + "," + "age=" + s2.getProfessor().getAge());
    }
}

输出结果:

name=wangwu,age=50
name=xiaowangwu,age=40

5、Sigleton

单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的“单一实例”的需求时才可使用。

在内部创建一个实例,构造器全部设置为private,所有方法均在该实例上改动,在创建上要注意类的实例化只能执行一次,可以采用许多种方法来实现,如Synchronized关键字,或者利用内部类等机制来实现。

public class Singleton {
    private Singleton(){}

    private static class SingletonBuild{
        private static Singleton value = new Singleton();
    }

    public Singleton getInstance(){  return  SingletonBuild.value ;}

}

结构型模式

6、适配器模式(Adapter)

适配器(变压器)模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。

适配器模式的作用就是在原来的类上提供新功能。主要可分为3种:

类适配:创建新类,继承源类,并实现新接口,例如 

class  adapter extends oldClass  implements newFunc{}

对象适配:创建新类持源类的实例,并实现新接口,例如 

class adapter implements newFunc { private oldClass oldInstance ;}

接口适配:创建新的抽象类实现旧接口方法。例如 

abstract class adapter implements oldClassFunc { void newFunc();}

7、桥接模式(Bridge)

桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关联变成弱关联,也就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以独立的变化。

interface DrawAPI {
    public void drawCircle(int radius, int x, int y);
}
class RedCircle implements DrawAPI {
    @Override
    public void drawCircle(int radius, int x, int y) {
        System.out.println("Drawing Circle[ color: red, radius: "
                + radius +", x: " +x+", "+ y +"]");
    }
}
class GreenCircle implements DrawAPI {
    @Override
    public void drawCircle(int radius, int x, int y) {
        System.out.println("Drawing Circle[ color: green, radius: "
                + radius +", x: " +x+", "+ y +"]");
    }
}

abstract class Shape {
    protected DrawAPI drawAPI;
    protected Shape(DrawAPI drawAPI){
        this.drawAPI = drawAPI;
    }
    public abstract void draw();
}

class Circle extends Shape {
    private int x, y, radius;

    public Circle(int x, int y, int radius, DrawAPI drawAPI) {
        super(drawAPI);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    public void draw() {
        drawAPI.drawCircle(radius,x,y);
    }
}

//客户端使用代码
Shape redCircle = new Circle(100,100, 10, new RedCircle());
Shape greenCircle = new Circle(100,100, 10, new GreenCircle());
redCircle.draw();
greenCircle.draw();

8、组合模式(Composite)

合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就是一个处理对象的树结构的模式。合成模式把部分与整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待。

abstract class component{}

class File extends  component{ String filename;}

class Folder extends  component{
    component[] files ;  //既可以放文件File类,也可以放文件夹Folder类。Folder类下又有子文件或子文件夹。
    String foldername ;
    public Folder(component[] source){ files = source ;}

    public void scan(){
        for ( component f:files){
            if ( f instanceof File){
                System.out.println("File "+((File) f).filename);
            }else if(f instanceof Folder){
                Folder e = (Folder)f ;
                System.out.println("Folder "+e.foldername);
                e.scan();
            }
        }
    }

}

9、装饰模式(Decorator)

装饰模式:装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性。动态给一个对象增加功能,这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。

给一类对象增加新的功能,装饰方法与具体的内部逻辑无关。例如:

interface Source{ void method();}
public class Decorator implements Source{

    private Source source ;
    public void decotate1(){
        System.out.println("decorate");
    }
    @Override
    public void method() {
        decotate1();
        source.method();
    }
}

10、外观模式(Facade)

门面模式:外部与一个子系统的通信必须通过一个统一的门面对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。每一个子系统只有一个门面类,而且此门面类只有一个实例,也就是说它是一个单例模式。但整个系统可以有多个门面类。

public class Facade {
    private subSystem1 subSystem1 = new subSystem1();
    private subSystem2 subSystem2 = new subSystem2();
    private subSystem3 subSystem3 = new subSystem3();

    public void startSystem(){
        subSystem1.start();
        subSystem2.start();
        subSystem3.start();
    }

    public void stopSystem(){
        subSystem1.stop();
        subSystem2.stop();
        subSystem3.stop();
    }
}

11、享元模式(Flyweight)

享元模式:FLYWEIGHT在拳击比赛中指最轻量级。享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。内蕴状态存储在享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。外蕴状态不能影响内蕴状态,它们是相互独立的。将可以共享的状态和不可以共享的状态从常规类中区分开来,将不可以共享的状态从类里剔除出去。客户端不可以直接创建被共享的对象,而应当使用一个工厂对象负责创建被共享的对象。享元模式大幅度的降低内存中对象的数量。

abstract class flywei{ }

public class Flyweight extends flywei{
    Object obj ;
    public Flyweight(Object obj){
        this.obj = obj;
    }
}

class  FlyweightFactory{
    private HashMap<Object,Flyweight> data;

    public FlyweightFactory(){ data = new HashMap<>();}

    public Flyweight getFlyweight(Object object){
        if ( data.containsKey(object)){
            return data.get(object);
        }else {
            Flyweight flyweight = new Flyweight(object);
            data.put(object,flyweight);
            return flyweight;
        }
    }
}

12、代理模式(Proxy)

代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。

interface Source{ void method();}

class OldClass implements Source{
    @Override
    public void method() {
    }
}

class Proxy implements Source{
    private Source source = new OldClass();

    void doSomething(){}
    @Override
    public void method() {
        new Class1().Func1();
        source.method();
        new Class2().Func2();
        doSomething();
    }
}

行为模式

13、chain of responsibleity

责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接

起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的情况下动态的重新组织链和分配责任。处理者有两个选择:承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象所接受。

14、command

命令模式:命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行的。系统支持命令的撤消。

15、interpreter

解释器模式:给定一个语言后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。解释器模式将描述怎样在有了一个简单的文法后,使用模式设计解释这些语句。在解释器模式里面提到的语言是指任何解释器对象能够解释的任何组合。在解释器模式中需要定义一个代表文法的命令类的等级结构,也就是一系列的组合规则。每一个命令对象都有一个解释方法,代表对命令对象的解释。命令对象的等级结构中的对象的任何排列组合都是一个语言。

16、iterator

迭代子模式:迭代子模式可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容器对象。迭代子模式将迭代逻辑封装到一个独立的子对象中,从而与聚集本身隔开。迭代子模式简化了聚集的界面。每一个聚集对象都可以有一个或一个以上的迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。迭代算法可以独立于聚集角色变化。

17、mediator

调停者模式:调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用。从而使他们可以松散偶合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。调停者模式将多对多的相互作用转化为一对多的相互作用。调停者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理。

18、memento

备忘录模式:备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。

19、observer

观察者模式:观察者模式定义了一种一队多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。

20、state

状态模式:状态模式允许一个对象在其内部状态改变的时候改变行为。这个对象看上去象是改变了它的类一样。状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每一个系统可能取得的状态创立一个状态类的子类。当系统的状态变化时,系统便改变所选的子类。

21、strategy

策略模式:策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模式把行为和环境分开。环境类负责维持和查询行为类,各种算法在具体的策略类中提供。由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。

22、template method

模板方法模式:模板方法模式准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。

23、visitor

访问者模式:访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。访问者模式使得增加新的操作变的很容易,就是增加一个新的访问者类。访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。当使用访问者模式时,要将尽可能多的对象浏览逻辑放在访问者类中,而不是放到它的子类中。访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。

java生命周期

引言

最近有位细心的朋友在阅读笔者的文章时,对java类的生命周期问题有一些疑惑,笔者打开百度搜了一下相关的问题,看到网上的资料很少有把这个问题讲明白的,主要是因为目前国内java方面的教材大多只是告诉你“怎样做”,但至于“为什么这样做”却不多说,所以造成大家在基础和原理方面的知识比较匮乏,所以笔者今天就斗胆来讲一下这个问题,权当抛砖引玉,希望对在这个问题上有疑惑的朋友有所帮助,文中有说的不对的地方,也希望各路高手前来指正。

方法区:在java的虚拟机中有一块专门用来存放已经加载的类信息、常量、静态变量以及方法代码的内存区域,叫做方法区。
常量池:常量池是方法区的一部分,主要用来存放常量和类中的符号引用等信息。
堆区:用于存放类的对象实例。
栈区:也叫java虚拟机栈,是由一个一个的栈帧组成的后进先出的栈式结构,栈桢中存放方法运行时产生的局部变量、方法出口等信息。当调用一个方法时,虚拟机栈中就会创建一个栈帧存放这些数据,当方法调用完成时,栈帧消失,如果方法中调用了其他方法,则继续在栈顶创建新的栈桢。

除了以上四个内存区域之外,jvm中的运行时内存区域还包括本地方法栈和程序计数器

类的生命周期

当我们编写一个java的源文件后,经过编译会生成一个后缀名为class的文件,这种文件叫做字节码文件,只有这种字节码文件才能够在java虚拟机中运行,java类的生命周期就是指一个class文件从加载到卸载的全过程。

一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况,如图所示:
Image text

下面我们就依次来说一说这五个阶段。

加载

在java中,我们经常会接触到一个词——类加载,它和这里的加载并不是一回事,通常我们说类加载指的是类的生命周期中加载、连接、初始化三个阶段。在加载阶段,java虚拟机会做什么工作呢?其实很简单,就是找到需要加载的类并把类的信息加载到jvm的方法区中,然后在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息的入口。

类的加载方式比较灵活,我们最常用的加载方式有两种,一种是根据类的全路径名找到相应的class文件,然后从class文件中读取文件内容;另一种是从jar文件中读取。另外,还有下面几种方式也比较常用:

从网络中获取:比如10年前十分流行Applet。
根据一定的规则实时生成,比如设计模式中的动态代理模式,就是根据相应的类自动生成它的代理类。
从非class文件中获取,其实这与直接从class文件中获取的方式本质上是一样的,这些非class文件在jvm中运行之前会被转换为可被jvm所识别的字节码文件。

对于加载的时机,各个虚拟机的做法并不一样,但是有一个原则,就是当jvm“预期”到一个类将要被使用时,就会在使用它之前对这个类进行加载。比如说,在一段代码中出现了一个类的名字,jvm在执行这段代码之前并不能确定这个类是否会被使用到,于是,有些jvm会在执行前就加载这个类,而有些则在真正需要用的时候才会去加载它,这取决于具体的jvm实现。我们常用的hotspot虚拟机是采用的后者,就是说当真正用到一个类的时候才对它进行加载。

加载阶段是类的生命周期中的第一个阶段,加载阶段之后,是连接阶段。有一点需要注意,就是有时连接阶段并不会等加载阶段完全完成之后才开始,而是交叉进行,可能一个类只加载了一部分之后,连接阶段就已经开始了。但是这两个阶段总的开始时间和完成时间总是固定的:加载阶段总是在连接阶段之前开始,连接阶段总是在加载阶段完成之后完成。

连接

连接阶段比较复杂,一般会跟加载阶段和初始化阶段交叉进行,这个阶段的主要任务就是做一些加载后的验证工作以及一些初始化前的准备工作,可以细分为三个步骤:验证、准备和解析。

1、验证:当一个类被加载之后,必须要验证一下这个类是否合法,比如这个类是不是符合字节码的格式、变量与方法是不是有重复、数据类型是不是有效、继承与实现是否合乎标准等等。总之,这个阶段的目的就是保证加载的类是能够被jvm所运行。

2、准备:准备阶段的工作就是为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。有一点需要注意,这时候,静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。jvm默认的初值是这样的:

基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0。
引用类型的默认值为null。
常量的默认值为我们程序中设定的值,比如我们在程序中定义final static int a = 100,则准备阶段中a的初值就是100。

3、解析:这一阶段的任务就是把常量池中的符号引用转换为直接引用。那么什么是符号引用,什么又是直接引用呢?我们来举个例子:我们要找一个人,我们现有的信息是这个人的身份证号是1234567890。只有这个信息我们显然找不到这个人,但是通过公安局的身份系统,我们输入1234567890这个号之后,就会得到它的全部信息:比如安徽省黄山市余暇村18号张三,通过这个信息我们就能找到这个人了。这里,123456790就好比是一个符号引用,而安徽省黄山市余暇村18号张三就是直接引用。在内存中也是一样,比如我们要在内存中找一个类里面的一个叫做show的方法,显然是找不到。但是在解析阶段,jvm就会把show这个名字转换为指向方法区的的一块内存地址,比如c17164,通过c17164就可以找到show这个方法具体分配在内存的哪一个区域了。这里show就是符号引用,而c17164就是直接引用。在解析阶段,jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址。

4、连接阶段完成之后会根据使用的情况(直接引用还是被动引用)来选择是否对类进行初始化。

初始化

如果一个类被直接引用,就会触发类的初始化。在java中,直接引用的情况有:

通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法。

通过反射方式执行以上三种行为。

初始化子类的时候,会触发父类的初始化。

作为程序入口直接运行时(也就是直接调用main方法)。

除了以上四种情况,其他使用类的方式叫做被动引用,而被动引用不会触发类的初始化。请看主动引用的示例代码:

import java.lang.reflect.Field;  
import java.lang.reflect.Method;  

class InitClass{  
    static {  
        System.out.println("初始化InitClass");  
    }  
    public static String a = null;  
    public static void method(){}  
}  

class SubInitClass extends InitClass{}  

public class Test1 {  

    /**  
     * 主动引用引起类的初始化的第四种情况就是运行Test1的main方法时  
     * 导致Test1初始化,这一点很好理解,就不特别演示了。  
     * 本代码演示了前三种情况,以下代码都会引起InitClass的初始化,  
     * 但由于初始化只会进行一次,运行时请将注解去掉,依次运行查看结果。  
     * @param args  
     * @throws Exception  
     */ 
    public static void main(String[] args) throws Exception{  
    //  主动引用引起类的初始化一: new对象、读取或设置类的静态变量、调用类的静态方法。  
    //  new InitClass();  
    //  InitClass.a = "";  
    //  String a = InitClass.a;  
    //  InitClass.method();  

    //  主动引用引起类的初始化二:通过反射实例化对象、读取或设置类的静态变量、调用类的静态方法。  
    //  Class cls = InitClass.class;  
    //  cls.newInstance();  

    //  Field f = cls.getDeclaredField("a");  
    //  f.get(null);  
    //  f.set(null, "s");  

    //  Method md = cls.getDeclaredMethod("method");  
    //  md.invoke(null, null);  

    //  主动引用引起类的初始化三:实例化子类,引起父类初始化。  
    //  new SubInitClass();  

    }  
} 

上面的程序演示了主动引用触发类的初始化的四种情况。

类的初始化过程是这样的:按照顺序自上而下运行类中的变量赋值语句和静态语句,如果有父类,则首先按照顺序运行父类中的变量赋值语句和静态语句。先看一个例子,首先建两个类用来显示赋值操作:

public class Field1{  
    public Field1(){  
        System.out.println("Field1构造方法");  
    }  
}  
public class Field2{  
    public Field2(){  
        System.out.println("Field2构造方法");  
    }  
}  

下面是演示初始化顺序的代码:

class InitClass2{  
    static{  
        System.out.println("运行父类静态代码");  
    }  
    public static Field1 f1 = new Field1();  
    public static Field1 f2;   
}  

class SubInitClass2 extends InitClass2{  
    static{  
        System.out.println("运行子类静态代码");  
    }  
    public static Field2 f2 = new Field2();  
}  

public class Test2 {  
    public static void main(String[] args) throws ClassNotFoundException{  
        new SubInitClass2();  
    }  
} 

上面的代码中,初始化的顺序是:第03行,第05行,第11行,第13行。第04行是声明操作,没有赋值,所以不会被运行。而下面的代码:

class InitClass2{  
    public static Field1 f1 = new Field1();  
    public static Field1 f2;  
    static{  
        System.out.println("运行父类静态代码");  
    }  
}  

class SubInitClass2 extends InitClass2{  
    public static Field2 f2 = new Field2();  
    static{  
        System.out.println("运行子类静态代码");  
    }  
}  

public class Test2 {  
    public static void main(String[] args) throws ClassNotFoundException{  
        new SubInitClass2();  
    }  
}  

初始化顺序为:第02行、第05行、第10行、第12行,各位可以运行程序查看结果。

在类的初始化阶段,只会初始化与类相关的赋值语句和静态语句,也就是有static关键字修饰的信息,没有static修饰的赋值语句和静态语句在实例化对象的时候才会运行。

使用

类的使用包括主动引用和被动引用,主动引用在初始化的章节中已经说过了,下面我们主要来说一下被动引用:

引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化。
定义类数组,不会引起类的初始化。
引用类的常量,不会引起类的初始化。

被动引用的示例代码:

class InitClass{  
    static {  
        System.out.println("初始化InitClass");  
    }  
    public static String a = null;  
    public final static String b = "b";  
    public static void method(){}  
}  

class SubInitClass extends InitClass{  
    static {  
        System.out.println("初始化SubInitClass");  
    }  
}  

public class Test4 {  

    public static void main(String[] args) throws Exception{  
    //  String a = SubInitClass.a;// 引用父类的静态字段,只会引起父类初始化,而不会引起子类的初始化  
    //  String b = InitClass.b;// 使用类的常量不会引起类的初始化  
        SubInitClass[] sc = new SubInitClass[10];// 定义类数组不会引起类的初始化  
    }  
}  

最后总结一下使用阶段:使用阶段包括主动引用和被动引用,主动饮用会引起类的初始化,而被动引用不会引起类的初始化。

当使用阶段完成之后,java类就进入了卸载阶段。

卸载

关于类的卸载,笔者在单例模式讨论篇:单例模式与垃圾回收一文中有过描述,在类使用完之后,如果满足下面的情况,类就会被卸载:

该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。

加载该类的ClassLoader已经被回收。

该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了。

总结

做java的朋友对于对象的生命周期可能都比较熟悉,对象基本上都是在jvm的堆区中创建,在创建对象之前,会触发类加载(加载、连接、初始化),当类初始化完成后,根据类信息在堆区中实例化类对象,初始化非静态变量、非静态代码以及默认构造方法,当对象使用完之后会在合适的时候被jvm垃圾收集器回收。读完本文后我们知道,对象的生命周期只是类的生命周期中使用阶段的主动引用的一种情况(即实例化类对象)。而类的整个生命周期则要比对象的生命周期长的多。

原文链接:http://blog.csdn.net/zhengzhb/article/details/7517213

webpack中如何使用雪碧图

一、什么是雪碧图?

CSS雪碧 即CSS Sprite,也有人叫它CSS精灵,是一种CSS图像合并技术,该方法是将小图标和背景图像合并到一张图片上,然后利用css的背景定位来显示需要显示的图片部分。

二:为什么要用雪碧图
在开发过程中,我们需要用到很多图标,这些图标大小不是很大,但是每张图是一个请求加重服务器负担,这样是很可怕的,所以为了优化性能,减少http请求,决定采用雪碧图的形式。

雪碧图是将你想要的很多张图片整理成一张图片,然后通过icon-*来进行图片识别和定位来达到之前的效果。
三:如何使用雪碧图

雪碧图在之前有很多方式,如ps之类,现在最佳的方案还是在webpack-spritesmith。

我其实对webpack并不是很了解,我现在列出使用方法和我在使用webpack时候遇到的问题。
1.安装

执行命令行:npm install –save-dev webpack-spritesmith
2.在webpack.config.js中写入

企业微信小程序开发流程

背景

为其他公司开发一个小程序,这个小程序要只能在企业微信里面访问,不能通过普通微信访问。

企业微信里只有授权的用户可以使用该小程序

问题分析

首先,这里面需要解决几个问题:

1、只在企业微信里使用,不能在普通小程序使用

判断当前运行环境

2、只有授权的用户可以使用该小程序

权限校验

1:普通微信,使用账号+密码登陆,审核专用,可限制账号权限

2:企业微信,校验企业id,校验用户id,有权限的才允许使用

3、控制搜索

在小程序管理后台设置「不允许被搜索」

4、控制分享

小程序里面关闭分享

OK,做到以上几点,就实现了只有企业微信里授权的用户,才能够在企业微信的 工作台 看到该小程序。

处理流程

1、小程序基础功能开发,提交审核

对,没看错,先提交审核,因为只有审核通过的小程序才能被企业微信绑定。所以,先把小程序的基础功能做出来,可以限制一部分功能,总之,就是先让这个小程序上架。同时,在小程序的管理后台设置「不允许被搜索」,避免不必要的麻烦。

2、企业微信关联小程序

进入企业微信后台 -> 应用管理 -> 小程序 -> 关联小程序,然后使用小程序管理员的微信扫码,按照指引操作即可。

3、获取关联小程序的 Secret,设置可见范围

上一步的小程序页面会出现刚才关联的小程序,点击进入,然后就看到 secret 及可见范围。

这个 secret 相当于该小程序访问该企业微信数据的令牌,请妥善保存。

可见范围就是授权,哪些用户能看到这个小程序,被设置可见的人将会在自己企业微信的工作台看到该小程序。

4、修改小程序

4.1、判断运行环境

小程序需要判断当前的运行环境(普通微信 or 企业微信),需要判断使用当前小程序的用户是否有权限使用。

var isWxWork = false;
wx.getSystemInfo({
  success(res) {
    console.log(res.environment);

    isWxWork = res.environment == 'wxwork';
    if (!isWxWork) {
        // 当前环境不是企业微信,怎么处理你随便
        return;
    }

    // 当前环境是企业微信,执行登陆,获取用户 code,用于后面的权限校验
    wx.qy.login({
      success: function (res) {
        if (res.code) {
            console.log(res.code);

            // 这里可以将 res.code 通过请求发送给后台,让后台做权限校验
        } else {
            console.log('登录失败!' + res.errMsg);
        }
      }
    });
  }
})

4.2、权限校验

后台需要调用以下几个接口来进行权限校验。

1、获取 access_token

https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=xxxx&corpsecret=xxxx

请求方式:GET

这个接口和普通微信获取 token 的方法类似。

其中,corpid 在企业微信管理后台->我的企业->企业信息->企业ID;corpsecret 就是我们上一步关联小程序后获取的 secret。

返回内容如下:

{
    "errcode": 0,
    "errmsg": "ok",
    "access_token": "xxxxxx",
    "expires_in": 7200
}

2、获取 userid

https://qyapi.weixin.qq.com/cgi-bin/miniprogram/jscode2session?access_token=xxx&js_code=xxx&grant_type=authorization_code

请求方式:GET

其中,access_token 是我们前一步 gettoken 获取到的;js_code 是前面判断运行环境时获取到的 res.code;grant_type 固定传 authorization_code

返回内容如下:

{
    "userid": "bottle",
    "session_key": "xxxxx",
    "corpid": "xxxxxx",
    "deviceid": "xxxxxx",
    "errcode": 0,
    "errmsg": "ok"
}

这里的 corpid 可以用来初步校验当前用户是否有权限,因为无论哪个公司的人,只要他用企业微信,使用这个小程序,都会返回这样的结果,你需要根据 corpid 是否是你授权的企业的ID来校验,如果不是,直接返回无权限就可以,不用进行下一步。

当然 corpid 也可以用于处理一个小程序关联多家公司的情况,不过这是另一个问题了。这里简单提一嘴,因为是给其他公司开发的小程序,所以我们这个小程序也关联了两个企业,一个我们公司,一个对方公司,这样也方便我们测试,只需要给我们自己的测试人员授权就可以让他们使用完全相同的环境进行测试了。

3、获取用户信息(判断权限)

https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=xxx&userid=xxx

请求方式:GET

其中,access_token 是我们前一步 gettoken 获取到的;userid 就是我们上一步获取到的 userid。

返回内容如下:

  • 有权限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"errcode": 0,
"errmsg": "ok",
"userid": "xxx",
"name": "xxx",
"department": [],
"position": "",
"mobile": "xxx",
"gender": "2",
"email": "",
"avatar": "http://p.qlogo.cn/bizmail/xxx/0",
"status": 1,
"isleader": 0,
"extattr": {
"attrs": []
},
"telephone": "",
"enable": 1,
"hide_mobile": 0,
"order": [],
"qr_code": "https://open.work.weixin.qq.com/wwopen/userQRCode?vcode=xxx",
"alias": "",
"is_leader_in_dept": []
}
  • 无权限
1
2
3
4
5
6
7
{
"errcode": 60011,
"errmsg": "no privilege to access/modify contact/party/agent , hint: [1564556097_7_8d45297bd21be3702ff430560e1f0652], from ip: 118.113.1.217, more info at https://open.work.weixin.qq.com/devtool/query?e=60011",
"department": [],
"order": [],
"is_leader_in_dept": []
}

OK,后面根据有权限还是无权限,执行不同的操作就可以了,这里不再赘述。

参考资料

企业微信API(小程序):https://work.weixin.qq.com/api/doc#90000/90136/90289

企业微信接口调试工具:https://work.weixin.qq.com/api/devtools/devtool.php

错误码查询工具:https://open.work.weixin.qq.com/devtool/query