烈虎

烈虎 查看完整档案

北京编辑聊城大学东昌学院  |  软件工程 编辑AB.inc  |  web工程师 编辑 peichenhu.cn 编辑
编辑

失去人性,失去很多;
失去兽性,失去一切。

个人动态

烈虎 发布了文章 · 2019-10-09

JS-new操作构造函数深度解析

现看一段很熟悉的代码:

function Parent(name) {
    this.name = name;
    this.say = function () {
        return this.name;
    };
}
Parent.prototype.age = 18;

var child = new Parent("pch");

console.log(child); // 输出: Parent { name: 'pch', say: [Function] }
console.log(child.name); // 输出:pch
console.log(child.say()); // 输出:pch
console.log(child.age); // 输出:18

说到继承就必先说构造函数 new Function()
那么 new 操作符究竟内部是如何实现的?
它是如何创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例?

查阅资料我们得知内部操作是:

  1. 创建一个空的简单JavaScript对象(即{});
  2. 链接该对象(即设置该对象的构造函数)到另一个对象 ;
  3. 将步骤1新创建的对象作为this的上下文 ;
  4. 如果该函数没有返回对象,则返回this。

现在我们自己实现一个 new Function() 同样功能的模拟构造函数 _new():

1、先创建我们的目标函数

function Parent(name) {
    this.name = name;
    this.say = function () {
        return this.name;
    };
    return {};
}

// 原型也加点属性测试原型链继承情况
Parent.prototype.age = 18;

2、创建我们的 _new 模拟构造函数:

function _new() { }

3、如何调用模拟构造函数呢?

我们假定这样调用:

var child = _new(Parent, "pch");

4、实现函数内部逻辑


function _new() { 
    // 4.1、我们处理参数
    // 检验第一个参数是不是函数。不是函数返回提示信息
    if (Object.prototype.toString.call(arguments[ 0 ]) !== "[object Function]") {
        throw "The first parameter must be a function.";
    }
    
    // 4.2、创建一个空的简单JavaScript对象(即{});
    
    var obj = {};
    
    // 4.3、链接该对象(即设置该对象的构造函数)到另一个对象(我认为说的是目标函数对象,绑定原型) ;
    obj.__proto__ = arguments[ 0 ].prototype;
    
    // 4.4、将步骤1新创建的对象作为this的上下文(大概是切换 this的意思,我们可以使用 apply 进行参数传递);
    var res = arguments[ 0 ].apply(obj, Array.prototype.slice.call(arguments).slice(1));
    
    // 4.5、如果该函数没有返回对象,则返回this(大概意思是说目标函数不存在返回值时,返回新对象,否则返回目标函数的返回值)。
    // 经过测试,使用 new 操作符时,
    // 目标函数返回值是 Object 就返回目标函数的返回值,
    // 否则就返回新对象
    return ( res instanceof Object ) ? res : obj;
}

5、测试我们的函数

// 测试代码

var child = _new(Parent, "pch");

console.log(child); // 输出: Parent { name: 'pch', say: [Function] }
console.log(child.name); // 输出:pch
console.log(child.say()); // 输出:pch
console.log(child.age); // 输出:18

// 测试错误传参
try {
    _new(1, 2, 3);
} catch (e) {
    console.log(e); // 输出:The first parameter must be a function.
}

// 测试带返回值的目标函数
function ParentHasReturn() {
    return "pch";
}

console.log(_new(ParentHasReturn)); // 输出:pch

6、总结:测试成功

补充内容:

  • 对象有__proto__属性,函数有prototype属性;
  • 对象由函数生成;
  • 生成对象时,对象的__proto__属性指向函数的prototype属性。
更多内容请移驾:我的博客
查看原文

赞 1 收藏 0 评论 0

烈虎 发布了文章 · 2019-09-20

React 封装 Echarts 公共组件

前序

LZ 之前工作一直在用 Vue,但最近听说 Vue 新版也要 All IN JS,所以想着干脆换到 React 算了,所以目前在学习 React + TS + Hook,顺手拿了一个老项目重构,今天主要讲 React 封装 Echarts 公共组件, 因为第一次正式搞,所以本文中如果有 React 代码哪里不规范还请大佬们批评指正哈!

目录:

  1. 需求分析
  2. 技术评估
  3. 实现思路
  4. 测试优化
  5. 总结分享

1. 需求分析

  • ECharts 图表要用到很多,所以要将公共部分抽取成组件减少重复代码
  • ECharts 是需要操作到真实dom的第三方库,和MVVM框架一起使用需要做一些特殊处理
  • Vue项目中使用的Vue-ECharts,组件自动去处理实例挂、 数据更新等
  • React也有echarts-for-react等优秀的开源组件,但为了学习和更舒适的使用,我们需要自己去造新的轮子
  • 参考资料:

2. 技术评估

我们需要用到四个东西:

  • React
  • React Hook
  • Echarts
  • TypeScript

3. 实现思路

1) 一个组件需要的参数配置 ChartProps

参数描述必填
keystring用于保持多图表时每一个图表的独立性
optionobject 或者 nullEcharts 配置参数
style { width: string, height: string }用于保持多图表时每一个图表的独立性
classNamestring组件样式类 className
onRenderonRender?(instance): void;渲染时回调函数,返回图表示例

2) 参数类型检查接口 interface

interface ChartProps {
    key: string;
    option: object | null;
    style: {
        width: string;
        height: string;
    };
    className?: string;
    onRender?(instance): void;
}

3) 基础组件 Chart.tsx

import * as React from "react";
import echarts from "echarts";

const Chart = (props: ChartProps): React.ReactElement => {
    // 挂载节点
    let chartDom = null;

    // 元素挂载到浏览器事件
    const refOnRender = (el): void => chartDom = el;
    
    // 返回组件
    return React.createElement("div", {
        ref: refOnRender,
        style: props.style,
        className: props.className
    });

};

export default Chart;

4) 当组件挂载到真实DOM,初始化Echarts实例,使用Hook

// 生命钩子函数
type Callback = () => void;
React.useEffect((): Callback => {

    // 加载状态
    function showLoading(instance): void {
        instance.showLoading("default", {
            text: "",
            color: "#c23531",
            textColor: "#000000",
            maskColor: "rgba(255, 255, 255, 0.8)",
            zlevel: 0
        });
    }

    // 获取实例对象
    let instance = echarts.getInstanceByDom(chartDom) || echarts.init(chartDom);

    // 默认加载状态
    showLoading(instance);
    
    // 如果存在参数,渲染图表
    if (props.option) {
        // 关闭加载状态
        if (instance) instance.hideLoading();
        // 渲染图表
        instance.setOption(props.option);
    }
    
}, [props.option]);

5)浏览器窗口大小变化时图表大小自适应重绘

// 大小自适应
const resize = (): void => instance.resize();
window.removeEventListener("resize", resize);
window.addEventListener("resize", resize);

6) 给图表加动画需要图表实例,设置回调函数将图表实例返回

// 回调函数返回实例
if (props.onRender) props.onRender(instance);

7) 组件销毁时清除 监听器组件状态值

// 销毁并清除状态
return (): void => {
    echarts.dispose(instance);
    window.removeEventListener("resize", resize);
};

8) 最终完整组件

import * as React from "react";
import echarts from "echarts";

