Git Product home page Git Product logo

myblog's Introduction

Hi there 👋

I am a web developer

  • 🔭 I’m currently working in Meituan
  • 🌱 I Have developed pages such as Free-Try(Overlord meal), Black Pearl, Must-eat List, etc.
  • 😄 Reach me on WeChat

myblog's People

Contributors

simonzhangiter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

myblog's Issues

ng-options和ng-checked在表单中的高级运用

AngularJS是当前非常的流行的前端框架,它的语法糖非常多,也极大的方便了前端开发者,但是有着用法还是需要去琢磨一下的

#ng-options
在select表单控件中,总结一下目前的几种写法。

##普通写法

<select>
	<option value="test1">test1</option>
	<option value="test1">test1</option>
	<option value="test1">test1</option>
	<option value="test1">test1</option>
</select>

优点:简单

缺点:

  • 代码很不简洁,如果选项较多就会很乱
  • 不方便渲染,如果option在变需要使用js动态加载
  • 不方便存储对象

##使用ng-repeat

ng-repeat是angularJS中非常强大的一个directive,在渲染列表上极大的方便了前端开发者,那么由于有多个重复的option,当然可以使用ng-repeat,用法如下:

<select>
	<option ng-repeat="option in options" value="{{option}}">{{option.name}}</option>
</select>
<script>
	$scope.options = [{id:1,name:'test1'},{id:2,name:'test2'},{id:3,name:'test3'}];
</scirpt>

优点:

  • 代码简介
  • 可存储对象,取值方便

缺点:

  • 没有默认显示!,在有些界面需求中,select可能是需要placeholder一样的显示提示效果的,那么使用这个方式显示效果默认是空白
  • 无法通过ng-model来获取当前选择的值

##使用ng-options

这里使用一个年级、班级的选项来作为例子:即选择年级之后再显示对应的可选班级。

<select ng-model="modal.grade" ng-change="modalChangeGrade()" ng-options="grade.gradeText for grade in modal.grades">
   <option value="" disabled>请选择</option>
</select>
<script>
	$scope.modal.grades = [
	{id:1,gradeText:'初一',classes:[]},
	{id:2,gradeText:'初二',classes:[]},
	{id:3,gradeText:'高一'},classes:[]];
	$scope.modalChangeGrade = function(){
		//班级的HTML片段就不在这里写了
		$scope.modal.classes = $scope.modal.grade.classes;
	}
</scirpt>

注:

  • "请选择"的option需要有value,不然会报错
  • 如果要设置默认选择值,比如一开始就选择"高一",则需要设置modal在数组里的对象。
$scope.modal.grade = $scope.modal.grades[2];//高一在数组的位置角标为2

优点:

  • 代码简洁,易于维护
  • 有默认显示
  • 可以使用ng-modal准确获取当前选择的对象

#ng-checked

checkbox和radio是我们经常使用到的表单组件,那么如何使用angularJs简洁方便的获取当前已选择对象呢?

这里只说angularJs的用法:

下面依然以年级和班级为例:

<div ng-repeat="class in grade.classes" ng-click="class.is_checked=!class.is_checked">
   <input type="checkbox" value="" ng-checked="class.is_checked">
   {{class.id+'班'}}
</div>

最后需要查看有哪些checkbox被选中时,只需要遍历$scope.grade.classes数组查看有哪些对象的is_checked属性为true即可。

radio的用法同理。

Gulp实现SASS、HTML的监控和自动编译以及页面的热更新

Gulp是一款当前很流行的前端自动化构建工具

功能

先说明一下本文所要实现的功能:

  • gulp本地服务器
  • 多个sass文件汇总为一个css并压缩
  • 自动监控和编译SASS和HTML文件
  • HTML文件修改后保存则自动刷新页面显示。

配置

首先在项目目录下面创建package.json和gulpfile.js两个文件。

看一下本地的项目目录结构:

package.json

{
    "name": "exam",
    "version": "1.0.0",
    "description": "examWeb",
    "main": "gulpfile.js",
    "dependencies": {},
    "devDependencies": {
        "gulp": "^3.9.0",
        "gulp-connect": "^2.3.1",
        "gulp-sass": "^2.3.2",
        "gulp-rename": "^1.2.2",
        "gulp-minify-css": "^1.2.4",
    },
    "scripts": {
        "test": "test"
    },
    "repository": {
        "type": "git",
        "url": "https://git.coding.net/xxx/xxx.git"
    },
    "keywords": [
        "exam",
        "angular",
        "web",
        "bootStrap"
    ],
    "author": "Simon Zhang",
    "license": "ISC"
}
  • devDependencies就是所需要的插件
  • repository.url是项目的git地址,也可不填

gulpfile.js

备注直接写在文件里了:

var gulp = require('gulp');
var connect = require('gulp-connect');
var sass = require('gulp-sass');
var minifyCss = require('gulp-minify-css');
var rename = require('gulp-rename');
//以上是项目需要引入的插件

var buildConfig = {
    IS_RELEASE_BUILD: true,
    styleDir: 'web/css',//导出的css目录
    htmlDir: 'web/**/*.html',//监控的HTML文件
    watchSass: 'web/**/*.scss'//监控的sass文件  **表示所有
};

gulp.task('server', function() {
    connect.server({
        livereload: true  //实时刷新
    });
});

gulp.task('start', ['server', 'watch-sass', 'watch-html']);
gulp.task('default', ['server', 'watch-sass', 'watch-html']);

gulp.task('watch-sass', ['sass'], function(donw) {
    gulp.watch(buildConfig.watchSass, ['sass']);
    console.log("====== watching hec sass files... =====");
});

gulp.task('sass', function(done) {
    gulp.src("web/exam.scss")
        .pipe(sass())
        .pipe(gulp.dest(buildConfig.styleDir))
        .pipe(minifyCss({
            keepSpecialComments: 0
        }))
        .pipe(rename({
            extname: '.min.css'
        }))
        .pipe(gulp.dest(buildConfig.styleDir))
        .on('end', done);
});

gulp.task('watch-html', function() {
    gulp.watch(buildConfig.htmlDir, ['html']);
});

gulp.task('html', function() {
    gulp.src(buildConfig.htmlDir)
        .pipe(connect.reload());
});

使用

gulp 或者 gulp start

一键开启本地服务器、sass和html的监控以及编译、页面的自动刷新。
Server started http://localhost:8080

gulp server

单独开启本地服务器

gulp watch-sass

单独开启sass监控和自动编译

gulp watch-html

单独开启html的监控和自动编译以及自动刷新服务

使用原生JS封装Tap事件,解决移动端300ms延迟

为了防止误操作,移动端iOS操作系统针对原生click事件做了300ms的延迟,这在一定程度上影响了我们的使用体验。

#FastClick

现在有现成的插件fastclick可以解决这个问题,但是也有弊端:

  • GitHub上最新版本的插件大小为25.4kb,轻量为趋势,能省则省。
  • 它的核心**是取消默认的click时间,判断当前dom节点的类型进行相应的操作,这个判断过程较为繁琐。

#MyTapEvent

本人最近在做微信项目,由于fastclick插件存在一定弊端,因此开发了一个简单的tap事件,主要**有以下几点:

##Thinking

  • 一次tap事件包含touchstart和touchmove(轻微移动)以及touchend三种状态
  • callback方法在touchend后执行
  • 根据chrome浏览器默认的判断取消点击的移动量,手指偏移量(水平或垂直)超过15px则判定为滚动,取消执行tap事件
  • 手指按下时间过长不视为点击,默认时间间隔为500ms
  • 使用HTMLElement来扩充原型,方便添加Event
  • 使用单例模式,确保只加载一次

ok,**定下来,代码写起来就清晰多了:

