方始终

方始终 查看完整档案

深圳编辑中国矿业大学  |  采矿工程 编辑腾讯  |  小学生 编辑 null.com 编辑
编辑

web developer.

个人动态

方始终 赞了文章 · 2020-11-01

element-ui中cascader同时获取label和value值

关于elementUI中cascader选中值后,能获取value或者label,但不能同时获value和label,这一问题,琢磨出了这么个办法。
以新增和编辑城市为例,type: 1 编辑,type: 0 新增

1. 配置元素
<el-cascader filterable
  :class="{'city-cascader': type==1}"
  :placeholder="city || '请选择'"
  :options="cityLists"
  :props="cityProps"
  v-model="citySelected"
  style="width:300px;"
  :show-all-levels="false"
  @change="changeCity" >
</el-cascader>
2. 配置cityProps
cityProps: {value: 'all', label: 'label'}
3. 组装props中的all
// cityLists中遍历组装all
all: {
  value: value,
  label: label
}
4. 使用

此时,点击cascader选择需要的内容后,
取出来的citySelected值就是[{value: 选中值的value, label: 选中值的label}]

这个方法可以通过配置all获取任意自己想要的值。

PS: 关于拿不到默认值的问题,我投机取巧的使用了placeholder。

:placeholder="city || '请选择'"
然后在cascader上加上样式:

:class="{'city-cascader': type==1}"

.city-cascader .el-input__inner::placeholder {
  color: #333 !important;
}

ok,完美解决cascader取值问题。

查看原文

赞 3 收藏 2 评论 1

方始终 关注了用户 · 2019-05-14

花裤衩 @panjiachen

show me the code

关注 2686

方始终 关注了专栏 · 2019-04-21

有赞技术

有赞技术相关内容

关注 6681

方始终 发布了文章 · 2018-09-19

如何在VSCode和Sublime Text当中开启eslint检查?

这个问题,我自己也折腾了很久,在这里分享给你们。

当前的环境

操作系统: MacOS(虽然我这么写,但是对于扩平台的编辑器来说,没什么影响)
node版本:v8.9.4
vue-cli版本:v3.0.1
sublime text版本:v3.1.1 build3176
vscode版本:v1.27.2

重要前提

  1. 你的代码目录中或者全局已经安装eslint以及相关的插件
  2. eslint的配置已经到位,在CLI中已经可以打印出错误
  3. 如果没有,请参考eslint官方文档

PS:如果你的项目目录是用vue-cli 3.0生成的,那恭喜你,你可以通过使用vue add @vue/eslint添加eslint需要的插件,并且可以选择需要遵循的代码规范。这部分内容请移步vue-cli 3.0官方文档,我就不赘述了。

开启VSCode的eslint

  1. 安装ESLint插件,鉴于安装及其简单,如果实在需要,安装插件的方法参考这里

clipboard.png

macOS下点击Code->首选项->设置,Windows下点击文件->首选项->设置

在左侧选中ESLint,找到Node Path下方的Edit in setting.json

clipboard.png

clipboard.png

就会打开全局配置的json文件,设置eslint.nodePath和eslint.validate属性

"eslint.nodePath": "/usr/local/bin/node",
"eslint.validate": [
    "javascript",
    "javascriptreact",
    "vue"
]

clipboard.png

这两个是最关键的属性,validate默认是没有vue的,所以默认是无法在.vue单文件组件中看到eslint报错。

其余的eESLint配置可以根据自身需要进行设置,这里就不BB了。

设置完这两个属性之后,保存配置,就大功告成了。

打开代码文件,就会看到红色波浪线的错误提示,鼠标指针hover在波浪线的地方,就会看到具体的报错了,比如下图的这个地方,报错显示定义了a但是作用于范围内没有使用到。

clipboard.png

开启Sublime Text的eslint

安装插件SublimeLinterSublimeLinter-eslint,安装插件的方法在插件的文档中都有介绍,实在需要,点击这行前面的插件名称。

安装完成后,点击 Preference->Package settings->SublimeLinter->Settings,macOS需要到Sublime Text菜单中找Preference

会看到打开了一个配置文件,将下方的这段配置粘贴到右边的User Settings,保存。
设置中添加

{
    "linters": {
        "eslint": {
            "selector": "text.html.vue, source.js - meta.attribute-with-value"
        }
    }
}

打开代码文件,此时会看到行号左边的小红点报错,hover鼠标上去就会提示具体的报错信息,搞定!!

clipboard.png

写在后面

上面的配置都是为了满足我使用vue开发的需要,如果是其他框架,有可能不适用,祝你幸福。

查看原文

赞 4 收藏 4 评论 0

方始终 关注了用户 · 2018-02-08

劉凯里 @kyrieliu

我可能是一个假前端
个人微信公众号:劉凯里(kkkyrieliu)
个人网站:kyrieliu.cn

关注 137

方始终 收藏了文章 · 2018-01-30

next.js、nuxt.js等服务端渲染框架构建的项目部署到服务器,并用PM2守护程序

前端渲染:vue、react等单页面项目应该这样子部署到服务器

貌似从前几年,前后端分离逐渐就开始流行起来,把一些渲染计算的工作抛向前端以便减轻服务端的压力,但为啥现在又开始流行在服务端渲染了呢?如vue全家桶或者react全家桶,都推荐通过服务端渲染来实现路由。搞得我们慌得不行,不禁让我想起一句话:从来没有任何一门语言的技术栈像Javascript一样,学习者拼尽全力也不让精通。没办法,流行,咱们就得学!

前断时间写了一篇vue、react等单页面项目应该这样子部署到服务器,结果反响不错!最近好多朋友私信或邀请问很多关于next.js和nuxt.js的问题,比如关于nextjs 和 nuxtjs如何部署?pm2如何配合?...在这里我们就一起讨论下在服务器上使用PM2守护next.js、nuxt.js等服务端渲染框架构建的项目!该篇我们只讨论服务端渲染应用部署静态应用部署就是我前段时间写的vue、react等单页面项目应该这样子部署到服务器

Nginx配置

既然是应用,我们就应该有域名,在这里我们以 nginx配置 为例,简单配置如下:
Next域名:http://next.sosout.com/
Nuxt域名:http://nuxt.sosout.com/