/**
 * 参数列表
 * key: string; 唯一值
 * option: object | null; 图表数据
 * style: {
 *      width: string; 图表宽度
 *      height: string; 图表高度
 * };
 * className?: string; 图表CSS样式类名称
 * onRender?(instance): void; 图表回调函数返回图表实例
 */

interface ChartProps {
    key: string;
    option: object | null;
    style: {
        width: string;
        height: string;
    };
    className?: string;

    onRender?(instance): void;
}

const Chart = (props: ChartProps): React.ReactElement => {

    // 挂载节点
    let chartDom = null;

    // 生命钩子函数
    type Callback = () => void;
    React.useEffect((): Callback => {
        console.log("useEffect");

        // 加载状态
        function showLoading(instance): void {
            instance.showLoading("default", {
                text: "",
                color: "#c23531",
                textColor: "#000000",
                maskColor: "rgba(255, 255, 255, 0.8)",
                zlevel: 0
            });
        }

        // 获取实例对象
        let instance = echarts.getInstanceByDom(chartDom) || echarts.init(chartDom);

        // 大小自适应
        const resize = (): void => instance.resize();
        window.removeEventListener("resize", resize);
        window.addEventListener("resize", resize);

        // 默认加载状态
        showLoading(instance);

        // 渲染图表
        if (props.option) {
            if (instance) instance.hideLoading();
            instance.setOption(props.option);
        }

        // 回调函数返回实例
        if (props.onRender) props.onRender(instance);

        // 销毁并清除状态
        return (): void => {
            echarts.dispose(instance);
            window.removeEventListener("resize", resize);
        };

    }, [chartDom, props]);


    // 元素挂载到浏览器事件
    const refOnRender = (el): void => chartDom = el;
    
    // 返回组件
    return React.createElement("div", {
        ref: refOnRender,
        style: props.style,
        className: props.className
    });

};

// 导出组件模块
export default Chart;

测试优化

你可以:

  1. 自行搭建React+TS+Echarts开发环境
  2. 使用 Clone 本项目测试(暂不支持 sry)

主要代码 Test.tsx

import * as React from "react";
// 导入 Chart 组件
import Chart from "../chart";

const chartEmpty = {
    title: {
        text: "暂无数据",
        show: true,
        textStyle: {
            color: "grey",
            fontSize: 20
        },
        left: "center",
        top: "center"
    }
};

const Test= (): React.ReactElement => {
    // 图表1数据
    let [chart1Data, setChart1Data] = React.useState(null);
    // 图表2数据
    let [chart2Data, setChart1Data] = React.useState(null);
    
    // 防护监控数据 实例
    let [chart1, chart2] = [null, null];
    
    // 模拟异步更新图表数据
    function updateChart(): void{
        let opts: null;
        
        chartEmpty.title.text = "图表 1 暂无数据" + (+new Date());
        opts = JSON.parse(JSON.stringify(chartEmpty));
        setChart1Data(opts);
        
        chartEmpty.title.text = "图表 2 暂无数据" + (+new Date());
        opts = JSON.parse(JSON.stringify(chartEmpty));
        setChart2Data(opts);
    }
    
    // 获取图表实例,添加自定义图表事件
    React.useEffect((): void => {
        console.log("chart1", chart1);
    }, [chart1]);
    
    // 返回组件
    return (
        <div>
           <Chart
                key="chart1"
                className="chart1"
                option={chart1Data}
                onRender={(e): void => chart1 = e}
                style={{width: "100%", height: "400px"}}/>
           <hr>       
           <Chart
                key="chart2"
                className="chart2"
                option={chart2Data}
                style={{width: "100%", height: "400px"}}/>
           <button onClick={updateChart}> 异步更新图表数据 </button>
        </div>
    )
}

export default Test;

总结分享

学习了React组件的封装,由此及彼,以后其他第三方库应该也可以轻松集成了
学习了React Hook使用方法。
学习了React + TS的开发模式。
不到100行代码的组件相比于echarts-for-react等其他优秀组件的少了很多高级和细节自定义。(开源的话会加进去吧)
欢迎大佬检阅。

查看原文

赞 1 收藏 0 评论 0

烈虎 发布了文章 · 2019-06-02

使用 Vue Router 的 addRoutes 方法实现动态添加用户的权限路由

最近做vue 单页项目涉及到多角色用户权限问题,不同的角色用户拥有不同的功能权限, 不同的功能权限对应的不同的页面

git: https://github.com/pch1024/dy...

online: http://dynamicrouter.peichenh...

举个例子:
    角色A =>功能1
         =>功能2
         =>功能3
         
    角色B =>功能1
         =>功能4
         =>功能5

第1步 定义默认路由和动态路由

//动态路由(所有角色的都在这里,我们都做好组件页面了所以我们一定有这个,防君子不防小人)
export const dynamicRouter = [
    { path: '/b', name: 'b', component: pageB },
    { path: '/c', name: 'c', component: pageC },
];

//默认路由(无需登录就可以使用)
const routes = [
    { path: '/', redirect: '/login' },
    { path: '/login', component: pageLogin},
    { path: '/404', component: page404},
    { path: '*', redirect: '/404' },
];

const router = new VueRouter({
    mode: 'history',
    routes, // (缩写) 相当于 routes: routes
});

第2步 登录获取权限规则

当然,登录还要获取token、用户信息等,我们暂时不关注,我们的权限规则需要在多处使用所以我们将它存到vuex里

// vue 组件
<li @click="login(['b'])">
  模拟用户1登录,权限['b'],跳转到页面B
</li>
<li @click="login(['c'])">
  模拟用户2登录,权限['c'],跳转到页面B,(用户2没有页面B权限,强行进入会gun去页面404)
</li>

// 登录模块---------------------------------------------------------
import { mapActions } from "vuex";
export default {
  methods: {
    ...mapActions([
      "set_roleRouterRules"
    ]),
    login(roleRouterRules) {
      // 登录成功,vuex 存储角色路由
      this.set_roleRouterRules(roleRouterRules);
      // 跳转到动态注册B
      this.$router.replace({ path: "/b" });
    }
  }
};

// vuex 对应功能实现-----------------------------------------------
// 引入第1步 定义的dynamicRouter 
import { dynamicRouter } from './router';
// 私有变量
state: {
    isAddRoutes: false,
    // 后端返回的原始数据默认存到 localStorage,每次初始化取出来
    roleRouterRules: JSON.parse(localStorage.getItem('roleRouterRules')),
},
// 公共变量 => 派生私有变量
getters: {
    isAddRoutes: state => state.isAddRoutes,
    // 根据 roleRouterRules 生成当前角色的动态路由配置数据(addRoutes方法可以直接使用的路由数组)
    roleRouter: state => {
        if (state.roleRouterRules) {
            return dynamicRouter.filter(
                router => state.roleRouterRules.indexOf(router.name) >= 0,
            );
        } else return null;
    },
},
// 私有方法(同步) => 改变静态变量
mutations: {
    set_isAddRoutes: (state, data) => (state.isAddRoutes = data), // payload: true/false
    set_roleRouterRules: (state, data) => (state.roleRouterRules = data), // payload: true/false
},
// 公共方法(可异步)=> 调用私有方法
actions: {
    set_isAddRoutes({ commit }, data) {
        commit('set_isAddRoutes', data);
    },
    set_roleRouterRules({ commit }, data) {
        // 保存到vuex
        commit('set_roleRouterRules', data);
        // 保存到 localStorage,当用户强制刷新浏览器时我们要使用这一份数据初始化 state.roleRouterRules
        localStorage.setItem('roleRouterRules', JSON.stringify(data));
    },
}