if (!HTMLElement.prototype.addTapEvent) {
    HTMLElement.prototype.addTapEvent = function(callback) {
        var tapStartTime = 0,
            tapEndTime = 0,
            tapTime = 500, //tap等待时间,在此事件下松开可触发方法
            tapStartClientX = 0,
            tapStartClientY = 0,
            tapEndClientX = 0,
            tapEndClientY = 0,
            tapScollHeight = 15, //水平或垂直方向移动超过15px测判定为取消(根据chrome浏览器默认的判断取消点击的移动量)
            cancleClick = false;
        this.addEventListener('touchstart', function() {
            tapStartTime = event.timeStamp;
            var touch = event.changedTouches[0];
            tapStartClientX = touch.clientX;
            tapStartClientY = touch.clientY;
            cancleClick = false;
        })
        this.addEventListener('touchmove', function() {
            var touch = event.changedTouches[0];
            tapEndClientX = touch.clientX;
            tapEndClientY = touch.clientY;
            if ((Math.abs(tapEndClientX - tapStartClientX) > tapScollHeight) || (Math.abs(tapEndClientY - tapStartClientY) > tapScollHeight)) {
                cancleClick = true;
            }
        })
        this.addEventListener('touchend', function() {
            tapEndTime = event.timeStamp;
            if (!cancleClick && (tapEndTime - tapStartTime) <= tapTime) {
                callback();
            }
        })
    }
}

##Usage

HTMLElement.addTapEvent(function(){
	//do something...
})
如:
document.querySelect('#test').addTapEvent(function(){
	alert('this is a tap event');
})

##Case

这里给一个移动端案例,同时也包含了闭包的知识,前20项为tap事件,后30项为click事件,大家可以在手机上试一下效果,感受一下两种方法的差别:

<style media="screen">
    li {
        padding: 20px;
    }

</style>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta name="apple-mobile-web-app-capable" content="yes">
        <meta name="apple-touch-fullscreen" content="yes">
        <meta name="apple-mobile-web-app-status-bar-style" content="black">
        <meta name="format-detection" content="telephone=no,email=no">
        <meta name="x5-orientation" content="portrait">
        <title>test</title>
    </head>
    <body>
        <ul></ul>
    </body>
    <script type="text/javascript">
        if (!HTMLElement.prototype.addTapEvent) {
            HTMLElement.prototype.addTapEvent = function(callback) {
                var tapStartTime = 0,
                    tapEndTime = 0,
                    tapTime = 500, //tap等待时间,在此事件下松开可触发方法
                    tapStartClientX = 0,
                    tapStartClientY = 0,
                    tapEndClientX = 0,
                    tapEndClientY = 0,
                    tapScollHeight = 15, //水平或垂直方向移动超过15px测判定为取消(根据chrome浏览器默认的判断取消点击的移动量)
                    cancleClick = false;
                this.addEventListener('touchstart', function() {
                    tapStartTime = event.timeStamp;
                    var touch = event.changedTouches[0];
                    tapStartClientX = touch.clientX;
                    tapStartClientY = touch.clientY;
                    cancleClick = false;
                })
                this.addEventListener('touchmove', function() {
                    var touch = event.changedTouches[0];
                    tapEndClientX = touch.clientX;
                    tapEndClientY = touch.clientY;
                    if ((Math.abs(tapEndClientX - tapStartClientX) > tapScollHeight) || (Math.abs(tapEndClientY - tapStartClientY) > tapScollHeight)) {
                        cancleClick = true;
                    }
                })
                this.addEventListener('touchend', function() {
                    tapEndTime = event.timeStamp;
                    if (!cancleClick && (tapEndTime - tapStartTime) <= tapTime) {
                        callback();
                    }
                })
            }
        }

        var ul = document.querySelector('ul');
        for (var i = 1; i <= 20; i++) {
            var li = document.createElement('li');
            li.innerHTML = i;
            li.addTapEvent(function() {
                var x = i;
                return function() {
                    alert(x);
                }
            }())
            ul.appendChild(li);
        }
        for (var j = 21; j <= 50; j++) {
            var li = document.createElement('li');
            li.innerHTML = j;
            li.onclick = function() {
                var x = j;
                return function() {
                    alert(x);
                }
            }()
            ul.appendChild(li);
        }
    </script>
</html>

Vue2+Echarts实现多种图表数据可视化Dashboard详解

数据可视化

将数据通过图表的形式展现出来将大大的提升可读性和阅读效率

本例包含柱状图、折线图、散点图、热力图、复杂柱状图、预览面板等

后续会有新的版本,欢迎大家关注

技术栈

  • vue2.x
  • vuex 存储公共变量,如色值等
  • vue-router 路由
  • element-ui 饿了么基于vue2开发组件库,本例使用了其中的datePicker
  • echarts 一款丰富的图表库
  • webpack、ES6、Babel、Stylus...

GitHub地址:https://github.com/SimonZhangITer/DataVisualization

演示

此项目为PC端数据可视化,请在电脑端查看

项目截图

开发

组件化

本项目完全采用组件化的**进行开发。使用vue-router作为路由,每个页面都是一个组件,每个组件里又包含多个组件。可以看到,多种图表的标题和日期筛选等都是类似的格式,所以这两个都分别做成了组件。

除此之外,在筛选产品的时候用到的checkbox也被我写成了组件,有需要的朋友也可以剥离出去单独使用(不过写的比较粗糙~)

每个图表都是一个单独的组件,也可以单独的剥离出去使用。

柱状图

本项目包含多种图表,这里挑“柱状图”说一说,其他的图标实现方式类似。

<template>
<div class="multipleColumn">
  <v-header :name="name" :legendArr="legendArr" :myChart="myChart"></v-header>
  <v-filter :myChart="myChart" v-if="myChart._dom"></v-filter>
  <div class="main"></div>
</div>
</template>

页面HTML可以看到,一个完整的图标是由三个部分组成:

v-header

头组件,存放标题以及不同类型的筛选等

  • name 图表的标题
  • legendArr 图表所包含的多种类型
  • myChart 当前图表对象

v-filter

筛选组件,日期的筛选以及不同产品的筛选

  • myChart 当前图表对象

v-if="myChart._dom"表示在当前图表dom元素渲染完成之后再渲染filter组件

main

图表的主体文件

需要注意的是:main必须要指定宽高,否则echarts无法渲染dom

初始化

初始化需要在vue的mounted()方法里执行,因为这里能保证当前的页面元素都已经被加载完成。

mounted() {
  // 基于准备好的dom,初始化echarts实例
  this.myChart = echarts.init(document.querySelector('.multipleColumn .main'))
  this.myChart.setOption(this.options) //this.options为echarts的配置,详情可去我的gitHub查看
}

如果要在created()方法里执行,则需要另外加上

this.$nextTick(() => {
  this._init()
})

DashBoard

dashboard是一个所有图表的预览,并且有一个点击切换的动画效果,这里大概讲解一个实现方式。

html

<template lang="html">
  <div class="dashboard">
    <div class="flex-container column">
        <div class="item one" @click="clickChart('1')" style="transform: translate(-22.4%,-33.5%) scale(0.33)">
          <multipleColumn></multipleColumn>
        </div>
        <div class="item two" @click="clickChart('2')" style="transform: translate(-22.4%,0.5%) scale(0.33)">
          <column></column>
        </div>
        <div class="item three" @click="clickChart('3')" style="transform: translate(-22.4%,34.5%) scale(0.33)">
          <v-line></v-line>
        </div>
        <div class="item four active" @click="clickChart('4')" style="transform: translate(43.7%, 0) scale(1)">
          <point></point>
        </div>
    </div>
  </div>
</template>

可以看到,这里是设置了四张图表的Wrapper,每个Wrapper里面装一个图表组件。通过动态的改变style样式来切换。

整体的**为:

  • 使用百分比布局,这样才能在不能大小的屏幕做到自适应
  • 确定图表显示比例,长宽比
  • 只做一个transform变换,这样才能提高性能

性能

关于性能方面,这里多说一句:

相信大家都看过在线演示的demo了,不同图表间的切换不仅有位置的变换,还有大小的变换。那么大小的变换大家都知道是用transform的scale变换,但是位置的变换呢,使用left、top?

很显然这样是不对的,但是这样确实也能实现效果,但是会非常的消耗性能。一个CSS属性的变化就相当于一个线程,那么如果使用了left、top以及transform的话就是三个线程同时开启,那你的电脑温度将会很快飙升的

正确的解决方案还是继续使用transform,使用它的 translate ,如:

transform: translate(-22.4%,0) scale(0.33)

结语