http {
    ....  # 省略其他配置
   
    server {
        listen 80;
        server_name  *.sosout.com;
        
        if ($host ~* "^(.*?)\.sosout\.com$") {
            set $domain $1;
        }

        location / {
            if ($domain ~* "next") {
                root /mnt/html/next;
            }
            if ($domain ~* "nuxt") {
                root /mnt/html/nuxt;
            }
            proxy_set_header   Host             $host;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto  $scheme;
        }
        access_log  /mnt/logs/nginx/access.log  main;
    }

    #tcp_nopush     on;

    include /etc/nginx/conf.d/*.conf;
}

Nginx反向代理

由于服务端渲染的各个应用端口号各不相同,因此这个时候我们就需要反向代理了,配置如下:

#通过upstream nodejs 可以配置多台nodejs节点,做负载均衡
#keepalive 设置存活时间。如果不设置可能会产生大量的timewait
#proxy_pass 反向代理转发 http://nodejs

upstream nodenext {
    server 127.0.0.1:3001; #next项目 监听端口
    keepalive 64;
}

server {
    listen 80;
    server_name next.sosout.com;
    location / {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;  
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Nginx-Proxy true;
        proxy_cache_bypass $http_upgrade;
        proxy_pass http://nodenext; #反向代理
    }
}

upstream nodenuxt {
    server 127.0.0.1:3002; #nuxt项目 监听端口
    keepalive 64;
}

server {
    listen 80;
    server_name nuxt.sosout.com;
    location / {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;  
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Nginx-Proxy true;
        proxy_cache_bypass $http_upgrade;
        proxy_pass http://nodenuxt; #反向代理
    }
}

服务器的准备工作已完成,接下来我们就分别看看Next.js和Nuxt.js服务端渲染应用如何部署?

Next.js服务端渲染应用部署

部署 Next.js 服务端渲染的应用不能直接使用 next 命令,而应该先进行编译构建,然后再启动 Next 服务,官方通过以下两个命令来完成:

next build
next start

官方推荐的 package.json 配置如下:

{
  "name": "my-app",
  "dependencies": {
    "next": "latest"
  },
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  }
}

而我更推荐如下配置,稍后你会发现这样和 pm2 一起使用更方便,自动化部署也方便:

{
  "name": "my-app",
  "dependencies": {
    "next": "latest"
  },
  "scripts": {
    "dev": "next",
    "start": "next start -p $PORT",
    "build": "next build && PORT=3001 npm start"
  }
}

next.js服务端渲染应用部署这样就完成了,官方先后执行 npm run build 、npm start 即可完成部署。而我这边只要执行 npm run build ,其实我只是把两个合并成一个,并设置了端口以便区别其他应用,避免端口占用!

接下来简单的说一下next这几个命令:
next: 启动一个热加载的Web服务器(开发模式)
next build: 利用webpack编译应用,压缩JS和CSS资源(发布用)。
next start: 以生成模式启动一个Web服务器 (next build 会先被执行)。

Nuxt.js服务端渲染应用部署

其实部署 Nuxt.js 服务端渲染的应用和 Next.js 极其相似!在这里我就把代码粘粘贴贴,复复制制,改改写写。。。。
Nuxt.js 服务端渲染的应用不能直接使用 nuxt 命令,而应该先进行编译构建,然后再启动 Nuxt 服务,官方通过以下两个命令来完成:

nuxt build
nuxt start

官方推荐的 package.json 配置如下:

{
  "name": "my-app",
  "dependencies": {
    "nuxt": "latest"
  },
  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start"
  }
}

而我更推荐如下配置,稍后你会发现这样和 pm2 一起使用更方便,自动化部署也方便:

{
  "name": "my-app",
  "dependencies": {
    "nuxt": "latest"
  },
  "scripts": {
    "dev": "nuxt",
    "start": "PORT=3002 nuxt start",
    "build": "nuxt build && npm start"
  }
}

nuxt.js服务端渲染应用部署这样就完成了,官方先后执行 npm run build 、npm start 即可完成部署。而我这边只要执行 npm run build ,其实我只是把两个合并成一个,并设置了端口以便区别其他应用,避免端口占用!

接下来简单的说一下nuxt这几个命令:
nuxt: 启动一个热加载的Web服务器(开发模式)
nuxt build: 利用webpack编译应用,压缩JS和CSS资源(发布用)。
nuxt start: 以生成模式启动一个Web服务器 (nuxt build 会先被执行)。

PM2守护程序

Next.js使用pm2,进入对应的应用目录,执行以下命令:

pm2 start npm --name "my-next" -- run build

Nuxt.js使用pm2,进入对应的应用目录,执行以下命令:

pm2 start npm --name "my-nuxt" -- run build

使用pm2时,把两个部署命令合成一个更方便!执行完pm2的启动命令后,我们用 pm2 list 查看一下进程列表,我截一下我个人服务器的pm2列表:

clipboard.png

以后您就可以用pm2进行维护了,比如我们的next应用更改了代码,因为当时创建时给next应用命名的进程名称为 my-next ,因此我们可以直接使用 pm2 reload my-next 进行重载。接下来我就简单介绍一下pm2,如果有需要,我可以另写一篇关于pm2的文章!

pm2 简单介绍

pm2是nodejs的一个带有负载均衡功能的应用进程管理器的模块,类似有Supervisor,forever,用来进行进程管理。

一、安装:
npm install pm2 -g
二、启动:
pm2 start app.js
pm2 start app.js --name my-api       #my-api为PM2进程名称
pm2 start app.js -i 0                #根据CPU核数启动进程个数
pm2 start app.js --watch             #实时监控app.js的方式启动,当app.js文件有变动时,pm2会自动reload
三、查看进程:
pm2 list
pm2 show 0 或者 # pm2 info 0         #查看进程详细信息,0为PM2进程id 
四、监控:
pm2 monit
五、停止:
pm2 stop all                         #停止PM2列表中所有的进程
pm2 stop 0                           #停止PM2列表中进程为0的进程
六、重载:
pm2 reload all                       #重载PM2列表中所有的进程
pm2 reload 0                         #重载PM2列表中进程为0的进程
七、重启:
pm2 restart all                      #重启PM2列表中所有的进程
pm2 restart 0                        #重启PM2列表中进程为0的进程
八、删除PM2进程:
pm2 delete 0                         #删除PM2列表中进程为0的进程
pm2 delete all                       #删除PM2列表中所有的进程
九、日志操作:
pm2 logs [--raw]                     #Display all processes logs in streaming
pm2 flush                            #Empty all log file
pm2 reloadLogs                       #Reload all logs
十、升级PM2:
npm install pm2@lastest -g           #安装最新的PM2版本
pm2 updatePM2                        #升级pm2
十一、更多命令参数请查看帮助:
pm2 --help
十二、PM2目录结构:
  • 1、默认的目录是:当前用于的家目录下的.pm2目录(此目录可以自定义,请参考:十三、自定义启动文件),详细信息如下:
$HOME/.pm2                   #will contain all PM2 related files
$HOME/.pm2/logs              #will contain all applications logs
$HOME/.pm2/pids              #will contain all applications pids
$HOME/.pm2/pm2.log           #PM2 logs
$HOME/.pm2/pm2.pid           #PM2 pid
$HOME/.pm2/rpc.sock          #Socket file for remote commands
$HOME/.pm2/pub.sock          #Socket file for publishable events
$HOME/.pm2/conf.js           #PM2 Configuration
十三、自定义启动文件:
  • 1、创建一个test.json的示例文件,格式如下:
{
  "apps":
    {
      "name": "test",
      "cwd": "/data/wwwroot/nodejs",
      "script": "./test.sh",
      "exec_interpreter": "bash",
      "min_uptime": "60s",
      "max_restarts": 30,
      "exec_mode" : "cluster_mode",
      "error_file" : "./test-err.log",
      "out_file": "./test-out.log",
      "pid_file": "./test.pid"
      "watch": false
    }
}
  • 2、参数说明:
apps:json结构,apps是一个数组,每一个数组成员就是对应一个pm2中运行的应用
name:应用程序的名称
cwd:应用程序所在的目录
script:应用程序的脚本路径
exec_interpreter:应用程序的脚本类型,这里使用的shell,默认是nodejs
min_uptime:最小运行时间,这里设置的是60s即如果应用程序在60s内退出,pm2会认为程序异常退出,此时触发重启max_restarts设置数量
max_restarts:设置应用程序异常退出重启的次数,默认15次(从0开始计数)
exec_mode:应用程序启动模式,这里设置的是cluster_mode(集群),默认是fork
error_file:自定义应用程序的错误日志文件
out_file:自定义应用程序日志文件
pid_file:自定义应用程序的pid文件
watch:是否启用监控模式,默认是false。如果设置成true,当应用程序变动时,pm2会自动重载。这里也可以设置你要监控的文件。

由于工作原因,一直没光顾segmentfault,收到很多关于部署的私信和评论,特此补充以下内容:

部署(以nuxt为例)

基础模板的部署方式

何为基础模板?使用了 vue init nuxt-community/starter-template <project-name> 进行搭建的!

第一步,打包

在执行 npm run build 的时候, nuxt 会自动打包。

第二步,选择要部署的文件(社友最关心的步骤):

  • .nuxt/ 文件夹
  • package.json 文件
  • nuxt.config.js 文件(如果你配置proxy等,则需要上传这个文件,建议把它传上去)

第三步,启动你的nuxt:

使用pm2启动你的nuxt.js:

$ npm install // or yarn install 如果未安装依赖或依赖有更改
$ pm2 start npm --name "my-nuxt" -- run start
查看原文

方始终 收藏了文章 · 2018-01-30

vue、react等单页面项目应该这样子部署到服务器

服务端渲染:next.js、nuxt.js等服务端渲染框架构建的项目部署到服务器,并用PM2守护程序

最近好多伙伴说,我用vue做的项目本地是可以的,但部署到服务器遇到好多问题:资源找不到直接访问index.html页面空白刷新当前路由404。。。用react做的项目也同样遇到类似问题。现在我们一起讨论下单页面如何部署到服务器?

由于前端路由缘故,单页面应用应该放到nginx或者apache、tomcat等web代理服务器中,千万不要直接访问index.html,同时要根据自己服务器的项目路径更改react或vue的路由地址。

如果说项目是直接跟在域名后面的,比如:http://www.sosout.com ,根路由就是 '/'。
如果说项目是直接跟在域名后面的一个子目录中的,比如:http://www.sosout.com/children ,根路由就是 '/children ',不能直接访问index.html。

以配置Nginx为例,配置过程大致如下:(假设:
1、项目文件目录: /mnt/html/spa(spa目录下的文件就是执行了npm run dist 后生成的dist目录下的文件)
2、访问域名:spa.sosout.com
进入nginx.conf新增如下配置:

server {
    listen 80;
    server_name  spa.sosout.com;
    root /mnt/html/spa;
    index index.html;
    location ~ ^/favicon\.ico$ {
        root /mnt/html/spa;
    }

    location / {
        try_files $uri $uri/ /index.html;
        proxy_set_header   Host             $host;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto  $scheme;
    }
    access_log  /mnt/logs/nginx/access.log  main;
}

注意事项:
1、配置域名的话,需要80端口,成功后,只要访问域名即可访问的项目
2、如果你使用了react-router的 browserHistory 模式或 vue-router的 history 模式,在nginx配置还需要重写路由:

server {
    listen 80;
    server_name  spa.sosout.com;
    root /mnt/html/spa;
    index index.html;
    location ~ ^/favicon\.ico$ {
        root /mnt/html/spa;
    }

    location / {
        try_files $uri $uri/ @fallback;
        index index.html;
        proxy_set_header   Host             $host;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto  $scheme;
    }
    location @fallback {
        rewrite ^.*$ /index.html break;
    }
    access_log  /mnt/logs/nginx/access.log  main;
}

为什么要重写路由?因为我们的项目只有一个根入口,当输入类似/home的url时,如果找不到对应的页面,nginx会尝试加载index.html,这是通过react-router或vue-router就能正确的匹配我们输入的/home路由,从而显示正确的home页面,如果browserHistory模式或history模式的项目没有配置上述内容,会出现404的情况。

简单举两个例子,一个vue项目一个react项目:

vue项目:

域名:http://tb.sosout.com

clipboard.png

import App from '../App'

// 首页
const home = r => require.ensure([], () => r(require('../page/home/index')), 'home')

// 物流
const logistics = r => require.ensure([], () => r(require('../page/logistics/index')), 'logistics')

// 购物车
const cart = r => require.ensure([], () => r(require('../page/cart/index')), 'cart')

// 我的
const profile = r => require.ensure([], () => r(require('../page/profile/index')), 'profile')

// 登录界面
const login = r => require.ensure([], () => r(require('../page/user/login')), 'login')

export default [{
  path: '/',
  component: App, // 顶层路由,对应index.html
  children: [{
    path: '/home', // 首页
    component: home
  }, {
    path: '/logistics', // 物流
    component: logistics,
    meta: {
      login: true
    }
  }, {
    path: '/cart', // 购物车
    component: cart,
    meta: {
      login: true
    }
  }, {
    path: '/profile', // 我的
    component: profile
  }, {
    path: '/login', // 登录界面
    component: login
  }, {
    path: '*',
    redirect: '/home'
  }]
}]

clipboard.png

############
# 其他配置
############

http {
    ############
    # 其他配置
    ############
    server {
        listen 80;
        server_name  tb.sosout.com;
        root /mnt/html/tb;
        index index.html;
        location ~ ^/favicon\.ico$ {
            root /mnt/html/tb;
        }
    
        location / {
            try_files $uri $uri/ @fallback;
            index index.html;
            proxy_set_header   Host             $host;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto  $scheme;
        }
        location @fallback {
            rewrite ^.*$ /index.html break;
        }
        access_log  /mnt/logs/nginx/access.log  main;
    }
    ############
    # 其他配置
    ############   
}

react项目:

域名:http://antd.sosout.com

clipboard.png

/**
* 疑惑一:
* React createClass 和 extends React.Component 有什么区别?
* 之前写法:
* let app = React.createClass({
*      getInitialState: function(){
*        // some thing
*      }
*  })
* ES6写法(通过es6类的继承实现时state的初始化要在constructor中声明):
* class exampleComponent extends React.Component {
*    constructor(props) {
*        super(props);
*        this.state = {example: 'example'}
*    }
* }
*/

import React, {Component, PropTypes} from 'react'; // react核心
import { Router, Route, Redirect, IndexRoute, browserHistory, hashHistory } from 'react-router'; // 创建route所需
import Config from '../config/index';
import layout from '../component/layout/layout'; // 布局界面

import login from '../containers/login/login'; // 登录界面

/**
 * (路由根目录组件,显示当前符合条件的组件)
 * 
 * @class Roots
 * @extends {Component}
 */
class Roots extends Component {
    render() {
        // 这个组件是一个包裹组件,所有的路由跳转的页面都会以this.props.children的形式加载到本组件下
        return (
            <div>{this.props.children}</div>
        );
    }
}

// const history = process.env.NODE_ENV !== 'production' ? browserHistory : hashHistory;

// 快速入门
const home = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../containers/home/homeIndex').default)
    }, 'home');
}