第3步 登录成功跳转权限页面(核心)

基本思路:

  • 所有的路由跳转都要做鉴权,
  • 不是动态路由(也就是默认路由)直接放过,
  • 是动态路由(也就是还未创建的,强行进入会被重定向到404,但依然可以在to.redirectedFrom获取到用户希望进去的路由),检查前端是否有路由权限规则数据

    • 没有,让他去登录页
    • 有,就根据 roleRouterRules 生成当前角色的动态路由配置数据并addRoutes添加到真实router,此时通过let path = to.redirectedFrom || to.path;next(path); 再走一遍鉴权(这一次真实router上有它就进页面,还没有就代表这个用户没有这个页面访问权限gun去404)

注意事项:

  • addRoutes() 方法一个用户只能使用一次,所以要加一个状态值isAddRoutes到vuex里,每次用户进动态路由时检查 addRoutes 使用过没有
  • next() 方法没有参数会直接放行,有参数(例如 next({path:'/404'})) 放行后会再次进入router.beforeEach,一不小心就是死循环
import vuex from './vuex';
router.beforeEach((to, from, next) => {
    let path = to.redirectedFrom || to.path;
    // 白名单 放行
    if (whiteList.indexOf(path) >= 0) return next();
    // 黑名单
    if (!vuex.getters.roleRouter) return next({ path: '/login' });
    if (!vuex.getters.isAddRoutes) {
        console.log('path未注册,存在角色路由,立即注册尝试匹配');
        router.addRoutes(vuex.getters.roleRouter);
        vuex.dispatch('set_isAddRoutes', true);
        next(path);
    } else {
        console.log('已注册过动态路由,尝试匹配');
        next();
    }
});

第4步 切换不同角色用户

此处有坑, Vue Router 只提供了 addRoutes ,却没有删除和替换方法,所以只能通过强刷新浏览器来重置 Vue Router,先清空localStorage,在刷新时,初始化的Vue Router只有默认路由,用户只能去登录页了
还有一种方法我没看懂,感兴趣可以查看:https://github.com/vuejs/vue-...

exit() {
  localStorage.clear();
  window.location.reload();
}
查看原文

赞 0 收藏 0 评论 0

烈虎 发布了文章 · 2019-06-02

使用 Vue Router 的 addRoutes 方法实现动态添加用户的权限路由

最近做vue 单页项目涉及到多角色用户权限问题,不同的角色用户拥有不同的功能权限, 不同的功能权限对应的不同的页面

git: https://github.com/pch1024/dy...

online: http://dynamicrouter.peichenh...

举个例子:
    角色A =>功能1
         =>功能2
         =>功能3
         
    角色B =>功能1
         =>功能4
         =>功能5

第1步 定义默认路由和动态路由

//动态路由(所有角色的都在这里,我们都做好组件页面了所以我们一定有这个,防君子不防小人)
export const dynamicRouter = [
    { path: '/b', name: 'b', component: pageB },
    { path: '/c', name: 'c', component: pageC },
];

//默认路由(无需登录就可以使用)
const routes = [
    { path: '/', redirect: '/login' },
    { path: '/login', component: pageLogin},
    { path: '/404', component: page404},
    { path: '*', redirect: '/404' },
];

const router = new VueRouter({
    mode: 'history',
    routes, // (缩写) 相当于 routes: routes
});

第2步 登录获取权限规则

当然,登录还要获取token、用户信息等,我们暂时不关注,我们的权限规则需要在多处使用所以我们将它存到vuex里

// vue 组件
<li @click="login(['b'])">
  模拟用户1登录,权限['b'],跳转到页面B
</li>
<li @click="login(['c'])">
  模拟用户2登录,权限['c'],跳转到页面B,(用户2没有页面B权限,强行进入会gun去页面404)
</li>

// 登录模块---------------------------------------------------------
import { mapActions } from "vuex";
export default {
  methods: {
    ...mapActions([
      "set_roleRouterRules"
    ]),
    login(roleRouterRules) {
      // 登录成功,vuex 存储角色路由
      this.set_roleRouterRules(roleRouterRules);
      // 跳转到动态注册B
      this.$router.replace({ path: "/b" });
    }
  }
};

// vuex 对应功能实现-----------------------------------------------
// 引入第1步 定义的dynamicRouter 
import { dynamicRouter } from './router';
// 私有变量
state: {
    isAddRoutes: false,
    // 后端返回的原始数据默认存到 localStorage,每次初始化取出来
    roleRouterRules: JSON.parse(localStorage.getItem('roleRouterRules')),
},
// 公共变量 => 派生私有变量
getters: {
    isAddRoutes: state => state.isAddRoutes,
    // 根据 roleRouterRules 生成当前角色的动态路由配置数据(addRoutes方法可以直接使用的路由数组)
    roleRouter: state => {
        if (state.roleRouterRules) {
            return dynamicRouter.filter(
                router => state.roleRouterRules.indexOf(router.name) >= 0,
            );
        } else return null;
    },
},
// 私有方法(同步) => 改变静态变量
mutations: {
    set_isAddRoutes: (state, data) => (state.isAddRoutes = data), // payload: true/false
    set_roleRouterRules: (state, data) => (state.roleRouterRules = data), // payload: true/false
},
// 公共方法(可异步)=> 调用私有方法
actions: {
    set_isAddRoutes({ commit }, data) {
        commit('set_isAddRoutes', data);
    },
    set_roleRouterRules({ commit }, data) {
        // 保存到vuex
        commit('set_roleRouterRules', data);
        // 保存到 localStorage,当用户强制刷新浏览器时我们要使用这一份数据初始化 state.roleRouterRules
        localStorage.setItem('roleRouterRules', JSON.stringify(data));
    },
}

第3步 登录成功跳转权限页面(核心)

基本思路:

  • 所有的路由跳转都要做鉴权,
  • 不是动态路由(也就是默认路由)直接放过,
  • 是动态路由(也就是还未创建的,强行进入会被重定向到404,但依然可以在to.redirectedFrom获取到用户希望进去的路由),检查前端是否有路由权限规则数据

    • 没有,让他去登录页
    • 有,就根据 roleRouterRules 生成当前角色的动态路由配置数据并addRoutes添加到真实router,此时通过let path = to.redirectedFrom || to.path;next(path); 再走一遍鉴权(这一次真实router上有它就进页面,还没有就代表这个用户没有这个页面访问权限gun去404)

注意事项:

  • addRoutes() 方法一个用户只能使用一次,所以要加一个状态值isAddRoutes到vuex里,每次用户进动态路由时检查 addRoutes 使用过没有
  • next() 方法没有参数会直接放行,有参数(例如 next({path:'/404'})) 放行后会再次进入router.beforeEach,一不小心就是死循环