这个项目还是挺实用的一个项目,较好的运用了vue的组件化**。

大家感兴趣的可以去看看代码,希望对大家有帮助。

Build Setup

# install dependencies
npm install

# serve with hot reload at localhost:8080
npm run dev

# build for production with minification
npm run build

交流

欢迎热爱学习、忠于分享的胖友一起来交流

  • QQ:745913574

  • QQ群:149683643

  • WeChat: C.a.l.m

Donation

If you find this project useful, you can buy me a cup of coffee

JavaScript设计模式——策略模式

#javaScript设计模式——策略模式

策略模式(Strategy):将定义的一组算法封装起来,使其相互之间可以替换。封装的算法具有一定独立性,不会随客户端变化而变化。

##商品促销问题

问题描述:超时年底促销,部分商品5折销售,部分9折,普通用户满100返30,高级VIP用户满100返50...

对于前端,一般的处理方式可能是写多个方法,针对不同的优惠策略选择不同的方法来处理。如:

//100返30
function return30(price) {
    //dosomething
}
//100返50
function return50(price) {
    //dosomething
}
//9折
function percent90(price) {
    //dosomething
}
...

这里一个促销策略对应一个方法显得过于冗余,并且很不方便管理,代码阅读性较差。可能有的胖友会想到把这些都封装为一个方法,然后使用if或者switch语句来判断状态并返回策略,如:

function priceStrategy(algorithm,price) {
    switch (algorithm) {
        case 'return30':
            //dosomething
            break;
        case 'return50':
            //dosomething
            break;
        case 'percent90':
            //dosomething
            break;
    }
}
//调用方式
priceStrategy('return30',999);

这种方式较上一种已经好了很多,在策略的封装以及可读性提高了许多。但是这种方式在寻找策略的时候是从上到下一个一个的找,这样如果方法多了的话,多少还是会影响一些效率的,所以我们采用对象的方法,使用键值对,直接找到对应的策略方法:

//价格策略对象
var priceStrategy = function() {
    var strategy {
        return30: function(price) {
            //do something
        },
        return50: function(price) {
            //do something
        },
        percent90: function(price) {
            //do something
        },
        percent50: function(price) {
            //do something
        }
    }
    //算法调用接口
    return function(algorithm, price) {
        //如果算法存在,则调用算法,否则返回false
        return strategy[algorithm] && strategy[algorithm](price);
    }
}();

总的来说,策略模式在平常的开发中还是比较常用的一个设计模式,希望对大家有帮助。

TypeScript+Vue实例教程

功能

  • 轮播
  • 搜索
  • 列表
  • 懒加载
  • 简单动画
  • loading
  • vue-router.ts
  • vuex.ts
  • vue-class-component使用
  • vuex-class使用
  • xxx.d.ts声明文件
  • 基于类的编写方式
  • mock数据
  • tsconfig.json
  • webpack配置
  • vue-typescript-cli

完成后的简单例子

基于类的写法加上静态类型检查,简直不能再嗨

<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";
import { State } from "vuex-class";

@Component
export default class Shops extends Vue {
  @State shops: StoreState.shop[];
  @State searchVal: string;

  get shopList(): StoreState.shop[] {
    const shops = this.shops;
    const searchVal = this.searchVal;
    return shops.filter(
      (el: StoreState.shop) => el.shopName.indexOf(searchVal) > -1
    );
  }
}
</script>

为什么使用TypeScript

1. JavaScript的超集

支持所有原生JavaScript的语法

2. 强类型语言

现在很多主流语言都是强类型的,而这点也一直是JavaScript所被人诟病的地方。使用TypeScript之后,将会在代码调试、重构等步骤节省很多时间。

比如说:函数在返回值的时候可能经过复杂的操作,那我们如果想要知道这个值的结构就需要去仔细阅读这段代码。那如果有了TypeScript之后,直接就可以看到函数的返回值结构,将会非常的方便

3. 强大的IDE支持

现在的主流编辑器如VSCodeWebStormAtomSublime等都对TypeScript有着非常友好的支持,主要体现在智能提示上,非常的方便

4. 可运行于任何浏览器、计算机、操作系统

强大的编译引擎

5. 迭代更新快

不断更新,提供更加方便友好的Api

6. 微软和Google爸爸

TypeScript是微软开发的语言,而Google的Angular使用的就是TypeScript,所以不用担心会停止维护,至少在近几年内TypeScript都会一门主流开发语言

7. npm下载量非常高

截止2017.12.17, TypeScript在全球范围内的npm日均下载量在30w左右,这个数字将近是vue下载量的10倍,可见TypeScript还是非常受欢迎的

Vue-TypeScript-Cli

官方虽然明确提出对TypeScript的支持,但是并没有明确的配置文档,自己在配置的时候还是需要查阅很多资料以及踩很多坑的(这个过程真的很蓝瘦-_-)

但是现在可以不用踩这个坑啦,我基于官方的vue-cli写了一个vue-typescript-cli,可以一键构建TypeScript模板

用法

vue init SimonZhangITer/vue-typescript-template <project-name>

比如

vue init SimonZhangITer/vue-typescript-template my-project

然后配置好的TypeScript模板就下载到./my-project文件夹了,npm run dev即可运行

TypeScript配置

这里记录一下当时的踩坑过程,所有配置已经在vue-typescript-template配置完毕

1. Webpack

安装ts-loader

首先需要安装ts-loader,这是TypeScript为Webpack提供的编译器,类似于babel-loader

npm i ts-loader -D

配置rules

接着在Webpack的module.rules里面添加对ts的支持(我这里的webpack版本是2.x):

{
    test: /\.vue$/,
    loader: 'vue-loader',
    options: vueLoaderConfig
},
{
    test: /\.ts$/,
    loader: 'ts-loader',
    options: {
      appendTsSuffixTo: [/\.vue$/],
    }
}

配置extensions

添加可识别文件后缀对ts的支持,如:

extensions: ['.js', '.vue', '.json', '.ts']

2. tsconfig.json

创建tsconfig.json文件,放在根目录下,和package.json同级

配置内容主要也看个人需求,具体可以去typescript的官网查看,但是有一点需要注意:

在Vue中,你需要引入 strict: true (或者至少 noImplicitThis: true,这是 strict 模式的一部分) 以利用组件方法中 this 的类型检查,否则它会始终被看作 any 类型。

这里列出我的配置,功能在注释中给出

{
  "include": [
    "src/*",
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ],
  "compilerOptions": {
    // types option has been previously configured
    "types": [
      // add node as an option
      "node"
    ],
    // typeRoots option has been previously configured
    "typeRoots": [
      // add path to @types
      "node_modules/@types"
    ],
    // 以严格模式解析
    "strict": true,
    // 在.tsx文件里支持JSX
    "jsx": "preserve",
    // 使用的JSX工厂函数
    "jsxFactory": "h",
    // 允许从没有设置默认导出的模块中默认导入
    "allowSyntheticDefaultImports": true,
    // 启用装饰器
    "experimentalDecorators": true,
    "strictFunctionTypes": false,
    // 允许编译javascript文件
    "allowJs": true,
    // 采用的模块系统
    "module": "esnext",
    // 编译输出目标 ES 版本
    "target": "es5",
    // 如何处理模块
    "moduleResolution": "node",
    // 在表达式和声明上有隐含的any类型时报错
    "noImplicitAny": true,
    "lib": [
      "dom",
      "es5",
      "es6",
      "es7",
      "es2015.promise"
    ],
    "sourceMap": true,
    "pretty": true
  }
}

3. 修改main.js

  1. 把项目主文件main.js修改成main.ts,里面的写法基本不变,但是有一点需要注意:
    引入Vue文件的时候需要加上.vue后缀,否则编辑器识别不到

  2. 把webpack的entry文件也修改成main.ts

4. vue-shims.d.ts

TypeScript并不支持Vue文件,所以需要告诉TypeScript*.vue文件交给vue编辑器来处理。解决方案就是在创建一个vue-shims.d.ts文件,建议放在src目录下再创建一个typings文件夹,把这个声明文件放进去,如:src/typings/vue-shims.d.ts,文件内容:

*.d.ts类型文件不需要手动引入,TypeScript会自动加载

declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}

到这里TypeScript在Vue中配置就完成了,可以愉快的撸代码了~

第三方插件库

现在Vue官方已经明确提出支持TypeScript,并考虑出一个对应的vue-cli,在这之前,Vue开发团队已经开发出了一些插件库来支持TypeScript,这里简单和大家介绍一下。

Vue-Class-Component

vue-class-component是官方维护的TypeScript装饰器,写法比较扁平化。Vue对其做到完美兼容,如果你在声明组件时更喜欢基于类的 API,这个库一定不要错过

ps:用了这个装饰器之后写方法不需要额外加逗号,贼嗨~~~

import Vue from "vue";
import Component from "vue-class-component";

@Component
export default class App extends Vue {
  name:string = 'Simon Zhang'

  // computed
  get MyName():string {
    return `My name is ${this.name}`
  }

  // methods
  sayHello():void {
    alert(`Hello ${this.name}`)
  }

  mounted() {
    this.sayHello();
  }
}

这个代码如果用原生Vue语法来写的话就是这样:

export default {
  data () {
    return {
      name: 'Simon Zhang'
    }
  },

  mounted () {
    this.sayHello()
  },

  computed: {
    MyName() {
      return `My name is ${this.name}`
    }
  },

  methods: {
    sayHello() {
      alert(`Hello ${this.name}`)
    },
  }
}

Vuex-Class

vuex-class是基于基于vue-class-component对Vuex提供的装饰器。它的作者同时也是vue-class-component的主要贡献者,质量还是有保证的。

import Vue from "vue";
import Component from "vue-class-component";
import { State, Action, Getter } from "vuex-class";

@Component
export default class App extends Vue {
  name:string = 'Simon Zhang'
  @State login: boolean;
  @Action initAjax: () => void;
  @Getter load: boolean;

  get isLogin(): boolean {
    return this.login;
  }

  mounted() {
    this.initAjax();
  }
}

上面的代码就相当于:

export default {
  data() {
    return {
      name: 'Simon Zhang'
    }
  },

  mounted() {
    this.initAjax()
  },

  computed: {
    login() {
      return this.$store.state.login
    },
    load() {
      return this.$store.getters.load
    }
  },

  methods: {
    initAjax() {
      this.$store.dispatch('initAjax')
    }
  }
}

Vue-Property-Decorator

vue-property-decorator 是在 vue-class-component 上增强了更多的结合 Vue 特性的装饰器,新增了这 7 个装饰器

  • @Emit
  • @Inject
  • @Model
  • @Prop
  • @Provide
  • @Watch
  • @Component (从 vue-class-component 继承)

引入部分第三方库的时候需要额外声明文件

比如说我想引入vue-lazyload,虽然已经在本地安装,但是typescript还是提示找不到模块。原因是typescript是从node_modules/@types目录下去找模块声明,有些库并没有提供typescript的声明文件,所以就需要自己去添加

解决办法:在src/typings目前下建一个tools.d.ts文件,声明这个模块即可

declare module 'vue-awesome-swiper' {
  export const swiper: any
  export const swiperSlide: any
}

declare module 'vue-lazyload'

对vuex的支持不是很好

在TypeScript里面使用不了mapState、mapGetters等方法,只能一个变量一个变量的去引用,这个要麻烦不少。不过使用vuex-class库之后,写法上也还算简洁美观

export default class modules extends Vue {
  @State login: boolean; // 对应this.$store.state.login
  @State headline: StoreState.headline[]; // 对应this.$store.state.headline

  private swiperOption: Object = {
    autoplay: true,
    loop: true,
    direction: "vertical"
  };

  logoClick(): void {
    alert("点我干嘛");
  }
}

项目截图

总结

TypeScript还是非常值得学习和使用一个语言,还是有很多优点的

欢迎大家对我的项目提建议,欢迎Star~

QQ交流群:323743292

Build Setup

# 安装依赖
npm install

# 启动项目
npm run dev

# 打包项目
npm run build

使用vue2+Vuex+Router写的Demo以及遇到的一些坑和使用建议

一直对vue很感兴趣,最近使用vue2.0开发了高仿饿了么点餐系统来练练手,不得不说vue真是一个很不错的框架,但是也遇到过一些坑,在这里和大家分享一下

饿了么点餐系统

vue2.0、vuex、vue-router、axios、webpack、eslint、better-scroll

演示

在线演示戳我(请使用chrome开发者手机演示模式预览)

移动端演示

扫二维码在手机上查看效果更好

组件

  • 购物车
  • 购买物品小球飞入动画
  • 评价star组件
  • 商品添加、删除组件
  • 优惠图标组件
  • 目录、列表联动滚动
  • 画廊
  • 评论的是否满意和内容筛选
  • 商品列表页面
  • 店铺评价页面
  • 商家介绍页面
  • 优惠活动页面
  • 商品详情页面

构建

vue有自己的脚手架构建工具vue-cli,使用起来非常方便,使用webpack来集成各种开发便捷工具,比如:

  • 代码热更新,修改代码之后网页无刷新改变,对前端开发来说非常的方便
  • PostCss,再也不用去管兼容性的问题了,只针对chrome写css代码,会自动编译生成支持多款浏览器的css代码
  • Eslint,统一代码风格,规避低级错误,对于有代码洁癖的人来说是绝对的好东西,不过有些地方的代码校验有时候也挺麻烦的-.-
  • bable,ES2015出来已经有一段时间了,但是不少浏览器还没有兼容ES6.有了bable,放心使用ES6语法,它会自动转义成ES5语法。
  • Stylus,类似于SASS/SCSS,但是可以不写{}和“:”,使用起来还是很方便的
  • ...

除此之外,vue-cli已经使用node配置了一套本地服务器和安装命令等,本地运行和打包只需要一个命令就可以搞定,非常的方便

开发

vue非常好的融合了react的组件化**和angular的指令**。
一个vue的组件将HTML、CSS、JS代码写在一个文件里面,这样既方便编写,也方便管理和修改

Axios

在vue1.x的时候,vue的官方推荐HTTP请求工具是vue-resource,但是在vue2.0的时候将推荐工具改成了axios。

使用方式都差不多,但需要注意的是:接口返回的res并不直接是返回的数据,而是经过axios本身处理过的json对象。真正的数据在res.data里:

axios.get(url).then((res)=>{
  this.data = res.data
})

Vuex

vue提供了一个数据管理工具vuex,有点类似于angular中factory和service,可以进行数据上的通信。
比如存储一些公共变量或者是不同组件间的数据处理等。

这个有一些高级用法在这里不细说,想要了解的可以去官方文档看,有中文版本。

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  }
})

Vue-Router

vue-router是vue的路由系统,可以用来创建单页应用。基本**是在主页面中引入标签,然后定义路由,把router挂在到app上,然后把各个子页面渲染到view里面。使用起来还是很方便的,
跳转页面只需要

router.push('test')

获取元素节点

vue2.0废除了v-el指令,所有的节点指令修改为ref,然后通过ref来获取元素节点,如

<div ref="testHook">test</div>
...js code
this.$ref.testHook

组件间的通信

一。如果是和子组件通信,则使用ref就可以实现,如:

<test ref="testHook"></test>
...js code
this.$ref.testHook.add() //调用test子组件的add方法

二。使用emit来发送广播

vue2提供了一套广播机制,即一边发送广播,一边接收广播来执行相应操作。使用方法如下:

比如想要给test组件发送一个“相加”广播:

export default {
  method:{
  	click(){
  	  Vue.$emit('add',{}) //第二个参数可作为传递数据传送到监听端口,不需要则传空对象
  	}
  }
}

那么test组件中就需要监听,在created方法里写

export default {
  created(){
   Vue.$on('add',this.add)
  },
  method:{
  	add(){
  	  this.count++
  	}
  }
}

除了以上总结的这点小的知识点以外,还有很多vue的知识想要和大家分享,以后会陆续写出来,大家感兴趣的也可以来我的GitHub一起来写这个项目(觉得不错的给个star Hah)

安装步骤

# install dependencies
npm install

# serve with hot reload at localhost:8080
npm run dev

# build for production with minification
npm run build