// 百度图表-折线图
const chartLine = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../containers/charts/lines').default)
    }, 'chartLine');
}

// 基础组件-按钮
const button = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../containers/general/buttonIndex').default)
    }, 'button');
}

// 基础组件-图标
const icon = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../containers/general/iconIndex').default)
    }, 'icon');
}

// 用户管理
const user = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../containers/user/userIndex').default)
    }, 'user');
}

// 系统设置
const setting = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../containers/setting/settingIndex').default)
    }, 'setting');
}

// 广告管理
const adver = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../containers/adver/adverIndex').default)
    }, 'adver');
}

// 组件一
const oneui = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../containers/ui/oneIndex').default)
    }, 'oneui');
}

// 组件二
const twoui = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../containers/ui/twoIndex').default)
    }, 'twoui');
}

// 登录验证
const requireAuth = (nextState, replace) => {
    let token = (new Date()).getTime() - Config.localItem('USER_AUTHORIZATION');
    if(token > 7200000) { // 模拟Token保存2个小时
        replace({
            pathname: '/login',
            state: { nextPathname: nextState.location.pathname }
        });
    }
}

const RouteConfig = (
    <Router history={browserHistory}>
        <Route path="/home" component={layout} onEnter={requireAuth}>
            <IndexRoute getComponent={home} onEnter={requireAuth} /> // 默认加载的组件,比如访问www.test.com,会自动跳转到www.test.com/home
            <Route path="/home" getComponent={home} onEnter={requireAuth} />
            <Route path="/chart/line" getComponent={chartLine} onEnter={requireAuth} />
            <Route path="/general/button" getComponent={button} onEnter={requireAuth} />
            <Route path="/general/icon" getComponent={icon} onEnter={requireAuth} />
            <Route path="/user" getComponent={user} onEnter={requireAuth} />
            <Route path="/setting" getComponent={setting} onEnter={requireAuth} />
            <Route path="/adver" getComponent={adver} onEnter={requireAuth} />
            <Route path="/ui/oneui" getComponent={oneui} onEnter={requireAuth} />
            <Route path="/ui/twoui" getComponent={twoui} onEnter={requireAuth} />
        </Route>
        <Route path="/login" component={Roots}> // 所有的访问,都跳转到Roots
            <IndexRoute component={login} /> // 默认加载的组件,比如访问www.test.com,会自动跳转到www.test.com/home
        </Route>
        <Redirect from="*" to="/home" />
    </Router>
);