import vuex from './vuex';
router.beforeEach((to, from, next) => {
    let path = to.redirectedFrom || to.path;
    // 白名单 放行
    if (whiteList.indexOf(path) >= 0) return next();
    // 黑名单
    if (!vuex.getters.roleRouter) return next({ path: '/login' });
    if (!vuex.getters.isAddRoutes) {
        console.log('path未注册,存在角色路由,立即注册尝试匹配');
        router.addRoutes(vuex.getters.roleRouter);
        vuex.dispatch('set_isAddRoutes', true);
        next(path);
    } else {
        console.log('已注册过动态路由,尝试匹配');
        next();
    }
});

第4步 切换不同角色用户

此处有坑, Vue Router 只提供了 addRoutes ,却没有删除和替换方法,所以只能通过强刷新浏览器来重置 Vue Router,先清空localStorage,在刷新时,初始化的Vue Router只有默认路由,用户只能去登录页了
还有一种方法我没看懂,感兴趣可以查看:https://github.com/vuejs/vue-...

exit() {
  localStorage.clear();
  window.location.reload();
}
查看原文

赞 0 收藏 0 评论 0

烈虎 发布了文章 · 2018-07-03

nodejs 全自动使用 Tinypng (免费版,无需任何配置)压缩图片

## 无需任何插件, 随意CV,一行命令搞定:

node ./tinypng.js -f ./test -deep

大体思路:

  • 递归获取本地文件夹里的文件
  • 过滤文件,格式必须是.jpg.png,大小小于5MB.(文件夹递归)
  • 每次只处理一个文件(可以绕过20个的数量限制)
  • 处理返回数据拿到远程优化图片地址
  • 取回图片更新本地图片
  • 纯node实现不依赖任何其他代码片段

`

/** 
 * 帮助文档
 * -------
 * 
 * 获取帮助
 * 指令 -h
 * 
 * 获取命令执行文件夹 
 * 指令 -f 
 * 参数 ./
 * 必填,待处理的图片文件夹
 * 
 * 获取是否深度递归处理图片文件夹
 * 指令 -deep
 * 可选,默认不深度递归
 * 
 * 命令行脚本参考示例
 * > node ./tinypng.js -f ./test -deep
 *  */

const fs = require('fs');
const path = require('path');
const https = require('https');
const URL = require('url').URL;
const EventEmitter = require('events');
const err = msg => new EventEmitter().emit('error', msg);

if (getHelp()) return false;

const conf = {
    files: [],
    EntryFolder: getEntryFolder(),
    DeepLoop: getDeepLoop(),
    Exts: ['.jpg', '.png'],
    Max: 5200000, // 5MB == 5242848.754299136
}

fileFilter(conf.EntryFolder)

console.log("本次执行脚本的配置:", conf);
console.log("等待处理文件的数量:", conf.files.length)

conf.files.forEach(img => fileUpload(img));

//////////////////////////////// 工具函数

/**
 * 获取帮助命令
 * 指令 -h
 */
function getHelp() {
    let i = process.argv.findIndex(i => i === "-h");
    if (i !== -1) {
        console.log(
        * 帮助文档
        * -------
        * 
        * 获取帮助
        * 指令 -h
        * 
        * 获取命令执行文件夹 
        * 指令 -f 
        * 参数 ./
        * 必填,待处理的图片文件夹
        * 
        * 获取是否深度递归处理图片文件夹
        * 指令 -deep
        * 可选,默认不深度递归
        * 
        * > node ./tinypng.js -f ./test -deep
        )
        return true;
    }
}

/**
 * 获取命令执行文件夹 
 * 指令 -f 
 * 参数 ./
 * 必填,待处理的图片文件夹
 */
function getEntryFolder() {
    let i = process.argv.findIndex(i => i === "-f");
    if (i === -1 || !process.argv[i + 1]) return err('获取命令执行文件夹:失败');
    return process.argv[i + 1];
}

/**
 * 获取是否深度递归处理图片文件夹
 * 指令 -deep
 * 可选,默认不深度递归
 */
function getDeepLoop() {
    return process.argv.findIndex(i => i === "-deep") !== -1;
}

/**
 * 过滤待处理文件夹,得到待处理文件列表
 * @param {*} folder 待处理文件夹
 * @param {*} files 待处理文件列表
 */
function fileFilter(folder) {
    // 读取文件夹
    fs.readdirSync(folder).forEach(file => {
        let fullFilePath = path.join(folder, file)
        // 读取文件信息
        let fileStat = fs.statSync(fullFilePath);
        // 过滤文件安全性/大小限制/后缀名
        if (fileStat.size <= conf.Max && fileStat.isFile() && conf.Exts.includes(path.extname(file))) conf.files.push(fullFilePath);
        // 是都要深度递归处理文件夹
        else if (conf.DeepLoop && fileStat.isDirectory()) fileFilter(fullFilePath);
    });
}

/**
 * TinyPng 远程压缩 HTTPS 请求的配置生成方法
 */

function getAjaxOptions() {
    return {
        method: 'POST',
        hostname: 'tinypng.com',
        path: '/web/shrink',
        headers: {
            rejectUnauthorized: false,
            "X-Forwarded-For": Array(4).fill(1).map(() => parseInt(Math.random() * 254 + 1)).join('.'),
            'Postman-Token': Date.now(),
            'Cache-Control': 'no-cache',
            'Content-Type': 'application/x-www-form-urlencoded',
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
        }
    }
}

/**
 * TinyPng 远程压缩 HTTPS 请求
 * @param {string} img 待处理的文件
 * @success {
 *              "input": { "size": 887, "type": "image/png" },
 *              "output": { "size": 785, "type": "image/png", "width": 81, "height": 81, "ratio": 0.885, "url": "https://tinypng.com/web/output/7aztz90nq5p9545zch8gjzqg5ubdatd6" }
 *           }
 * @error  {"error": "Bad request", "message" : "Request is invalid"}
 */
function fileUpload(imgPath) {
    let req = https.request(getAjaxOptions(), (res) => {
        res.on('data', buf => {
            let obj = JSON.parse(buf.toString());
            if (obj.error) console.log(压缩失败!\n 当前文件:${imgPath} \n ${obj.message});
            else fileUpdate(imgPath, obj);
        });
    });

    req.write(fs.readFileSync(imgPath), 'binary');
    req.on('error', e => console.error(请求错误! \n 当前文件:${imgPath} \n, e));
    req.end();
}

// 该方法被循环调用,请求图片数据
function fileUpdate(entryImgPath, obj) {
    let options = new URL(obj.output.url);
    let req = https.request(options, res => {
        let body = '';
        res.setEncoding('binary');
        res.on('data', (data) => body += data);
        res.on('end', () => {
            fs.writeFile(entryImgPath, body, 'binary', err => {
                if (err) return console.error(err);
                let log = 压缩成功 ,
                log += 优化比例: ${ (( 1 - obj.output.ratio) * 100).toFixed(2) }% ,
                log += 原始大小: ${ (obj.input.size / 1024).toFixed(2) }KB ,
                log += 压缩大小: ${ (obj.output.size / 1024).toFixed(2) }KB ,
                log += 文件:${entryImgPath}
                console.log(log);
            });
        });
    });
    req.on('error', e => console.error(e));
    req.end();
}

// node ./tinypng.js -f ./static/smp/m/course_task

`

查看原文

赞 6 收藏 5 评论 7