项目截图

    

交流

欢迎热爱学习、忠于分享的胖友一起来交流

  • QQ:745913574

  • QQ群:149683643

Donation

If you find this project useful, you can buy me a cup of coffee

This其实不难,通过实例全面解析JS中的This

this的指向问题应该是让每一个前端er都头疼的问题,我也一样,曾经遇到甚至都是一顿乱猜。最近在研读一些书籍如《你不知道的JavaScript》和《JavaScript语言精粹与编程实践》,让我对this的问题豁然开朗。故写下此篇文章,分享一下我的心得。

隐式绑定

关于this,一般来说,谁调用了方法,该方法的this就指向谁,如:

function foo(){
	console.log(this.a)
}

var a = 3;

var obj = {
	a: 2,
	foo: foo
};

obj.foo(); // 输出2,因为是obj调用的foo,所以foo的this指向了obj,而obj.a = 2

如果存在多次调用,对象属性引用链只有上一层或者说最后一层在调用位置中起作用,如:

function foo() {
    console.log( this.a )
}

var obj2 = { 
    a: 42,
    foo: foo
}

var obj1 = {
    a: 2,
    obj2: obj2
}

obj1.obj2.foo(); // 42

隐式丢失

一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说他回应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式。

function foo() {
    console.log( this.a )
}

var obj1 = {
    a: 2,
    foo: foo
}

var bar = obj1.foo; // 函数别名!

var a = "oops, global"; // a是全局对象的属性

bar(); // "oops, global"

虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定

一个更微妙、更常见并且更出乎意料的情况发生在传入回调函数时

function foo() {
    console.log( this.a )
}

function doFoo( fn ){
    // fn 其实引用的是 foo
    fn(); // <-- 调用位置!
}

var obj = {
    a: 2,
    foo: foo
}

var a = "oops, global"; // a是全局对象的属性

doFoo( obj.foo ); // "oops, global"

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样,如果把函数传入语言内置的函数而不是传入自己声明的函数(如setTimeout等),结果也是一样的

显式绑定

简单的说,就是指定this,如:call、apply、bind、new绑定等

硬绑定

function foo( something ) {
    console.log( this.a, something)
    return this.a + something
}

var obj = {
    a: 2
}

var bar = function() {
    return foo.apply( obj, arguments)
}

var b = bar(3); // 2 3
console.log(b); // 5

这里简单做一下解释:
在bar函数中,foo使用apply函数绑定了obj,也就是说foo中的this将指向obj,与此同时,使用arguments(不限制传入参数的数量)作为参数传入foo函数中;所以在运行bar(3)的时候,首先输出obj.a也就是2和传入的3,然后foo返回了两者的相加值,所以b的值为5

同样,本例也可以使用bind:

function foo( something ) {
    console.log( this.a, something)
    return this.a + something
}

var obj = {
    a: 2
}

var bar = foo.bind(obj)

var b = bar(3); // 2 3
console.log(b); // 5

new绑定

在传统面向类的语言中,使用new初始化类的时候会调用类中的构造函数,但是JS中new的机制实际上和面向类和语言完全不同。

使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:

  • 创建(或者说构造)一个全新的对象
  • 这个新对象会被执行[[Prototype]]连接
  • 这个新对象会绑定到函数调用的this
  • 如果函数没有返回其他对象,那么new表达式中的函数会自动返回这个新对象
    如:
function foo(a){
    this.a = a
}

var bar = new foo(2);
console.log(bar.a); // 2

使用new来调用foo(...)时,我们会构造一个新对象并把它绑定到foo(...)调用中的this上。new是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。

this的优先级

毫无疑问,默认绑定的优先级是四条规则中最低的,所以我们可以先不考虑它。

隐式绑定和显式绑定哪个优先级更高?我们来测试一下:

function foo(a){
    console.log(this.a)
}

var obj1 = {
    a: 2,
    foo: foo
}

var obj2 = {
    a: 3,
    foo: foo
}

obj1.foo(); // 2
obj2.foo(); // 3

obj1.foo.call(obj2); // 3
obj2.foo.call(obj1); // 2

可以看到,显式绑定优先级更高,也就是说在判断时应当先考虑是否可以存在显式绑定。

现在我们要搞清楚new绑定隐式绑定的优先级谁高谁低 :

function foo(a){
    this.a = something
}

var obj1 = {
    foo: foo
}

var obj2 = {}

obj1.foo(2); 
console.log(obj1.a); // 2

obj1.foo.call(obj2,3);
console.log(obj2.a); // 3

var bar = new obj1.foo(4)
console.log(obj1.a); // 2
console.log(bar.a); // 4

可以看到new绑定隐式绑定优先级高。但是new绑定显式绑定谁的优先级更高呢?

function foo(something){
    this.a = something
}

var obj1 = {}

var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2

var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3

可以看到,new绑定修改了硬绑定中的this,所以new绑定的优先级比显式绑定更高。

之所以要在new中使用硬绑定函数,主要目的是预先设置函数的一些参数,这样在使用new进行初始化时就可以只传入其余的参数。bind(...)的功能之一就是可以把除了第一个参数(第一个参数用于绑定this)之外的其他参数都传给下层的函数(这种技术称为“部分应用”,是“柯里化”的一种)。举例来说:

function foo(p1,p2){
    this.val = p1 + p2;
}

// 之所以使用null是因为在本例中我们并不关心硬绑定的this是什么
// 反正使用new时this会被修改
var bar = foo.bind(null,'p1');

var baz = new bar('p2');

baz.val; // p1p2
}

柯里化:在直觉上,柯里化声称“如果你固定某些参数,你将得到接受余下参数的一个函数”。所以对于有两个变量的函数yx,如果固定了 y = 2,则得到有一个变量的函数 2x

This在箭头函数中的应用

箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。

我们来看一下箭头函数的词法作用域:

function foo() {
	// 返回一个箭头函数
	return (a) => {
		// this继承自foo()
		console.log(this.a)
	};
}

var obj1 = {
	a: 2
};

var obj2 = {
	a: 3
};

var bar = foo.call(obj1);
bar.call(obj2); // 2, 不是3!

foo()内部创建的箭头函数会捕获调用时foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new也不行!)

总结

如果要判断一个运行中的函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断this的绑定对象。

  1. 由new调用?绑定到新创建的对象。
  2. 由call或者apply(或者bind)调用?绑定到指定的对象。
  3. 由上下文对象调用?绑定到那个上下文对象。
  4. 默认:在严格模式下绑定到undefined,否则绑定到全局对象。

如果你觉得有帮助,欢迎Star^_^

从4个方面优化你的Vue项目

运行时优化

1、使用v-if代替v-show

两者的区别是:v-if不渲染DOM,v-show会预渲染DOM

除以下情况使用v-show,其他情况尽量使用v-if

  • 有预渲染需求

  • 需要频繁切换显示状态

2、v-for必须加上key,并避免同时使用v-if

一般我们在两种常见的情况下会倾向于这样做:

  • 为了过滤一个列表中的项目
    比如 v-for="user in users" v-if="user.isActive"。在这种情形下,请将 users替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表

  • 为了避免渲染本应该被隐藏的列表
    比如 v-for="user in users" v-if="shouldShowUsers"。这种情形下,请将 v-if 移动至容器元素上 (比如 ul, ol)

3、事件及时销毁

Vue组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。

也就是说,在js内使用addEventListener等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露,如:

created() {
  addEventListener('touchmove', this.touchmove, false)
},
beforeDestroy() {
  removeEventListener('touchmove', this.touchmove, false)
}

首屏优化

1、图片裁剪、使用webp

  • 图片需要裁剪,一般使用二倍图即可

  • 尽量使用webp图片

  • 如果使用了vue-lazyload插件,可以使用以下方法一键替换webp(替换使用v-lazy指令的图片)

Vue.use(VueLazyload, {
  error: require('./assets/img/defaultpic_small.png'),
  filter: {
    webp (listener: any, options: any) {
      if (!options.supportWebp) return
      // listener.src += '.webp'
    }
  }
});

2、资源提前请求

经测试,Vue项目中各文件的加载顺序为:router.js、main.js、App.vue、[page].vue、[component].vue,如图:
image