export default RouteConfig;

clipboard.png

############
# 其他配置
############

http {
    ############
    # 其他配置
    ############
    server {
        listen 80;
        server_name  antd.sosout.com;
        root /mnt/html/reactAntd;
        index index.html;
        location ~ ^/favicon\.ico$ {
            root /mnt/html/reactAntd;
        }

        location / {
            try_files $uri $uri/ @router;
            index index.html;
            proxy_set_header   Host             $host;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto  $scheme;
        }
        location @router {
            rewrite ^.*$ /index.html break;
        }
        access_log  /mnt/logs/nginx/access.log  main;
    }

    ############
    # 其他配置
    ############   
}
查看原文

方始终 收藏了文章 · 2018-01-30

前端真的能做到彻底权限控制吗?

有一天突然想到一个问题,web端的权限控制:
1.真的能控制权限吗?
2.仅仅靠前端,能不能做到真正的权限控制?
3.如果需要后台配合,应该如何配合?
可能这是一个老生常谈的问题,但还是想整理下,有误的地方望大家指出。

何为权限控制

权限控制大致分为两个维度:

  • 垂直维度: 控制用户可以访问哪些url的权限
  • 水平维度: 控制用户访问特定url,获取哪些数据的权限(e.g. 普通用户、管理员、超级管理员访问同一url,获取的数据是不同的)

Web权限控制方案List

  • 前后端不分离:以Java为例,后端通过jsp、freemark、thmeleaf等模板来渲染相应权限的数据,渲染完呈现在浏览器端
  • 前后端分离:

    • SPA单页面应用,路由由前端控制,前端通过js控制hash路由的权限
    • SSR服务端渲染,Node中间层做代理路由,判断权限渲染特定的路由至浏览器端

SPA前端权限控制方案

SPA: 单页Web应用(single page web application)将所有web活动局限于一个html页面中,利用js通过hash或者浏览器history api来实现无刷新路由跳转,前后端通过ajax数据通信,避免了浏览器的刷新重新加载,为用户提供流程的操作体验。这意味着前端接管了路由层,需要通过调用前端自身的MVC模块,来渲染不同的页面。

Base on:

  • Vue 前端MVVM框架
  • Vuex 状态管理机
  • Vue-router 路由
  • Axios HTTP请求库

1.登陆事件Login

// 1.触发登陆事件
dispatch('login')
 
// actions
commit(types.LOGIN_SUCCESS, res.data.data)
...

2.获取Token,经Base64编码后存至sessionStorage

// mutations
const mutations = {
    [types.LOGIN_SUCCESS] (state, data) {
        state.authlock = false
    // 2.登陆成功回调拿到token,经Base64 编码后存入本地sessionStorage
        let token = Base64.encode(data + ':HIKDATAE')
        sessionStorage.setItem('userToken', token)
    // 路由跳转至目标页面
        router.push({name: 'xxx'})
    },
    [types.LOGOUT_SUCCESS] (state) {
        state.authlock = true
    // 登出成功回调,移除本地token
        sessionStorage.removeItem('userToken')
        router.push({name: 'Login'})
    }
}

3.所有HTTP Header Authorization 加上编码后的token(前后端可约定规则)

// Axios 请求钩子(request)
axios.interceptors.request.use(req => {
    let token = sessionStorage.getItem('user')     
    if (token) {         
        // 3.token 存在,则在之后所有请求的http请求头 Authorization 带上base64编码后的token,后台拿到token后进行验证权限         
        req.headers.Authorization = `Basic ${token}`     
    }
    req.data = qs.stringify(req.data)     
    return req 
}, error => {
    return Promise.reject(error) 
})
浏览器http header

FEAuths

4.请求拦截:后台拿到token后对每个请求进行校验,若校验失败返回401,前端response钩子里统一catch error 跳转至登陆页面

// Axios 请求钩子(response)
axios.interceptors.response.use(res => {
    return res
}, error => {
    if (error.response) {
        switch (error.response.status) {
        // 4.所有接口response校验钩子,若token检验失败,后台返回 401 error code, 清除token信息并跳转到登录页面
            case 401:
                store.commit(types.LOGOUT)
                router.replace({
                    path: '/login'
        })
    }
    }
    return Promise.reject(error)
})

5.路由跳转拦截:任意路由跳转时,在路由beforeEach钩子里校验本地是否存在token,若没有,则跳转至登陆页面

// 路由钩子(每个路由跳转前调起beforeEach钩子)
router.beforeEach((to, from, next) => {
  if (to.path === '/login') {
    sessionStorage.removeItem('userToken')
  }
  let user = sessionStorage.getItem('userToken')
  if (!user && to.path !== '/login') {
    // 若本地token不存在,则任意路由跳转的时候,重定向至login 登陆页面
    next({ path: '/login' })
  } else {
    next()
  }
})

6.登出Logout:清楚本地sessionStorage的token信息