烈虎 发布了文章 · 2018-06-05

数码照片Exif - Orientation 自动修正解决方案

使用Canvas + exif-js自动修正数码照片

使用场景,在做朋友圈 H5 时,时常遇到需要用户拍照上传图片需求,但是在一些手机(iso)上拍出来的照片会出现奇怪的旋转角度来呈现。经过各种百度才发现相机拍出来的图片拥有很多属性,其中一项是Orientation ,用于记录拍摄时相机物理旋转角度,例如把相机倒过来Orientation3,顺时针竖起来Orientation6,逆时针竖起来Orientation8,正常模式Orientation1。根据这个属性我们可以使用Canvas来对图片重绘。

Orientation 示意图

sadas

Show Code


import EXIF from 'exif-js'; // 引入依赖插件

// 参数列表:img 对象,callback返回Base64图片编码,生成图片质量默认值0.9
export const FixImg = (img, callback, quality = 0.9) => {
    
  let Orientation, ctxWidth, ctxHeight, base64; // 定义所需变量

  EXIF.getData(img, function() {
    Orientation = EXIF.getTag(this, 'Orientation');
    ctxWidth = this.naturalWidth;
    ctxHeight = this.naturalHeight;
    
    console.log(Orientation, ctxWidth, ctxHeight);

    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');

    canvas.width = ctxWidth;
    canvas.height = ctxHeight;
    if ([5, 6, 7, 8].includes(Orientation)) {
      canvas.width = ctxHeight;
      canvas.height = ctxWidth;
    }

    switch (Orientation) {
      case 2:
        ctx.transform(-1, 0, 0, 1, ctxWidth, 0);
        break;
      case 3:
        ctx.transform(-1, 0, 0, -1, ctxWidth, ctxHeight);
        break;
      case 4:
        ctx.transform(1, 0, 0, -1, 0, ctxHeight);
        break;
      case 5:
        ctx.transform(0, 1, 1, 0, 0, 0);
        break;
      case 6:
        ctx.transform(0, 1, -1, 0, ctxHeight, 0);
        break;
      case 7:
        ctx.transform(0, -1, -1, 0, ctxHeight, ctxWidth);
        break;
      case 8:
        ctx.transform(0, -1, 1, 0, 0, ctxWidth);
        break;
      default:
        ctx.transform(1, 0, 0, 1, 0, 0);
    }

    ctx.drawImage(img, 0, 0, ctxWidth, ctxHeight);
    
    // 默认输出jpeg,也可以读取原图片格式,最后输出原图格式,搜索关键词 :File.type
    base64 = canvas.toDataURL('image/jpeg', quality); 
    callback(base64);
  });
};

性感照骗,在线修复: http://peichenhu.cn/demo/awesome/#/Exif


相关补充:从图片 Exif 信息中取到 Orientation 后,就可以根据它来自动旋转图片了,canvas、filter 滤镜、vml、css3 都可以实现图片的旋转。
参考文章:https://imququ.com/post/how-t...

查看原文

赞 8 收藏 7 评论 0

烈虎 发布了文章 · 2018-04-21

JS代码复用模式

复用是一项非常重要的生活技能,因为生命是有限的,无意义的重复等于浪费生命。作为一个程序开发者,代码的复用既是一种能力,也是对积极生活的一种态度。那么JS 在代码复用方面都有哪些方法?
...................................................................................................

构造模式

构造函数与普通函数的唯一区别在于调用方式不同(构造函数首字母大写只是惯例),任何函数都可以用new关键字来作为构造函数调用(构造函数 = new + 普通函数)。

function Parent() {
  this.name = "jim";
  this.say = function() {
    console.log(this.name);
  };
  console.log(this.name);
}
Parent(); // 输出 jim
console.log(Parent); // 输出 Parent (){/* 函数体-略 */}

var child1 = new Parent(); // 输出 jim 构造函数创建 child1 对象(解析执行)
var child2 = new Parent(); // 输出 jim 构造函数创建 child2 对象(解析执行)

console.log(child1); // 输出 Parent {name: "jim", say: ƒ ()}
console.log(child1.say); // 输出 ƒ () {/* 函数体-略 */}

child1.say(); // 输出 jim
child2.say(); // 输出 jim

console.log(child1.name); // 输出 jim (child1 继承了 Parent name)
console.log(child2.name); // 输出 jim (child2 继承了 Parent name)

child1.name = "tom1"; // 修改 child 的 name 属性
child2.name = "tom2"; // 修改 child 的 name 属性

child1.say(); // 输出 tom1(说明 child 本地实例化了name属性 )
child2.say(); // 输出 tom2(说明 child 本地实例化了name属性 )
console.log(child1.name); // 输出 tom1(说明 child 本地实例化了name属性 )
console.log(child2.name); // 输出 tom2(说明 child 本地实例化了name属性 )

delete child1.name; // 删除 child1 的 name 属性
delete child2.name; // 删除 child2 的 name 属性

console.log(child1.name); // 输出 undefined(说明 child1 本地实例化name属性已删除 )
console.log(child2.name); // 输出 undefined(说明 child2 本地实例化name属性已删除 )

Parent(); // 输出 jim (说明构造函数属性 和 构造对象属性 没有关系)

缺点:无法复用父对象属性方法,当子对象数量变多,反复使用 new 重新创建父对象.

原型模式

我们知道所有引用类型都是 Object,也就是说引用类型的原型是 Object,他们是一个继承的关系。另外,原型的属性可以自定义。

function fn() {
  this.keyThis = ["fnThisValue"];
}
// name: "fn" prototype: {constructor: fn()} __proto__: Object
// 函数名是 fn
// 函数 prototype 指向一个对象,该对象的属性constructor 指向函数自身
// 函数 __proto__ 指向 Object(重点 __proto__ 是一个原型引用指针,指向父级原型)
// 此时fn 未执行, this 虽然指向window , 但是 keyThis 并未声明和赋值

// 以上是 JS 内部已经实现好的,下面我们来自定义一个原型属性
fn.prototype.keyProto = ["fnProtoValue"];
console.log(fn.prototype);
// 输出 {keyProto: ["fnProtoValue"], constructor: fn(),__proto__: Object}

var foo = new fn(); // fn() 执行, this指向window,key1声明和赋值
console.log(foo);
// 输出
// fn{
//    keyThis:["fooThisValue"],
//    __proto__:{ keyProto: ["fnProtoValue"], constructor: fn(), __proto__: Object}
// }
// foo 仅仅是一个构造对象(重点对象没有原型属性),原型引用指针__proto__指向 fn 的原型
// 原型链 就是 __proto__:{__proto__:{···}}

console.log(foo.keyThis); // 输出 ["fooThisValue"]
console.log(foo.keyProto); // 输出 ["fnProtoValue"]

foo.keyThis.push("fooThis");
foo.keyProto.push("fooProto");

console.log(foo);
// 输出
// fn{
//    keyThis:["fooThisValue", "fooThis"],
//    __proto__:{ keyProto: ["fnProtoValue", "fooThis"], constructor: fn(), __proto__: Object}
// }
// foo 的原型属性竟然被修改了,这应该不是我们想要的(小本本记下来),所以父级常量最好用 this 来定义

console.log(fn.prototype);
// 输出{ keyProto: ["fnProtoValue", "fooThis"], constructor: fn(), __proto__: Object}