其中,router的加载时间相比于page.vue快近100ms,如果page.vue的文件较多,时间差异会更大
所以,可以在页面挂载、渲染的同时去请求接口数据,如在router.js中请求数据:

import Router from 'vue-router'
import store from './store'

store.dispatch('initAjax')

3、异步路由

使用异步路由可以根据URL自动加载所需页面的资源,并且不会造成页面阻塞,较适用于移动端页面

建议主页面直接import,非主页面使用异步路由

使用方式:

{
  path: '/order',
  component: () => import('./views/order.vue')
}

4、异步组件

不需要首屏加载的组件都使用异步组件的方式来加载(如多tab),包括需要触发条件的动作也使用异步组件(如弹窗)
使用方式为:v-if来控制显示时机,引入组件的Promise即可

<template>
  <div>
    <HellowWorld v-if="showHello" />
  </div>
</template>
<script>
export default {
  components: { HellowWorld: () => import('../components/HelloWorld.vue') },
  data() {
    return {
      showHello: false
    }
  },
  methods: {
    initAsync() {
      addEventListener('scroll', (e) => {
        if (scrollY > 100) {
          this.showHello = true
        }
      })
    }
  }
}
</script>

5、使用轻量级插件、异步插件

  • 使用webpack-bundle-analyzer查看项目所有包的体积大小,较大的插件包尽量寻找轻量级的替代方案

  • 首屏用不到的插件、或只在特定场景才会用到的插件使用异步加载(如定位插件,部分情况可以通过URL传递经纬度;或生成画报插件,需要在点击时触发);插件第一次加载后缓存在本地,使用方式为:

// 以定位插件为例
const latitude = getUrlParam('latitude')
const longitude = getUrlParam('longitude')
// 如果没有经纬度参数,则使用定位插件来获取经纬度
if (!latitude || !longitude) {
  // 首次加载定位插件
  // webpack4写法,若使用webpack3及以下,则await import('locationPlugin')即可
  if (!this.WhereAmI) this.WhereAmI = (await import('locationPlugin')).default
  // do sth...
}

6、公用CDN

使用公用的CDN资源,可以起到缓存作用,并减少打包体积

网络优化

1、减少网络请求

浏览器对同一时间针对同一域名下的请求有一定数量限制(一般是6个),超过限制数目的请求会被阻塞

首屏尽可能减少同域名的请求,包括接口和js;按需减少首屏的chunk.js,合并接口请求

2、合理使用preload、dns-prefetch、prefetch

  • preload具有较高的加载优先级,它可以利用间隙时间预加载资源,将加载和执行分离开,不阻塞渲染和document的onload事件

  • 每次与域名连接都需要进行DNS解析,使用dns-prefetch可以预解析域名的DNS

  • prefetch会预加载页面将来可能用到的一些资源,优先级较低;对首屏渲染要求较高的项目不建议使用

三者的使用方式,在head标签中添加(vue-cli-3已经做了相应配置):

<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <link rel="icon" href="/dist/favicon.ico" />
  <!-- dns-prefetch写法 -->
  <link rel="dns-prefetch" href="//www.dpfile.com" />
  <title>md-config</title>
  <!-- preload写法,as属性必须 -->
  <link href="/dist/css/app.52dd885e.css" rel="preload" as="style" />
  <link href="/dist/js/app.05faf3b5.js" rel="preload" as="script" />
  <link href="/dist/js/chunk-vendors.04343b1f.js" rel="preload" as="script" />
  <!-- prefetch写法 -->
  <link href="/dist/js/chunk-vendors.04343b1f.js" rel="prefetch" />
</head>

3、PWA

PWA支持缓存HTML文档、接口(get)等,降低页面白屏时间
这样即使在弱网甚至断网情况下,也能迅速展示出页面

编译打包优化

1、升级Vue-Cli-3

vue-cli-3采用webpack4+babel7,对编译打包方面做了很多优化(成倍的提升),使用yarn作为包管理工具,并且对很多优化的最佳实践做了默认配置

经测试,将项目从vue-cli-2迁移到vue-cli-3之后,速度变化为:

编译时间:44s --> 7s
打包时间:55s --> 11s

效率提升非常明显

2、SSR

对加载性能要求较高的项目建议升级SSR

树状层级json转换数组的尾递归写法

树状层级包含关系的JSON转换为一个数组列表,使用尾递归算法不仅代码简单,而且性能高效,不会爆栈

需求简介: 把树状的JSON数据连起来转换成一个个的数组,如:item1层有一个item2,它的下面有两个item2_1,item2_2,那么转换为数据就是 [{item1:"item1_key",item2_1:"item2_1_key"},{item1:"item1_key",item2_2:"item2_2_key"}],也就是循环遍历

思路

优雅的写代码

可以看到,我们是从JSON里面取数据,那么对于Object类型,一般都是使用item.key这样的方式去取数据,但是对于现在的这个需求,这样写显然是很不优雅的一种写法,而且通用性很低,字段名字一改就都得改了。

所以:我们把需要取的字段名按顺序放在一个数据里面,记录index,每次从数组中取字段名称,这样代码看起来既简洁,维护起来也很方便。

对象闭包问题解决

这里插一句关于对象的闭包问题:for循环当中,我们取了当前的i值,肯定会发生闭包,那么简单的解决办法就是:

先将对象转成字符串,再转成对象,这样来回一转就取到的是对象值了

var str = JSON.stringfy(obj)

JSON.parse(str)

代码如下,大家可以放到网页中看一下控制台输出:

var json = {
    item1: {
        buckets: [
            {
                key: 'item1_1',
                item2: {
                    buckets: [
                        {
                            key: 'item2_1',
                            item3: {
                                buckets: [
                                    {
                                        key: 'item3_1',
                                        name: 'name'
                                    }, {
                                        key: 'item3_2',
                                        name: ''
                                    }
                                ]
                            }
                        }, {
                            key: 'item2_2',
                            item3: {
                                buckets: [
                                    {
                                        key: 'item3_1',
                                        name: 'name'
                                    }
                                ]
                            }
                        }, {
                            key: 'item2_3',
                            item3: {
                                buckets: [
                                    {
                                        key: 'item3_1',
                                        name: 'name'
                                    }
                                ]
                            }
                        }
                    ]
                }
            }, {
                key: 'item1_2',
                item2: {
                    buckets: [
                        {
                            key: 'item2_1',
                            item3: {
                                buckets: [
                                    {
                                        key: 'item3_1',
                                        name: 'name'
                                    }
                                ]
                            }
                        }, {
                            key: 'item2_2',
                            item3: {
                                buckets: [
                                    {
                                        key: 'item3_1',
                                        name: 'name'
                                    }
                                ]
                            }
                        }
                    ]
                }
            }
        ]
    }
}
var filedArr = ['item1', 'item2', 'item3', 'name']
var arr = []
function recursion(obj, tempObj, index) {
    var key = filedArr[index];
    for (var i = 0; i < obj[key].buckets.length; i++) {
        tempObj[key] = obj[key].buckets[i].key
        if (index == filedArr.length - 2) {
            var temp = JSON.stringify(tempObj)
            arr.push(JSON.parse(temp))
        } else {
            recursion(obj[key].buckets[i], tempObj, index + 1)
        }
    }
}
recursion(json, {}, 0);
console.log(arr);

JavaScript对象的的创建及属性状态维护详解

在说属性之前,我们先来了解一下ES5的新方法,Object.create()函数。
#新的对象创建方法
在旧的“原型继承”观念中,它的本质上是“复制原型”,即:以原型为模板复制一个新的对象。然而我们应该注意到一点事实:在这个思路上,“构造器函数”本身是无意义的。更确切的说,构造器函数对实例的修饰作用可有可无,例如:

//在构造器中修饰对象实例
function MyObject(){
	this.yyy = ...;
}

当意识到这一点后,ES5实现Object.cerate()这样一种简单的方法,通过这一方法将“构造器函数”从对象创建过程中赶了出去。在新的机制中,对象变成了简单的“原型继承+属性定义”,而不再需要“构造器”这样一层语义,例如:

//新的对象创建方法
newObj = Object.create(prototypeObj,PropertyDescriptors);

这里的PropertyDescriptors是一组属性描述符,用于声明基于prototypeObj这个原型之上的一些新的属性添加或修改,它与defineProperties()方法中的props参数是一样的,并在事实上也将调用后者。它的用法如下例所示:

var aPrototypeObject = {name1:"value1"};
var aNewInstance = Object.create(aPrototypeObject,{
	name2:{value:'value2'},
	name3:{get:function(){ return 'value3' }}
})

很显然,在这种新方案中我们看不到类似MyObject()那样的构造器了。事实上在引擎实现Object.create()时也并不特别地声明某个构造器。

所以,所有由Object.create()创建的对象实例具有各自不同的原型(这取决于调用create()方法时传入的参数),但它们的constractor值指向相同的引用——引擎内建的Object构造器。

#属性状态维护
ES5中在Object()上声明了三组方法,用于维护对象本身在属性方面的信息,如下表(Markdown不会使用分组列表,大家凑合看看。。如果有知道的也告诉我一下哈~)

分类 方法 说明
取属性列表 getOwnPropertyNames(obj) 取对象自有的属性名数组
取属性列表 keys(obj) 取对象自由的、可见的属性名数组
状态维护 preventExtensions(obj) 使实例obj不能添加新属性
状态维护 seal(obj) 使实例obj不能添加新属性,也不能删除既有属性
状态维护 freeze(obj) 使实例obj所有属性只读,且不能再添加、删除属性
状态检查 isExtensible(obj) 返回preventExtensions状态
状态检查 isSealed(obj) 返回seal状态
状态检查 isFrozen(obj) 返回freeze状态
其中,preventExtensions、seal和freeze三种状态都是针对对象来操作的,会影响到所有属性的性质的设置。需要强调的有两点:
  • 由原型继承来的性质同样会受到影响
  • 以当前对象作为原型时,子类可以通过重新定义同名属性来覆盖这些状态

更进一步的说,这三种状态是无法影响子类使用defineProperty()和defineProperties()来“重新定义(覆盖)”同名属性的。

本质上说,delete运算是用于删除运算对象属性的属性描述符,而非某个属性。

##取属性列表

取属性列表的传统方法是使用for...in语句。为方便后续讨论,我们先为该语句封装一个与Object.keys()类似的方法:

Object.forIn = function(obj){
	var Result = [];
	for(var n in obj) Result.push(n);
	return Result;
}

forIn()得到的总是该对象全部可见的属性列表。而keys()将是其中的一个子集,即"自有的(不包括继承而来的)"可见属性列表。下面的例子将显示二者的不同:

var obj1 = {n1:100};
var obj2 = Object.create(obj1,{n2 : {value :200,enumerable:true}});

//显示'n1' , 'n2'
//  - 其中n1继承自obj1
alert(Object.forIn(obj2));

//显示'n2'
alert(Object.keys(obj2));

getOwnPropertyNames()得到的与上述两种情况都不相同。它列举全部自有的属性,但无论它是否可见。也就是说,它是keys()所列举内容的超集,包括全部可见和不可见的、自有的属性。仍以上述为例:

// (续上例)

//定义属性名n3,其enumerable性质默认为false
Object.defineProperty(obj2,'n3',{value:300})

//仍然显示'n1','n2'
// - 新定义的n3不可见
alert(Object.forIn(obj2));

//显示'n2'
alert(Object.keys(obj2));

//显示n2,n3
alert(Object.getOwnPropertyNames(obj2));

##使用defineProperty来维护属性的性质
在defineProperty()或defineProperties()中操作某个属性时,如果该名字的属性未声明则新建它;如果已经存在,则使用描述符中的新的性质来覆盖旧的性质值。

这也意味着一个使用"数据属性描述符"的属性,也可以重新使用"存取属性描述符"——但总的来说只能存在其中一个。例如:

var pOld,pNew;
var obj = { data : 'oldValue'}

//显示'value,writable,enumerable,configuable'
pOld = Object.getOwnPropertyDescriptor(obj,'data');
alert(Object.keys(pOld));

//步骤一:通过一个闭包来保存旧的obj.data的值
Object.defineProperty(obj,'data',function(oldValue){
	return {
		get:function(){ return oldValue},
		configurable:false
	}
}(obj.data))

//显示'get,set,enumerable,configurable'
pNew = Object.getOwnPropertyDescriptor(obj,'data');
alert(pNew);

//步骤二:测试使用重定义的getter来取obj.data的值
// - 显示 'oldValue'
alert(obj.data);

//步骤三:(测试)尝试再次声明data属性
// - 由于在步骤一中已经设置configurable为false,因此导致异常(can't redefine)。
Object.defineProperty(obj,'data',{value:100});

##对于继承自原型的属性,修改其值的效果

如果某个从原型继承来的属性是可写的,并且它使用的是"数据属性描述符",那么在子类中修改该值,将隐式地创建一个属性描述符。这个新属性描述符将按照"向对象添加一个属性"的规格来初始化。即:必然是数据属性描述符,且Writable,Enumerable和Configurable均为true值。例如:

var obj1 = {n1 : 100};
var obj2 = Object.create(obj1);

//显示为空
// - 重置n1的enumerable性质为false,因此在obj1中是不可见的
Object.defineProperty(obj1,'n1',{enumerable:false})
alert(Object.keys(obj1));

//显示为空
// - n1不是obj2的自有属性
alert(Object.getOwnPropertyNames(obj2));

//显示n1
// - 由于n1赋值导致新的属性描述符,因此n1成为了自有的属性
obj2.n1 = 'newValue';
alert(Object.getOwnPropertyNames(obj2));

//显示n1,表明n1是可见的
// - 由于新的属性描述符的enumerable重置为true,因此在obj2中它是可见的
alert(Object.keys(obj2));

如果一个属性使用的是"存取属性描述符",那么无论它的读写性为何,都不会新建属性描述符。对子类中该属性的读写,都只会忠诚地调用(继承而来的、原型中的)读写器。

##重写原型继承来的属性的描述符
使用defineProperty()或defineProperties()将重新定义该属性,会显式的创建一个属性描述符。在这种情况下,该属性也将变成自雷对象中"自有的"属性,它的可见性等性质就由新的描述符来决定。

与上一小节不同的是,这与原型中该属性是否"只读"或是否允许修改性质(configurable)无关。

这可能导致类似如下的情况:在父类中某个属性时只读的,并且不可修改其描述符性质的,但是在子类中,同一个名字的属性却可以读写并可以重新修改性质。更为严重的是,仅仅观察两个对象实例的外观,我们无法识别这种差异是如何导致的。下面的示例说明这种情况:

var obj1 = {n1 : 100};
var obj2 = Object.create(obj1);

//对于原型对象obj1,修改其属性n1的性质,使其不可列举、修改、且不能重设性质
Object.defineProperty(obj1,'n1',{writable:false,enumerable:false,configurable:false});

//显示为空,obj1.n1是不可列举的
alert(Object.keys(obj1));

//由于不可重设性质,因此对obj1.n1的下述调用将导致异常
//Object.defineProperty(obj1,'n1',{configurable:true});

接下来我们观察"重新定义属性"带来的效果:

//(续上例)

//重新定义obj2.n1
Object.defineProperty(obj2,'n1',{value:obj2.n1,writable:true,enumerable:true,configurable:true});

//显示newValue'
// - 结论:可以通过重定义属性,使该属性从"只读"变成"可读写"(以及其他性质的变化)
obj2.n1 = 'newValue';
alert(obj2.n1);

//列举obj2的自有性质,结果显示:n1
// - 现在n1是自有的属性了
alert(Object.getOwnpropertyNames(obj2));

从表面上看,一个父类中只读的属性在子类变成了可读写。而且,一旦我们用delete删除该属性,它又会恢复父类中的值和性质。例如:

//尝试删除该属性
// - 显示100,即它在原型中的值
delete obj2.n1;
alert(obj2.n1);

再次强调这一事实:在ES5中没有任何方法可以阻止上述过程。也就是说,我们无法阻止子类对父类同名属性的重定义,也无法避免这种重定义可能带来的业务逻辑问题。