// mutations
const mutations = {
    ...
    [types.LOGOUT_SUCCESS] (state) {
        state.authlock = true
    // 登出成功回调,移除本地token
        sessionStorage.removeItem('userToken')
    router.push({name: 'Login'})
    }
}

流程示意图如下:

FEAuth

写完才觉得,什么才是真正的安全权限?任重而道远。。。
查看原文

方始终 收藏了文章 · 2018-01-30

条理清晰的JavaScript面向对象

最近一直在搞基础的东西,弄了一个持续更新的github笔记,可以去看看,诚意之作(本来就是写给自己看的……)链接地址:Front-End-Basics

此篇文章的地址:面向对象

基础笔记的github地址:https://github.com/qiqihaobenben/Front-End-Basics ,可以watch,也可以star。


正文开始……


JavaScript的面向对象

JavaScript的对象

对象是JavaScript的一种数据类型。对象可以看成是属性的无序集合,每个属性都是一个键值对,属性名是字符串,因此可以把对象看成是从字符串到值的映射。这种数据结构在其他语言中称之为“散列(hash)”、“字典(dictionary)”、“关联数组(associative array)”等。

原型式继承:对象不仅仅是字符串到值的映射,除了可以保持自有的属性,JavaScript对象还可以从一个称之为原型的对象继承属性,对象的方法通常是继承的属性,这是JavaScript的核心特征。

JavaScript对象是动态的—可以新增属性也可以删除属性,但是他们常用来模拟静态以及静态类型语言中的“结构体”

创建对象

1、对象直接量

创建对象最简单的方式就是在JavaScript代码中使用对象直接量。

var book = {
            "main title": 'guide',  //属性名字里有空格,必须加引号
            "sub-title": 'JS',  //属性名字里有连字符,必须加引号
            for: 'development',  //for是关键字,不过从ES5开始,作为属性名关键字和保留字可以不加引号
            author: {
                firstname: 'David',  //这里的属性名就都没有引号
                surname: 'Flanagan'
            }
        }

注意: 从ES5开始,对象直接量中的最后一个属性后的逗号将被忽略。