缺点:虽然复用父对象属性方法,当子对象数量变多,反复使用 new 重新创建父对象.

借用模式

JS 基础数据类型操作系列(四)函数 中,我们介绍了 call,apply 和 bind 的函数作用域借用操作,这也是一种代码复用的好方法。

function Parent() {
  this.keyThis = ["fnThisValue"];
}
Parent.prototype.keyProto = ["fnProtoValue"];
function Child() {
  Parent.call(this);
  console.log(this.keyThis); // 输出 ["fnThisValue"]
  console.log(this.keyProto); // 输出 undefined
}
Child();
// 这种借用只能够针对 this 绑定的属性方法起作用。

var jim = new Child();
console.log(jim.keyThis); // 输出 ["fnThisValue"]
console.log(jim.keyProto); // 输出 undefined
// 这种借用只能够针对 this 绑定的属性方法起作用。

代理模式

function inherit(parent, child) {
  var F = function() {};
  F.prototype = parent.prototype;
  child.prototype = new F();
  child.prototype.constructor = child;
}

function Parent() {
  this.keyThis = ["fnThisValue"];
}
Parent.prototype.keyProto = ["fnProtoValue"];

function Child() {}
inherit(Parent, Child);

var jim = new Child();
console.log(jim.keyThis); // 输出 undefined
console.log(jim.keyProto); // 输出 ["fnProtoValue"]

缺点:只是代理了原型

标准模式

在 ES 5 中,提供了Object.create()方法来实现原型构造继承(语法糖)。
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

语法 :Object.create(proto, [propertiesObject]) 。

第二个可选参数是 null 或一个对象,添加到新创建对象的自定义可枚举属性,对应 Object.defineProperties()的第二个参数。

function Parent() {}
Parent.prototype.keyProto = ["fnProtoValue"];

var jim = Object.create(Parent, {
  key: { value: "val" }
});

console.log(jim); // 输出 Function {key: "val",__proto__: Parent()}
jim.hasOwnProperty("key");


var Fn = {
    key:"value"
}
Object.create(Fn)
// {__proto__:{ key:"value"}}

克隆模式

通过复制属性来实现继承

浅克隆

简单对象,单层克隆

function extend(parent, child) {
  var i;
  child = child || {};
  for (i in parent) {
    if (parent.hasOwnProperty(i)) {
      child[i] = parent[i]; // 这里只是引用, 并非实例化
    }
  }
  return child;
}

var Parent = {
    key:"value",
    arr:[1,2,3,4],
    obj:{
        key:"value",
        arr:[1,2,3,4],
    }
}
var kid = extend(Parent)
kid.arr.push(4);
console.log(Parent.arr)  // 输出 [1,2,3,4,4]

深克隆

复杂对象,递归克隆

function extendDeep(parent, child) {
  var i,
    toStr = Object.prototype.toString,
    astr = "[object Array]";
  child = child || {};
  for (i in parent) {
    if (parent.hasOwnProperty(i)) {
      if (typeof parent[i] === "object") {
        child[i] = toStr.call(parent[i]) === astr ? [] : {};
        arguments.callee(parent[i], child[i]);
      } else {
        child[i] = parent[i];
      }
    }
  }
  return child;
}
var Parent = {
    key:"value",
    arr:[1,2,3,4],
    obj:{
        key:"value",
        arr:[1,2,3,4],
    }
}
var kid = extendDeep(Parent)
kid.arr.push(4);
console.log(Parent.arr)  // 输出 [1,2,3,4]

缺点:针对的是对象,不是函数,当然对象用这个是最好的

总结

综上了解,我们想要一个既可以继承this属性,又可以继承prototype属性的方法。继承this属性最好用的是借用模式,继承prototype属性最好用的是Object.create()标准模式。

function parent() {
  this.money = 1000;
}
parent.prototype.say = function(money) {
  console.log("I have " + (this.money + money));
}

function inherit(parent,childParams){
    function Child() {
        parent.call(this);      // 借用 父级 this 属性
    }
    childParams = childParams || {}; // 定义额外参数
    Child.prototype = Object.create(parent.prototype,childParams);
    // parent.prototype 指向原型对象parent Prototype
    // Object.create(parent.prototype)
    // 输出 {__proto__:{ say:ƒ (money),constructor:ƒ parent(), __proto__:Object}}
    Child.prototype.constructor = Child; // 原型的构造函数应该永远指向自身
    return new Child()
}

var jim = inherit(parent);
var tom = inherit(parent,{key:{value:500}});
jim.say(100);   //输出 I have 1100
tom.say(500);   //输出 I have 1100
tom.key         //输出 500
查看原文

赞 1 收藏 5 评论 0

烈虎 发布了文章 · 2018-04-16

Local Storage 操作小插件 iStorage 介绍

iStorage

Local Storage 是 HTML 5 新增的一个本地存储 API,所谓 Local Storage 就是一个小仓库的意思,它有 5 M 的大小空间,存储在浏览器中,我们可以通过 JavaScript 来操纵 Local Storage。

iStorage 介绍

iStorage是针对浏览器 Local Storage 的一个便捷操作插件。它支持用户直接存储获取数字(非 NaN)、字符串、数组、JSON 类型数据。

iStorage 是基于原生 JavaScript 实现的,它编译压缩后仅 2kb 左右,请放心试用。也是作者的第一个 NPM 插件,不足之处还请见谅和指正。

iStorage 安装

iStorage 支持 CommonJSRequireJS<script> 方式引入。

// NPM 安装
npm i istorage

// ES6
import iStorage from "istorage";

// require
var iStorage = require('istorage');

// html
<script data-original="http://peichenhu.cn/doc/lib/iStorage.min.js"></script>

iStorage 使用

iStorage 支持 getLengthgetIndexgetItemsetItemremoveItemclearAll 操作。debug 尾参数可选,用于在控制台打印操作详情日志。

// 获取 Local Storage Length
iStorage.getLength(debug: Boolean);

// 根据 Local Storage 的长度(length)作为索引值,来获取键名
iStorage.getIndex(index:Number, debug: Boolean);

// 依据键名获取值
iStorage.getItem(key: String, debug: Boolean);

// 添加键值对,值类型允许长度为0
iStorage.setItem(key:String, value:!NaN || Number || String || Array || JSON, debug:Boolean)

// 删除键值对
iStorage.removeItem(key: String, debug: Boolean);

// 清楚该域的所有Local Storage 记录
iStorage.clearAll(key: String, debug: Boolean);

iStorage 本地测试

// 请先fork,下载到本地后命令行初始化开发调试环境:
> npm i // 安装 uglify-js 用于压缩生产
> npm test // webpack-dev-server open: http://localhost:9000/

// 若要修改插件,比如 0.0.3 版,请参考以下指令
// 修改未压缩版本 iStorage.js, 修改完后,使用 uglify-js 压缩
> uglifyjs iStorage.js  -m -c -o iStorage.min.js

iStorage 未来

使用 TypeScript 重构,添加 sessionStorage 和 cookie,并希望支持异步操作.

iStorage 更新日志

0.0.1