PDF转HTML神器,解决跨平台问题

在手机端展示PDF是移动开发者的一大痛点

目前在PC端展示PDF有较多的解决方案,比如:

PC端

embed标签

<embed type="application/pdf" src="test.pdf" width="100%" height="100%"/>

pdf.js

这里不再赘述使用方法,网上一大堆

那么在手机端展示pdf就成了一个难题,尤其是在微信上(兼容性无力吐槽),经过一番研究,在GitHub上发现了pdf2htmlEX这个神器!

移动端

pdf2htmlEX

GitHub链接先来一发:https://github.com/coolwanglu/pdf2htmlEX

Demo再来一发,注意url,是xxx.html! http://coolwanglu.github.io/pdf2htmlEX/demo/geneve.html

ok,相信你看了Demo基本已经被它的显示效果惊呆了,下面我们来看一下它的使用吧:

安装

官方文档:https://github.com/coolwanglu/pdf2htmlEX/wiki/Building

Mac OS X可以使用brew来安装

brew install pdf2htmlEX

Windows在以上链接最底部有下载链接。

使用

官方文档:https://github.com/coolwanglu/pdf2htmlEX/wiki/Quick-Start

安装好pdf2htmlEX后,使用命令行切换到pdf文件目录。

这里简单介绍几个命令:

全部转换为HTML

pdf2htmlEX --zoom 1.3 pdf/test.pdf
  • zoom表示缩放 后面的数字就是缩放比例,数字越小,生成的HTML显示界面越小,但是本身的bytes大小不受影响
  • pdf/test.pdf是文件路径,相对于当前目录的路径
  • 生成的HTML文件就在当前目录下,文件名不变。

全部转换为目录(HTML,CSS,IMGS,FONTS)

pdf2htmlEX --embed cfijo --dest-dir out pdf/test.pdf
  • embed cfijo没有实际意义,是本身命令,不需要修改
  • out是自定义文件夹名称,所有的转换的文件都生成在此目录
  • pdf/test.pdf是源pdf文件

除了以上的命令以外,还有很多实用方式,比如截取片段转换、修改像素等,这里不再赘述,感兴趣的可以去官网查看。

JavaScript中的闭包问题多种实例分析

一般情况下,当一个函数实例被创建时,它唯一对应的一个闭包也就被创建。在下面的代码中,由于外部的构造器函数被执行两次,因此内部的foo函数也被创建了两个函数实例(以及闭包)并赋值给this对象的成员:

function MyObject() {
    function foo() {}
    this.method = foo;
}
obj1 = new MyObject();
obj2 = new MyObject();

//显示false,表明产生了两个对象
console.log(obj1.method === obj2.method);

这在函数之外(语句级别)也具有完全相同的表现。在下面的例子中,多个匿名函数的实例被赋给了obj的成员:

var obj = new Object();
for (var i = 0; i < 5; i++) {
    obj['method' + i] = function() {
        console.log(i);
    }
}
console.log(obj.method2 === obj.method3);

尽管是多个实例,但它们仍然是共享同一个外层函数闭包中的upvalue值——在上例中,外层函数闭包指的是全局闭包。因此下面例子所变现出来的,仍然只是闭包中对upvalue值访问的规则,而并非闭包或者函数实例的创建规则“出现了某种特例”:

var obj = new Object();
var events = {
  m1:'clicked',
  m2:'changed'
}
for(e in events){
  obj[e] = function () {
    console.log(events[e]);
  }
}
//显示false,表明是不同的函数实例
console.log(obj.m1 === obj.m2);

//方法m1()和m2()都都出相同值
//其原因,在于两个方法都访问全局闭包中的同一个upvalue值。
obj.m1();
obj.m2();

在这个例子中,王法m1与m2究竟输出何值,取决于前面的for...in语句在最后一次迭代中对e的置值。某些引擎中总保证for...in的顺序与events中声明时的属性顺序一致(例如SpiderMonkey),但也有一些引擎并没有这项约定。因此上例在不同的引擎中表现的结果未必一致,但m1()与m2()输出值总是相同的。

按照这段代码的本意,应该是每个函数实例输出不同值。对这个问题的处理之一,是再添加一个外层函数,利用“在函数内保存数据”的特性来为内部函数保存一个可访问的upvalue:

var obj = new Object();
var events = {
    m1: 'clicked',
    m2: 'changed'
}
for (e in events) {
    obj[e] = function(aValue) { //闭包lv1
        return function() { //闭包lv2
            console.log(events[aValue]);
        }
    }(e)
}

/*或者使用如下代码,在闭包内通过局部变量保存数据。
for (e in events) {
    obj[e] = function() { //闭包lv1
        var aValue = e;
        return function() { //闭包lv2
            console.log(events[aValue]);
        }
    }()
}*/

//下面将输出不同的值
obj.m1();
obj.m2();

由于闭包lv2引用了闭包lv1中的入口参数,因此两个闭包存在了关联关系。在obj的方法未被清除之前,两个闭包都不会被销毁,但lv1为lv2保存了一个可供访问的upvalue——这除了私有变量、函数之外,也包括它的传入参数。

很明显,上例的问题在于多了一层闭包,因此增加了系统消耗。不过这并非不可避免。我们要看清楚这个问题,其本质是一要一个地方来保存for...in列举中的每一个值e,而并非是“需要一个闭包来添加一个层”。那么,既然列举过程中产生了不同的函数实例,自然也可以将值e交给这些函数实例自己去保存:

var obj = new Object();
var events = {
    m1: 'clicked',
    m2: 'changed'
}
for (e in events) {
    (obj[e] = function(aValue) {
        //arguments.callee指向匿名函数自身
        console.log(events[arguments.callee.aValue]);
    }).aValue = e;
}
//下面将输出不同的值
obj.m1();
obj.m2();

使用pushState实现微信“返回”按钮控制单页应用页面的无刷新跳转

相信很多微信开发者都会遇到过这样的问题:为了提高用户体验,把多个页面内容放在一个HTML页面进行展示,通过display属性以及transition动画来实现页面的跳转动画,但是点击微信顶部的“返回”按键之后就会直接跳出整个页面。

所以针对以上的情况,一般的应用的解决方案都是在页面的顶部自己画一个导航栏,需要点击自定义的导航栏来实现返回效果。

弊端

但是不觉得和微信的导航栏放在一起,并且微信上面也有返回,两个返回很别扭吗?

并且对于安卓用户来说,底部的返回键岂不是没用了?万一手抖点到了怎么办?

没关系,使用history可以解决:

History

我们知道,浏览器的跳转以及返回都是存在history历史里面的,它是栈,后进先出。
跳转一个新页面就进栈一个,返回一次就出栈一个。所以我们可以控制它的栈来实现返回的效果

pushState

history提供了一个方法pushState,可以手动的添加页面进栈。

使用语法:history.pushState(state, title, url);

  • state:Object,与要跳转到的URL对应的状态信息。
  • title:页面名字,方便调试。
  • url:要跳转到的URL地址,不能跨域,对于单页应用来说没用,传空即可。

如:

history.pushState({
     "page": "productList"
 }, "首页", "");

##replaceState
用新的state和URL替换当前。不会造成页面刷新。语法和pushState相同,该方法一般在首页使用,更改一下首页的参数名,方便监控和判断。

onpopstate

history.go和history.back(包括用户按浏览器历史前进后退按钮)触发,并且页面无刷的时候(由于使用pushState修改了history)会触发popstate事件,事件发生时浏览器会从history中取出URL和对应的state对象替换当前的URL和history.state。通过event.state也可以获取history.state。

window.onpopState = function(){
	var json = window.history.state;//获取当前所在的state
    if (json && json.page == "home") { //page即是pushState时设置的参数
        if (searchParams.sourceUrl && !selected) {
            history.go(-1);
        } else {
            document.querySelector('.product-page').removeCls('show');
            document.querySelector(".visit-hospital-page").classList.remove("show");
        }
    }}

history.go

使用该方法即可实现出栈

history.go(-1);//返回一个页面
history.go(-2);//返回两个页面
以此类推

结合以上所述,即可实现页面的无刷新跳转,希望对大家有帮助

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.