扩展: [JavaScript中的关键字和保留字
](http://blog.mingsixue.com/it/...

2、通过new创建对象

new 运算符创建并初始化一个新对象。关键字new后跟一个函数调用。这里的函数称做构造函数(constructor),构造函数用以初始化一个新创建的对象。JavaScript中的数据类型都包含内置的构造函数。

var o = new Object(); //创建一个空对象,和{}一样。
var arr = new Array(); //创建一个空数组,和[]一样。

扩展 1:new

new 是一个一元运算符,专门运算函数的。new后面调用的函数叫做构造函数,构造函数new的过程叫做实例化。
当new去调用一个函数 : 这个时候函数中的this就指向创建出来的对象,而且函数的的返回值直接就是this(隐式返回)
有一个默认惯例就是构造函数的名字首字母大写。

注意:
当return的时候,如果是后面为简单类型,那么返回值还是这个对象;
如果return为对象类型,那么返回的就是return后面的这个对象。

扩展 2:基本类型和对象类型(复杂类型)的区别

赋值:
基本类型 : 赋值的时候只是值的复制
对象类型 : 赋值不仅是值的复制,而且也是引用的传递(可以理解为内存地址)可以理解为赋址。

比较相等
基本类型 : 值相同就可以
对象类型 : 值和引用都相同才行

扩展 3:原型 prototype

每一个JavaScript对象(null除外)都和另一个对象相关联,这个对象就是原型,每一个对象都从原型继承属性。

3、Object.create()

Object.create() 这个方法是ES5定义的,它创建一个新对象,其中第一个参数是这个对象的原型。第二个参数是可选参数,用以对对象属性进行进一步描述。

可以通过传入参数 null 创建一个没有原型的新对象,不过这个新对象不会继承任何东西,甚至不包括基础方法。
var o = Object.create(null); //o不会继承任何属性和方法,空空的。

如果想创建一个普通的空对象,需要传入Object.prototype
var o = Object.create(Object.prototype); //o相当于{}

对象属性的获取和设置

可以通过点(.)或方括号([])运算符来获取和设置属性的值。

var author = book.author;
var title = book["main title"];

在JavaScript中能用 . 连接的都可以用 []连接。有很多 . 运算符不能用的时候,就需要用[]代替。
1、在属性名可变的情况下用[]

function getAttr (obj, attr) {
    console.log(obj[attr])
}

2、属性名有空格或者连字符等时用[]
var title = book["main title"];

删除属性

delete运算符可以删除对象的属性。
delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性,如果删除的属性是个对象,那么这个对象的引用还是存在的。

var a = {b:{c:1}};
var b = a.b;
console.log(b.c); // 1
console.log(a.b); // {c:1}
delete a.b;
console.log(b.c); // 1
console.log(a.b); //undefined

delete只能删除自有属性,不能删除继承属性。

返回值

返回值为true

当delete表达式删除成功或没有任何副作用(比如删除不存在的属性),或者delete后不是一个属性访问表达式,delete会返回 true

var a = {b:{c:1}};
console.log(delete a.b);
console.log(delete a.b);
console.log(delete a.toString);
console.log(delete 1);

以上都会打印true
返回值为false

delete不能删除那些可配置性为false的属性,例如某些内置对象的属性是不可配置的,通过变量声明和函数声明创建的全局对象的属性。

var a = {};
Object.defineProperty(a,'b',{
    value:1,
    configurable: false // 设置为不可配置
})
console.log(delete a.b)
console.log(delete Object.prototype)
var x = 1;
console.log(delete this.x);
console.log(delete x)

以上打印都为false

检测属性

in 运算符

in 运算符的左侧是属性名(字符串),右侧是对象。如果对象的自有属性或继承属性中包含这个属性则返回true。

var a = {b:1};
console.log('a' in window); // true 声明的全局变量'a'是window的属性
console.log('b' in a); // true 'b'是a的属性
console.log('toString' in a); // true a继承了toString属性
console.log('c' in a); // false 'c'不是a的属性

跟in运算符类似的,还可以用"!=="判断一个属性是否是undefined,但是有一种场景只能使用in运算符,in可以区分不存在的属性和存在但值为undefined的属性。

var a = {b:undefined};
console.log(a.b !== undefined); //false
console.log(a.c !== undefined); //false
console.log('b' in a); //true
console.log('c' in a); //false

hasOwnProperty

对象的hasOwnProperty()方法用来检测给定的名字是否是对象的自有属性。对于继承属性它将返回false

var a = {b:1};
console.log(a.hasOwnProperty('b')); //true
console.log(a.hasOwnProperty('c')); //false
console.log(a.hasOwnProperty('toString')); //false toString是继承属性

propertyIsEnumerable

对象的propertyIsEnumerable()方法只有检测到是自身属性(不包括继承的属性)且这个属性的可枚举性为true时它才返回true。

var a = {b:1};
console.log(a.propertyIsEnumerable('b'));
console.log(a.propertyIsEnumerable('toString'));

包装对象

当使用原始类型的值(string、number、boolean),在调用对应属性和方法的时候,内部会自动转成对应的对象。隐式创建的这个对象,就成为包装对象。
基本类型都有自己对应的包装对象 : String Number Boolean

包装对象的特点
隐式创建对象后,可以调用对应的属性和方法
使用后,立马销毁,所以不能给原始类型的值添加属性和方法

其过程举例:str.substring - > new String(1234) - > 找到String的substring -> 将new String销毁

对象方法和属性的汇总

Object静态方法

Object的实例方法(定义在Object.prototype上的)


面向对象

编码思想

两种编程方式:
(1)、面向过程
(2)、面向对象

两者的区别:
面向过程:关注实现过程和每一步的实现细节。
面向对象:关注特征和功能。

面向对象编程

通俗点,用对象的思想写代码就是面向对象编程。

基本特征

1、抽象:抓住核心问题(简单理解为抽出像的部分;将相同或表现与问题相关特征的内容提取出来。)
其核心:抽出、抽离,将相同的部分(可能会维护、会迭代、会扩展)的代码抽离出来形成一类

2、封装:就是将类的属性包装起来,不让外界轻易知道它内部的具体实现;只提供对外接口以供调用

3、继承:从已有对象上继承出新的对象

4、多态:一个对象的不同形态

面向对象的好处

1、代码的层次结构更清晰
2、更容易复用
3、更容易维护
4、更容易扩展

面向对象相关的属性和概念

__proto__
属性原型链,实例对象与原型之间的连接,叫做原型链。

对象身上只有 __proto__ 构造函数身上有prototype也有 __proto__


constructor
返回创建实例对象的构造函数的引用,每个原型都会自动添加constructor属性,for..in..遍历原型是找不到这个属性的。
var a = new A();
console.log(a.constructor == A) //true


hasOwnProperty
可以用来判断某属性是不是这个构造函数的内部属性(不包括继承的)

语法: obj.hasOwnProperty(prop) 返回Boolean

function A (){
    this.b = 1;
}
var a = new A();
console.log(a.hasOwnProperty('b'));  //打印true 
console.log(a.hasOwnProperty('toString')); //toString是继承属性 打印 false
console.log(a.hasOwnProperty('hasOwnProperty')); //同上,打印false


instanceof
二元运算符,用来检测一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

语法: object instanceof constructor 即检测 constructor.prototype 是否存在于参数 object 的原型链上。

// 定义构造函数
function C(){} 
function D(){} 

var o = new C();
o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype
o instanceof D; // false,因为 D.prototype不在o的原型链上
o instanceof Object; // true,因为Object.prototype.isPrototypeOf(o)返回true
C.prototype instanceof Object // true,同上


toString
返回一个表示该对象的字符串

作用:
1、进行数字之间的进制转换

例如:var num = 255;
alert( num.toString(16) ); //结果就是'ff'

2、利用toString做类型的判断

例如:var arr = [];
alert( Object.prototype.toString.call(arr) == '[object Array]' );     弹出true
Object.prototype.toString.call()    得到是类似于'[object Array]'  '[object Object]'

面向对象的写法历程

1、原始模式

假如我们有一个对象是狗的原型,这个原型有“名字”和“颜色”两个属性。

var Dog = {
    name: '',
    color: ''
}

根据这个原型对象,我们要生成一个实例对象如下

var hashiqi = {}; //创建空对象,之后根据原型对象的相应属性赋值
hashiqi.name = 'hashiqi';
hashiqi.color = 'blackandwhite';

缺点:
1、如果要生成多个实例对象,要重复写多次。
2、实例和原型之间没有联系。

2、工厂模式

上面原始模式有一个缺点是要很麻烦的写很多重复的代码,我们可以写一个函数来解决代码重复的问题。

function Dog(name, color) {
    var obj = {};
    obj.name = name;
    obj.color = color;
    return obj;
}

var hashiqi = Dog('hashiqi', 'blackandwhite');
var jinmao = Dog('jinmao', 'yellow');

这种方式只是解决了代码重复的问题,但是生成的实例跟原型还是没有联系,而且hashiqijinmao也没有联系,不能反映出他们是同一个原型对象的实例。

3、构造函数模式

用来创建对象的函数,叫做构造函数,其实就是一个普通函数,但是默认函数名首字母大写,对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。

function Dog(name, color) {
    this.name = name;
    this.color = color;
}

var hashiqi = new Dog('hashiqi', 'blackandwhite');
var jinmao = new Dog('jinmao', 'yellow');
console.log(hashiqi.name); //hashiqi
console.log(jinmao.name); //jinmao

hasiqi 和 jinmao有一个共同的构造函数 hashiqi.constructor === jinmao.constructor 是true

有以下几种方法可以验证原型对象与实例对象的关系:

hashiqi instanceof Dog; // true

Object.getPrototypeOf(hashiqi) === Dog.prototype // true

Dog.prototype.isPrototypeOf(hashiqi) // true

缺点:
构造函数解决了代码重复和实例与原型之间的联系,但是存在一个浪费内存的问题。比如远行对象有一些不变的属性和通用的方法,这样没生成一个实例,都必须为重复的东西多占一些内存。

扩展

我们可以尝试实现new运算符的逻辑如下:

function New(func) {
    var obj = {};

    //判断构造函数是否存在原型,如果有实例的__proto__属性就指向构造函数的prototype
    if(func.prototype !== undefined) {
        obj.__proto__ = func.prototype;
    }

    // 模拟出构造函数内部this指向实例的过程,注意,我们会拿到构造函数的返回值
    var res = func.apply(obj, Array.from(arguments).slice(1));

    // 正常构造函数是不需要显式声明返回值的,默认的返回值是生成的实例,但是一旦在构造函数中return 一个不是对象或者函数,就会改变构造函数的默认的返回值,其他的类型是不变的
    if(typeof res === 'object' && res !== null || typeof res === 'function') {
        return res;
    }

    return obj;
}

var taidi = New(Dog, 'taidi', 'gray');

注意:
正常的构造函数是不需要自己写return 的,如果写了,当return的时候,如果是后面为简单类型,那么返回值还是构造函数生成的实例。如果return为对象类型或者函数,那么返回的就是return后面的这个对象或者函数。

4、prototype模式

每一个构造函数都有 prototype 属性,这个属性指向的是一个对象,这个对象的所有属性和方法,都会被构造函数的实例继承。
基于这个属性,我们就可以有选择性的将一些通用的属性和方法定义到 prototype 上,每一个通过 new 生成的实例,都会有一个 __proto__ 属性指向构造函数的原型即 prototype ,这样我们定义到构造函数原型对象的属性和方法,就会被每一个实例访问到,从而变成公用的属性和方法。

function Dog(name, color) {
    this.name = name;
    this.color = color;
}
Dog.prototype.say = function () {
    console.log("汪汪");
}

var hashiqi = new Dog('hashiqi', 'blackandwhite');
var jinmao = new Dog('jinmao', 'yellow');

hashiqi.say(); // 汪汪
jinmao.say(); // 汪汪
console.log(hashiqi.say === jinmao.say); // true

注意:当实例对象和原型对象有相同的属性或者方法时,会优先访问实例对象的属性或方法。

面向对象的继承

1、构造函数内部的属性和方法继承

使用call或apply方法,将父对象的构造函数绑定在子对象上。

//父类
function Animal() {
    this.species = '动物';
}

//子类
function Dog(name, color) {
    Animal.call(this);
    this.name = name;
    this.color = color;
}

var hashiqi = new Dog('hashiqi', 'blackandwhite');
console.log(hashiqi.species); //动物

2、prototype相关的继承

子类的prototype指向父类生成实例
function Animal() {};
Animal.prototype.species = '动物';
function Dog(name, color) {
    this.name = name;
    this.color = color;
}
Dog.prototype = new Animal();
//只要是prototype被完全覆盖,都得重写constructor。
Dog.prototype.constructor = Dog;
var hashiqi = new Dog('hashiqi', 'blackandwhite');

缺点: 每一次继承都得生成一个父类实例,比较占内存。


利用空对象作为中介
function Animal() {}
Animal.prototype.species = '动物';
function Dog(name, color) {
    this.name = name;
    this.color = color;
}
//Middle生成的是空实例(除了__proto__),几乎不占内存
function Middle() {}
Middle.prototype = Animal.prototype;
Dog.prototype = new Middle();
Dog.prototype.constructor = Dog;
var hashiqi = new Dog('hashiqi', 'blackandwhite');
console.log(hashiqi.species);

几个月前在 CSDN 面试的时候,我说了这种继承方式,面试官就纠结这样修改子类的prototype不会影响父类么?是真的不会影响的,因为子类的prototype是指向Middle构造函数生成的实例,如果真的有心要改,得Dog.prototype.__proto__这么着来改。


Object.create()
function Animal() {}
Animal.prototype.species = '动物';
function Dog(name, color) {
    this.name = name;
    this.color = color;
}
Dog.prototype = Object.create(Animal.prototype,{
    constructor: {
        value: Dog
    }
})

var hashiqi = new Dog('hashiqi','blackandwhite');
console.log(hashiqi.species); //动物

3、拷贝继承

浅拷贝
function Animal() {}
Animal.prototype.species = '动物';
function Dog(name, color) {
    this.name = name;
    this.color = color;
}
function extend(child, parent) {
    var c = child.prototype;
    var p = parent.prototype;
    for(key in p) {
        c[key] = p[key]
    }
}
extend(Dog, Animal);
var hashiqi = new Dog('hashiqi', 'blackandwhite');
console.log(hashiqi.species) // 动物


深拷贝
function deepCopy(parent, child) {
    var child = child || {};
    for(key in parent) {
        if(typeof parent[key] === 'object') {
            child[key] = parent[key].constructor === Array?[]:{};
            deepCopy(parent[key],child[key])
        } else {
            child[key] = parent[key];
        }
    }
    return child;
}

ES6的面向对象

上面所说的是JavaScript语言的传统方法,通过构造函数,定义并生成新的对象。
ES6中提供了更接近传统语言的写法,引入了Class(类)的概念,通过class关键字,可以定义类。

语法

ES6的类完全可以看成是构造函数的另外一种写法。

var method = 'say';
class Dog {
    constructor (name,color) {
        this.name = name;
        this.color = color;
    }
    //注意,两个属性之间跟对象不同,不要加逗号,并且类的属性名可以使用变量或者表达式,如下
    [method] () {
        console.log('汪汪');
    }
}
console.log(typeof Dog); // function 类的数据类型就是函数
console.log(Dog === Dog.prototype.constructor); // true 类本身就是构造函数

既然是构造函数,所以在使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。

var hashiqi = new Dog('hashiqi', 'blackandwhite');
console.log(hashiqi.color); // blackandwhite

//上面采用表达式声明的类的属性可以用一下两种方式调用
hashiqi[method](); // 汪汪
hashiqi.say(); // 汪汪

注意:
1、先声明定义类,再创建实例,否则会报错
class 不存在变量提升,这一点与ES5的构造函数完全不同

new Dog('hashiqi','blackandwhite')
class Dog {
    constructor (name,color) {
        this.name = name;
        this.color = color;
    }
}
//Uncaught ReferenceError: Dog is not defined
//上面代码,Dog类使用在前,定义在后,因为ES6不会把类的声明提升到代码头部,所以报错Dog没有定义。

2、必须使用new关键字来创建类的实例对象
类的构造函数,不使用new是没法调用的,会报错。 这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

class Dog {
    constructor (name,color) {
        this.name = name;
        this.color = color;
    }
}
Dog(); // Uncaught TypeError: Class constructor Dog cannot be invoked without 'new'

3、定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。并且,方法之间不需要逗号分隔,加了会报错。

属性概念

constructor 构造函数

构造方法constructor是一个类必须要有的方法,默认返回实例对象;创建类的实例对象的时候,会调用此方法来初始化实例对象。如果你没有编写constructor方法,执行的时候也会被加上一个默认的空的constructor方法。

constructor方法是必须的,也是唯一的,一个类体不能含有多个constructor构造方法。

class Dog {
    constructor (name,color) {
        this.name = name;
        this.color = color;
    }
    //定义了两个constructor,所以会报错
    constructor () {
        
    }
}
new Dog('hashiqi', 'blackandwhite')
//Uncaught SyntaxError: A class may only have one constructor


Class表达式

与函数一样,类可以使用表达式的形式定义。

const Hashiqi = class Dog {
    constructor (name,color) {
        this.name = name;
        this.color = color;
    }
    getName () {
        //此处的Dog就是Dog构造函数,在表达式形式中,只能在构造函数内部使用
        console.log(Dog.name);
    }
}
var hashiqi = new Hashiqi('hashiqi', 'blackandwhite'); // 真正的类名是Hashiqi
var jinmao = new Dog('jinmao', 'yellow'); // 会报错,Dog没有定义

通常我们的表达式会写成如下,省略掉类后面的名称

const Hashiqi = class {
    constructor (name,color) {
        this.name = name;
        this.color = color;
    }
}
var hashiqi = new Hashiqi('hashiqi', 'blackandwhite');


实例方法和静态方法
实例化后的对象才可以调用的方法叫做实例方法。
直接使用类名即可访问的方法,称之为“静态方法”

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

class Dog {
    constructor (name,color) {
        this.name = name;
        this.color = color;
    }
    static say () {
        console.log('汪汪');
    }
}
Dog.say(); //汪汪

静态方法和实例方法不同的是:静态方法的定义需要使用static关键字来标识,而实例方法不需要;此外,静态方法通过类名来的调用,而实例方法通过实例对象来调用。

类的继承

extends

类之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

class Dog extends Animal{}

extends的继承目标
extends关键字后面可以跟多种类型的值,有三种特殊情况

1、子类继承Object类

class A extends Object {}
console.log(A.__proto__ === Object) //true
console.log(A.prototype.__proto__ == Object.prototype) //true
//这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例。

2、不存在继承

class A {}

console.log(A.__proto__ === Function.prototype) // true
console.log(A.prototype.__proto__ === Object.prototype) // true
//这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Funciton.prototype。
//但是,A调用后返回一个空对象(即Object实例),所以A.prototype.__proto__指向构造函数(Object)的prototype属性。

3、子类继承null

class A extends null {}
console.log(A.__proto__ === Function.prototype) //true
console.log(A.prototype) //只有一个constructor属性,没有__proto__属性
这种情况与第二种情况非常像。A也是一个普通函数,所以直接继承Funciton.prototype。
但是,A调用后返回的对象不继承任何方法,所以没有__proto__这属性


super

uper这个关键字,既可以当作函数使用,也可以当作对象使用。

1、super作为函数调用时,代表父类的构造函数。作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。

2、super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

class Animal {
    constructor (name) {
        this.name = name;
        this.species = '动物';
    }
    say (){
        return this.species;
    }
}
class Dog extends Animal{
    constructor (name, color) {
        // 只要是自己在子类中定义constructor,必须调用super方法,否则新建实例会报错
        //super作为函数调用,只能用在子类的constructor中
        super(name);
        this.color = color;
    }
    getInfo () {
        //普通方法中,super指向父类的原型对象
        console.log(super.say()+': '+this.name +','+this.color);
    }
}
var hashiqi = new Dog('hashiqi', 'blackandwhite');
hashiqi.getInfo() //动物:hashiqi,balckandwhite

注意:
1、子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

2、在子类的普通方法中,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。

3、使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。

查看原文

方始终 收藏了文章 · 2018-01-30

你不知道的前端算法之热力图的实现

本文作者:TalkingData 可视化工程师李凤禄

编辑:Aresn

欢迎加入 QQ 群参与技术讨论:618308202

inMap 是一款基于 canvas 的大数据可视化库,专注于大数据方向点线面的可视化效果展示。目前支持散点、围栏、热力、网格、聚合等方式;致力于让大数据可视化变得简单易用。

GitHub 地址:https://github.com/TalkingData/inmap (点个 Star 支持下作者吧!)

热力图这个名字听起来很高大上,其实等同于我们常说的密度图。

image

如图表示,红色区域表示分析要素的密度大,而蓝色区域表示分析要素的密度小。只要点密集,就会形成聚类区域。
看到这么炫的效果,是不是自己也很想实现一把?接下来手把手实现一个热力(带你装逼带你飞、 哈哈),郑重声明:下面代码片段均来自 inMap

准备数据

inMap 接收的是经纬度数据,需要把它映射到 canvas 的像素坐标,这就用到了墨卡托转换,墨卡托算法很复杂,以后我们会有单独的一篇文章来讲讲他的原理。经过转换,你得到的数据应该是这样的:

[
  {
    "lng": "116.395645",
    "lat": 39.929986,
    "count": 6,
    "pixel": { //像素坐标
      "x": 689,
      "y": 294
    }
  },
  {
    "lng": "121.487899",
    "lat": 31.249162,
    "count": 10,
    "pixel": { //像素坐标
      "x": 759,
      "y": 439
    }
  },
  ...
]

好了,我们得到转换后的像素坐标数据(x、y),就可以做下面的事情了。

创建 canvas 渐变填充

创建一个由黑到白的渐变圆

let gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
gradient.addColorStop(0, 'rgba(0,0,0,1)');
gradient.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = gradient;
ctx.arc(x, y, radius, 0, Math.PI * 2, true);
  • createRadialGradient() 创建线性的渐变对象
  • addColorStop() 定义一个渐变的颜色带

效果如图:
image
那么问题就来了,如果每个数据权重值 count 不一样,我们该如何表示呢?

设置 globalAlpha

根据不同的count值设置不同的Alpha,假设最大的count的Alpha等于1,最小的count的Alpha为0,那么我根据count求出Alpha。

let alpha = (count - minValue) / (maxValue - minValue);

然后我们代码如下:

drawPoint(x, y, radius, alpha) {
    let ctx = this.ctx;
    ctx.globalAlpha = alpha; //设置 Alpha 透明度
    ctx.beginPath();
    let gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
    gradient.addColorStop(0, 'rgba(0,0,0,1)');
    gradient.addColorStop(1, 'rgba(0,0,0,0)');
    ctx.fillStyle = gradient;
    ctx.arc(x, y, radius, 0, Math.PI * 2, true);
    ctx.closePath();
    ctx.fill();
}

效果跟上一个截图有很大区别,可以对比一下透明度的变化。
image
(这么黑乎乎的一团,跟热力差距好大啊)

image

重置 canvas 画布颜色

  • getImageData() 复制画布上指定矩形的像素数据
  • putImageData() 将图像数据放回画布:

getImageData()返回的数据格式如下:

{
  "data": {
    "0": 0,   //R
    "1": 128, //G
    "2": 0,   //B
    "3": 255, //Aplah
    "4": 0, //R
    "5": 128, //G
    "6": 0,  //B
    "7": 255, //Aplah
    "8": 0,
    "9": 128,
    "10": 0,
    "11": 255,
    "12": 0,
    "13": 128,
    "14": 0,
    "15": 255,
    "16": 0,
    "17": 128,
    "18": 0,
    "19": 255,
    "20": 0,
    "21": 128,
    "22": 0
    ...

返回的数据是一维数组,每四个元素表示一个像素(rgba)值。

实现热力原理:读取每个像素的alpha值(透明度),做一个颜色映射。

代码如下:

let palette = this.getColorPaint(); //取色面板
let img = ctx.getImageData(0, 0, container.width, container.height);
    let imgData = img.data;
    let max_opacity = normal.maxOpacity * 255;
    let min_opacity = normal.minOpacity * 255;
    //权重区间
    let max_scope = (normal.maxScope > 1 ? 1 : normal.maxScope) * 255;
    let min_scope = (normal.minScope < 0 ? 0 : normal.minScope) * 255;
    let len = imgData.length;
    for (let i = 3; i < len; i += 4) {
        let alpha = imgData[i]; 
        let offset = alpha * 4;
        if (!offset) {
            continue;
        }
        //映射颜色
        imgData[i - 3] = palette[offset];
        imgData[i - 2] = palette[offset + 1];
        imgData[i - 1] = palette[offset + 2];

        // 范围区间
        if (imgData[i] > max_scope) {
            imgData[i] = 0;
        }
        if (imgData[i] < min_scope) {
            imgData[i] = 0;
        }

        // 透明度
        if (imgData[i] > max_opacity) {
            imgData[i] = max_opacity;
        }
        if (imgData[i] < min_opacity) {
            imgData[i] = min_opacity;
        }
    }
    //将设置后的像素数据放回画布
ctx.putImageData(img, 0, 0, 0, 0, container.width, container.height);

创建颜色映射,一个好的颜色映射决定最终效果。
inMap 创建一个长256px的调色面板:

let paletteCanvas = document.createElement('canvas');
let paletteCtx = paletteCanvas.getContext('2d');
paletteCanvas.width = 256;
paletteCanvas.height = 1;
let gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);

inMap 默认颜色如下:

this.gradient = {
    0.25: 'rgb(0,0,255)',
    0.55: 'rgb(0,255,0)',
    0.85: 'yellow',
    1.0: 'rgb(255,0,0)'
};

将gradient颜色设置到调色面板对象中

for (let key in gradient) {
    gradient.addColorStop(key, gradientConfig[key]);
}

返回调色面板的像素点数据:

return paletteCtx.getImageData(0, 0, 256, 1).data;

创建出来的调色面板效果图如下:(看起来像一个渐变颜色条)

image

最终我们实现的热力图如下:

image

下节预告

下一节,我们将重点介绍 inMap 文字避让算法的实现。

查看原文

认证与成就

  • 获得 165 次点赞
  • 获得 27 枚徽章 获得 3 枚金徽章, 获得 6 枚银徽章, 获得 18 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-02-21
个人主页被 1.2k 人浏览