第一版代码比较粗糙,仅用于验证代码结构和方法的设计、插件的实际用途。基本实现了数字、字符串、数组、JSON 对象四种基本数据的类型检测,直接存储和获取;方法上实现了:

  • check: 检查某个键名是否存在
  • get:获取某个键名的值
  • set:设置键值对
  • remove:根据键名删除某个键值对

详见 redeme@0.0.1

0.0.2

该版本在代码上进行了完善,并提供了更好 debug log 信息。

详见 redeme@0.0.2

0.0.3

此版本改动较大,涉及方法名称的语义化,新方法的添加,check 方法废弃,新增本地 Webpack 调试代码,debug log 信息进一步优化。

查看原文

赞 0 收藏 0 评论 1

烈虎 关注了标签 · 2018-04-16

关注 2

烈虎 关注了标签 · 2018-04-15

tween.js

用js封装的很多的动画效果函数

关注 1

烈虎 关注了标签 · 2018-04-15

vue.js

Reactive Components for Modern Web Interfaces.

Vue.js 是一个用于创建 web 交互界面的。其特点是

  • 简洁 HTML 模板 + JSON 数据,再创建一个 Vue 实例,就这么简单。
  • 数据驱动 自动追踪依赖的模板表达式和计算属性。
  • 组件化 用解耦、可复用的组件来构造界面。
  • 轻量 ~24kb min+gzip,无依赖。
  • 快速 精确有效的异步批量 DOM 更新。
  • 模块友好 通过 NPM 或 Bower 安装,无缝融入你的工作流。

官网:https://vuejs.org
GitHub:https://github.com/vuejs/vue

关注 132991

烈虎 关注了标签 · 2018-04-15

angular.js

AngularJS (Angular.JS) 是一款开源 JavaScript函式库,由Google维护, 众所周知地作为单一页面应用运作协助的。它的目标是增强基于浏览器的应用,并带有MVC模式 (MVC) 功能,这为了使得开发和测试变得更加容易。

外部链接

以上内容来自维基百科

关注 3519

烈虎 收藏了文章 · 2018-03-28

2018前端面试准备

前端面试常见问题按知识点分类整理

前端面试常考问题整理,按模块知识点分类,持续完善中... Front-end-Developer-Questions by Modules and knowledge

前端面试经典问题:CSS 中居中的几种方式

面试中经常遇到的面试题之一,居中布局,特来总结

几个让我印象深刻的面试题 (一)

分享几个我遇到的认为不错的面试题

27款优质简洁的个人简历打包下载

优质简洁的个人简历打包

前端开发面试题总结之——JAVASCRIPT(一)

前端面试系列之 JavaScript,还有两篇

44 个 JavaScript 变态题解析

读者可以先去做一下感受感受. 当初笔者的成绩是 21/44...

当初笔者做这套题的时候不仅怀疑智商, 连人生都开始怀疑了....

不过, 对于基础知识的理解是深入编程的前提. 让我们一起来看看这些变态题到底变态不变态吧!

技术 | 前端面试题(二):自定义事件

我和阿里巴巴的同事守雌将为大家带来一个系列专题:前端面试题解析,一周更新两篇,本篇主要讲如何实现自定义事件。

前端开发面试题总结之——JAVASCRIPT(二)

前端开发面试题总结之—JavaScript,一共三篇

前端开发面试题总结之——HTML

前端开发面试题,本文主要讲述的是 HTML 相关的面试题。面试相关的我整理成收藏集欢迎关注。

收集 JavaScript 各种疑难杂症的问题集锦

关于 JavaScript,工作和学习过程中遇到过许多问题,也解答过许多别人的问题。这篇文章记录了一些有价值的问题。

一道 JS 面试题所引发的 "血案",透过现象寻本质,再从本质看现象

由一道面试题想到拓展的,分析问题的方法

CSS 面试题解答

什么是 CSS reset?CSS 性能优化?浮动的原理和工作方式,会产生什么影响呢,要怎么处理?CSS 权重?

Excuse me?这个前端面试在搞事!

金三银四搞事季,前端这个近年的热门领域,搞事气氛特别强烈,我朋友小伟最近就在疯狂面试,遇到了许多有趣的面试官,有趣的面试题,我来帮这个搞事 boy 转述一下。

前端开发面试题总结之——JAVASCRIPT(三)

前端开发面试题总结之 JavaScript,一共三篇

1月前端面试记

背景 我于16.12.18辞职,之前有过一年左右的前端工作经验。从12月26号开始到1月9号先后面试了微信,百度,阿里巴巴uc,唯品会以及深圳腾讯等几家公司,特此总结与各位共勉。 微信 由于我已经毕业工作过,所以去微信面试是走的社招。微信社招极其严格,共八轮面试,总体来说我基本…

一道面试题引发的对 javascript 类型转换的思考

通过一道面试题,详细描述了 javascript 类型转换的相关内容,对 javascript 进阶有所帮助。

献给前端求职路上的你们(上)

我是一名前端开发,从2016年6月毕业到如今步入工作,期间也面试了一些公司,参考过一些面试文档,学习了一些面试宝典,掌握了一些面试、笔试技巧和经验,所以就总结了一些优质的前端面试题以及面试要点,初学者阅后也要用心钻研其中的原理,重要知识需要系统学习,透彻学习,才能形成自己的知识链,以不变应万变,万不可投机取巧,只求面试过关哦!

征服 JavaScript 面试:什么是闭包

征服 JavaScript 面试:什么是闭包

一个普通本科在校生的前端学习之路

今天我分享的内容主要是关于前端初学者的学习路线和一些建议,还有自己在准备校招过程中的一点经验。

CSS 并不简单 -- 一道微信面试题的实践

本系列会持续分享本人学习到的 CSS 知识点、技巧和效果展示。如有错误,希望您能指出。

从培训班出来之后找工作的经历,教会了我这五件事

这是 Medium 上的一篇文章(已有 5900 个赞),讲的是国外一个培训出来的程序员,用三个月时间,找到了一份年薪 12 万美元的工作,并从中得到的五个忠告的故事。 我觉得他总结得很好,尤其是心态和方法,非常值得学习。对正在找工作的同学非常有用。 想收到更多最新、最专业的前…

最近遇到的前端面试题 (2017.02.23 更新版)

最近整理的前端面试题,希望能对大家有帮助。转载自:http://www.jianshu.com/p/3944...

17 年 2 月面试经验 | _striveg blog

个人面试几家的面试题和一点小感悟

面试 -- 网络 HTTP

现在面试门槛越来越高,很多开发者对于网络知识这块了解的不是很多,遇到这些面试题会手足无措。本篇文章知识主要集中在 HTTP 这块。文中知识来自 《图解 HTTP》与维基百科,若有错误请大家指出。文章会持续更新。 面试 -- 网络 TCP/IP 了解 Web 及网络基础 对端传输…

从一道面试题,到 “我可能看了假源码”

今天想谈谈一道前端面试题

技术 | 前端面试题(一):递归解析

我和阿里巴巴的同事守雌将为大家带来一个系列专题:前端面试题解析,一周更新两篇,也许答案可能不是最优的,但是也可以给你提供解决问题的思路。

谈谈面试与面试题

Winter 对于前端面试的一些看法。

查看原文

烈虎 收藏了文章 · 2018-01-31

Javascript调试命令——你只会Console.log() ?

Javascript调试命令——你只会Console.log() ?

Console 对象提供对浏览器控制台的接入(如:Firefox 的 Web Console)。不同浏览器上它的工作方式是不一样的,但这里会介绍一些大都会提供的接口特性。
Console对象可以在任何全局对象中访问,如 Window,WorkerGlobalScope 以及通过属性工作台提供的特殊定义。
它被浏览器定义为 Window.Console,也可被简单的 Console 调用。

最常用的方法就是Console.log(),就是在控制台输出内容。刚开始学前端的时候看到大家都是用的Console.log(),几乎没有见过Console的其他用法,难道Console真的没有别的用法了?查了一下后发现Console还是非常强大的,至于为什么很少看到有人用可能是因为用过都删掉了吧。在此记录一下Console的其他用法。

注意:因为Console 对象提供对浏览器控制台的接入 所以在不同浏览器中的支持及表现形式可能不太一样,但是调试内容只有我们开发者会看,所以保证开发环境能用这些方法就可以了,下面演示全部都为Chrome上面的效果。

分类输出

不同类别信息的输出

console.log('文字信息');
console.info('提示信息');
console.warn('警告信息');
console.error('错误信息');

1.png

分组输出

使用Console.group()Console.groupEnd()包裹分组内容。

还可以使用Console.groupCollapsed()来代替Console.group()生成折叠的分组。

console.group('第一个组');
    console.log("1-1");
    console.log("1-2");
    console.log("1-3");
console.groupEnd();

console.group('第二个组');
    console.log("2-1");
    console.log("2-2");
    console.log("2-3");
console.groupEnd();

2.png

Console.group()还可以嵌套使用

console.group('第一个组');
    console.group("1-1");
        console.group("1-1-1");
            console.log('内容');
        console.groupEnd();
    console.groupEnd();
    console.group("1-2");
        console.log('内容');
        console.log('内容');
        console.log('内容');
    console.groupEnd();
console.groupEnd();

console.groupCollapsed('第二个组');
    console.group("2-1");
    console.groupEnd();
    console.group("2-2");
    console.groupEnd();
console.groupEnd();

表格输出

使用console.table()可以将传入的对象,或数组以表格形式输出。适合排列整齐的元素

var Obj = {
    Obj1: {
        a: "aaa",
        b: "bbb",
        c: "ccc"
    },
    Obj2: {
        a: "aaa",
        b: "bbb",
        c: "ccc"
    },
    Obj3: {
        a: "aaa",
        b: "bbb",
        c: "ccc"
    },
    Obj4: {
        a: "aaa",
        b: "bbb",
        c: "ccc"
    }
}

console.table(Obj);

var Arr = [
    ["aa","bb","cc"],
    ["dd","ee","ff"],
    ["gg","hh","ii"],
]

console.table(Arr);

查看对象

使用Console.dir()显示一个对象的所有属性和方法
在Chrome中Console.dir()Console.log()效果相同

var CodeDeer = {
    nema: 'CodeDeer',
    blog: 'www.xluos.com',
        
}
console.log("console.dir(CodeDeer)");
console.dir(CodeDeer);

console.log("console.log(CodeDeer)");
console.log(CodeDeer);

查看节点

使用Console.dirxml()显示一个对象的所有属性和方法
在Chrome中Console.dirxml()Console.log()效果相同

百度首页logo的节点信息

条件输出

利用console.assert(),可以进行条件输出。

  • 当第一个参数或返回值为真时,不输出内容
  • 当第一个参数或返回值为假时,输出后面的内容并抛出异常
console.assert(true, "你永远看不见我");
console.assert((function() { return true;})(), "你永远看不见我");

console.assert(false, "你看得见我");
console.assert((function() { return false;})(), "你看得见我");

计次输出

使用Console.count()输出内容和被调用的次数

(function () {
    for(var i = 0; i < 3; i++){
        console.count("运行次数:");
    }
})()

追踪调用堆栈

使用Console.trace()来追踪函数被调用的过程,在复杂项目时调用过程非常多,用这个命令来帮你缕清。

function add(a, b) {
    console.trace("Add function");
    return a + b;
}

function add3(a, b) {
    return add2(a, b);
}

function add2(a, b) {
    return add1(a, b);
}

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

var x = add3(1, 1);

计时功能

使用Console.time()Console.timeEnd()包裹需要计时的代码片段,输出运行这段代码的事件。

  • Console.time()中的参数作为计时器的标识,具有唯一性。
  • Console.timeEnd()中的参数来结束此标识的计时器,并以毫秒为单位返回运行时间。
  • 最多同时运行10000个计时器。
console.time("Chrome中循环1000次的时间");
for(var i = 0; i < 1000; i++)
{

}
console.timeEnd("Chrome中循环1000次的时间");

性能分析

使用Console.profile()Console.profile()进行性能分析,查看代码各部分运行消耗的时间,但是我在Chrome自带的调试工具中并没有找到在哪里查看这两个方法生成的分析报告。应该需要其他的调试工具。

具体参考这里:
http://www.oschina.net/transl...

有趣的Console.log()

最后再来介绍一下强大的Console.log(),这个方法有很多的用法(其他输出方法的用法,如error()等,可以参照log()使用)。

一、提示输出

可以再输出的对象、变量前加上提示信息,增加辨识度

var ans = 12345;
console.log("这是临时变量ans的值:",ans);

二、格式化输出

占位符含义
%s字符串输出
%d or %i整数输出
%f浮点数输出
%o打印javascript对象,可以是整数、字符串以及JSON数据

样例:

var arr = ["小明", "小红"];

console.log("欢迎%s和%s两位新同学",arr[0],arr[1]);

console.log("圆周率整数部分:%d,带上小数是:%f",3.1415,3.1415);

三、自定义样式

使用%c为打印内容定义样式,再输出信息前加上%c,后面写上标准的css样式,就可以为输出的信息添加样式了

console.log("%cMy stylish message", "color: red; font-style: italic");

console.log("%c3D Text", " text-shadow: 0 1px 0 #ccc,0 2px 0 #c9c9c9,0 3px 0 #bbb,0 4px 0 #b9b9b9,0 5px 0 #aaa,0 6px 1px rgba(0,0,0,.1),0 0 5px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.3),0 3px 5px rgba(0,0,0,.2),0 5px 10px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.2),0 20px 20px rgba(0,0,0,.15);font-size:5em");

console.log('%cRainbow Text ', 'background-image:-webkit-gradient( linear, left top, right top, color-stop(0, #f22), color-stop(0.15, #f2f), color-stop(0.3, #22f), color-stop(0.45, #2ff), color-stop(0.6, #2f2),color-stop(0.75, #2f2), color-stop(0.9, #ff2), color-stop(1, #f22) );color:transparent;-webkit-background-clip: text;font-size:5em;');

console.log('%cMy name is classicemi.', 'color: #fff; background: #f40; font-size: 24px;');

总结

Console的用法很多,有些再调试过程中非常实用,可以节省很多时间。当然我知道debug还是用断点调试的方法比较好,但是小问题用“printf大法”也是很好用的(滑稽脸)。

参考资料

  1. https://developer.mozilla.org...
  2. http://www.jb51.net/article/5...
  3. https://segmentfault.com/a/11...
  4. https://www.cnblogs.com/liyun...
查看原文