胖蔡叨叨叨
你听我说

Babel 编译使用基础(1)

胖蔡阅读(310)

前端项目工程化不断迭代更新,新的语法功能、特性让代码的书写变得更加舒服、简洁、易读、可维护。然而,对于浏览器的语法兼容性,却并不能很好的完美兼容新提出的语法、特性,不同版本的浏览器对于兼容语法方面也有着较大的差异。这时候,babel的出现可以说是应运而生,通过对于es2015+的语法进行编译适配,可以让我们更好的关注于业务本省、用最简洁的的代码完成需求,而不需要关注不同版本之间的兼容问题。Babel可以在不同测场景有着不同的使用,如下介绍的则是在cli(命令行)环境下,如何使用babel为我们的项目实现编译兼容。

安装babel

$ npm init
$ npm install --save-dev @babel/core @babel/cli
  • @babel/cli: babel自带的cli工具,可通过命令编译文件
  • @babel/core: babel功能核心库,babel转换核心库,可做一些中间操作

创建配置文件

配置文件用于配置babel的部分预设功能,常用的配置文件如babel.config.jsonbabel.config.js.babelrc

// babel.config.json
{
  "presets": ["@babel/preset-env"] // 启动 es6语法转换功能
}

// babel.config.js
const presets =   ["@babel/preset-env"];
module.exports = { presets };

如上配置需要安装 @babel/preset-env库,

$ npm install --save-dev @babel/preset-env

创建一个js文件

如下文件使用es6语法、api书写,通过对这个js文件编译来观察babel编译的效果。

// Expression bodies
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);

// Statement bodies
nums.forEach(v => {
if (v % 5 === 0)
fives.push(v);
});

// Lexical this
var bob = {
_name: "Bob",
_friends: [],
printFriends() {
this._friends.forEach(f =>
console.log(this._name + " knows " + f));
}
};

// Lexical arguments
function square() {
let example = () => {
let numbers = [];
for (let number of arguments) {
numbers.push(number * number);
}

return numbers;
};

return example();
}

square(2, 4, 7.5, 8, 11.5, 21); // returns: [4, 16, 56.25, 64, 132.25, 441]

编译js

编译过程需要配合@babel/cli 来实现,可以分为如下几个步骤:

  • 添加编译脚本
    在package.json中添加:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build:babel": "babel src -d lib"
},
  • -运行编译脚本
$ npm run build:babel

  • 编译结果
"use strict";
// lib/syntax.js
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
// Expression bodies
var odds = evens.map(function (v) { return v + 1;});
var nums = evens.map(function (v, i) {return v + i;}); // Statement bodies
nums.forEach(function (v) {
if (v % 5 === 0) fives.push(v);
}); // Lexical this
var bob = {
_name: "Bob",
_friends: [],
printFriends: function printFriends() {
var _this = this;
this._friends.forEach(function (f) {
return console.log(_this._name + " knows " + f);
});
}
}; // Lexical arguments
function square() {
var _arguments = arguments;
var example = function example() {
var numbers = [];
var _iterator = _createForOfIteratorHelper(_arguments),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var number = _step.value;
numbers.push(number * number);
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return numbers;
};
return example();
}
square(2, 4, 7.5, 8, 11.5, 21); // returns: [4, 16, 56.25, 64, 132.25, 441]

上面可以发现,使用@babel/preset预设编译可以完成es6的语法代码的转译(let、var、for…in、for…of等),但对于es6中新增的api功能并不能完成编译(Array.map、Array.forEach、Promise等)。为此,我们需要对于es6中新增的api进行单独的转译(本篇暂不介绍)。

源码示例:GITHUB

Vue 插槽(slot)的类型与使用

胖蔡阅读(334)

Vue slot是组件内容分发的出口,其设计规范源于:Web Components规范草案。一般的,基于Slot使用场景我们可以将其分为三大类,其中各个类型之间又存在着依次聚合的关系。

默认插槽

常见适用于静态、或者无强联系、分类布局的组件中。如网站底部的footer组件,我们会有些基础的元素,如copyright、开发公司、地址等,但还存在一些不确定是否公用的东西,如时间、统计等,这时,我们就可以通过slot开发一个分发的出口,让具体场景使用的开发人员自己决定是否使用扩展分发已经如何扩展的问题。默认插槽的使用方式如下:

// 子组件 Aritcile.vue

<template>
<div>
 <p> 这时子组件的标题 </p>
 <slot> <!--这是子组件的段落 --></slot>
</div>
</template>

<script>
.......
</script>

// 父组件 detail.vue

<template>
  <p> 父组件的标题 </p>
  <article>
     <p> 这里是需要分发的子组件的段落内容  </p>  
  </article>
</template>

<script>
import Aritcile from './Aritcile';

export default {
    components:{
         Aritcile,
    }
}

</script>

如此显示内容为:

父组件的标题
这时子组件的标题
这里是需要分发的子组件的段落内容

具名插槽

默认插槽在常见的常见基本使用,但存在需要多组件的多个部分进行自定义分发时,默认插槽的方式就不太适用了。因为无法指定位置不确定需要将内容插入到那个slot下。这时,我们就需要通过给slot一个名字来实现指定位置的插入。常见的场景如页面布局,我们需要将一个页面分为三个部分:头部、底部、内容展示区域,这时我们就需要给对应的slot进行命名。使用如下:


<!-- 子组件 page.vue  -->

<template>
  <div>
     <slot name="header"> </slot>
     <slot> </slot>
     <slot name="footer"> </slot>
  </div>


</template>
<script>
export default {

}
</script>

<!-- 父组件 dashborad.vue  -->

<template>
  <div>
   当前页面的显示内容

   <page>
      <div slot="footer"> 底部内容 </div>
      <div slot="header"> 頭部内容 </div>
      <div>打印正文内容 </div>
   </page>

  </div>
</template>
<script>

import Page from './page';
export default {
    components:{
        page
    }

}
</script>

显示内容如下:

当前页面的显示内容
頭部内容
打印正文内容
底部内容

作用域插槽

基于上述两种插槽的基础上,当我们需要在分发内容中获取到分发的参数内容,如对于一个列表而言,每个item在我们每次调用的时候都有可能不同,我们希望在列表组件中处理些通用逻辑,但具体item的样式我们需要足够的可扩展性,这是,我们就需要通过分发slot出口及其item的数据来实现插槽的功能,这就是作用域插槽。下面,通过一个列表的示例来了解。

<!-- 子组件 list.vue-->

<template>
  <div
    v-loading="loading"
  >
    <div v-if="dataSource && dataSource.length" class="content">
      <div class="content-list">
        <!--列表显示内容 -->
        <template v-for="item in dataSource">
          <slot name="listItem" v-bind="item" />
        </template>
      </div>

      <div class="content-pagination">
        <pagination
          :total="pagination.total"
          :page="pagination.current"
          :limit="pagination.pageSize"
          :layout="pagination.layout"
          :hideOnSinglePage="pagination.hideOnSinglePage"
          @paginationChange="handlePaginationChange"
        />

        <el-button
          v-if="btnName"
          type="primary"
          class="btn"
          :loading="btnLoading"
          @click.native="btnClick"
          >{{ btnName }}</el-button
        >
      </div>
    </div>
    <!-- 未读 -->
    <div v-else class="empty-box">
      <img
        src="@/assets/local_images/table_empty.png"
        class="empty-image"
        alt="logo"
      />
      <span>暂无相关数据</span>
    </div>
  </div>
</template>

<script>
import Pagination from "@/components/Pagination";

export default {
  name: "NotificationList",
  components: {
    Pagination,
  },

  data() {
    return {
      info: {}, // 数据源
      dataSource: [], // 数组数据源
      loading: false, // 加载中
      btnLoading: false,
      pagination: {
        /* 分页参数 */
        current: 1,
        pageSize: 10,
        pageSizeOptions: ["10", "20", "30", "50", "100"],
        layout: "total, prev, pager, next, sizes, jumper",
        total: 0,
      },
    };
  },
  methods: {
    setDateSource(data) {
      if (!data || !data.records || data.records.length === 0) {
        this.info = data || {}; // 设置数据源
        this.dataSource = []; // 数据源为空
        if (Number(this.pagination.current) === 1) {
          this.pagination.total = 0;
        } else {
          const currentPage = Number(this.pagination.current) - 1;
          const current = currentPage > 0 ? currentPage : 1;
          this.pagination.current = current;
          this.$emit("loadData", { current });
          //  这里需要重新加载数据
        }
      } else {
        console.log("get data:", data);
        // 数据不为空
        this.pagination.total = data.total;
        // 设置数据源
        this.dataSource = data.records.map((item) => {
          return {
            title: item.content,
            id: item.id,
            messageType: Number(item.messageType),
            date: item.sendTime,
            status: Number(item.status),
            domain: JSON.parse(item.messages),
          };
        });

        console.log("get pagination:", this.pagination);
      }
    },
    setLoading(flag) {
      this.loading = flag;
    },
    // 分页控制器变化
    handlePaginationChange(pageData) {
      this.pagination.current = pageData.page;
      this.pagination.pageSize = pageData.limit;
      this.$emit("loadData", { current: pageData.page, size: pageData.limit });
    },
    btnClick() {
      this.$emit("onSubmit"); // 按钮点击回调
    },
    setBtnLoading(flag) {
      // 设置btn的加载装填
      this.btnLoading = flag;
    },
  },
};
</script>




<! --父组件调用 -->

<template>
  <div class="container">

   <list>
       <template v-slot:listItem="record">
         <div>
            <div>
                <span class="time item-label">{{ record.time }}</span>
                <div class="title">{{ record.title }}</div>
            </div>

           <div class="second-line">
               <span class="date item-label">{{ record.date }}</span>
              <div class="domain">
                    {{ record.domainType}}
               <span class="domain-name">{{ record.domainName }}</span>
              </div>
            </div>

            <div v-if="record.approvalOpinion" class="third-line">
                审核意见:{{ record.approvalOpinion }}
            </div>
         </div>     
        </template>
   </list>
  </div>

</template>

<script>
import List from './list';
export default {
    components:{
        List
    }
}

</script>

如上,通过在组件list中 “ <slot name=“listItem” v-bind=“item” />“绑定数据,然后在父组件中使用 “ <template v-slot:listItem=“record”>“来获取数据。

Vue中 v-if和v-else 的使用

胖蔡阅读(360)

使用



<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    </head>
    <body>
        <div id="app">
            <p v-if="show">显示IF分支</p>
            <p v-else>IF分支隐藏了</p>
        </div>
    </body>
    <script>
        new Vue({
            el:'#app',
            data:{
                show:true,
            }
        })
    </script>

显示

显示IF分支

Vue的elementUI实现自定义主题

胖蔡阅读(388)

本文转载自:https://blog.csdn.net/wangcuiling_123/article/details/78513245

使用vue开发项目,用到elementUI,根据官网的写法,我们可以自定义主题来适应我们的项目要求,下面来介绍一下两种方法实现的具体步骤,(可以参考官方文档自定义主题官方文档),先说项目中没有使用scss编写,用主题工具的方法(使用的较多)

使用命令行主题工具

使用vue-cli安装完项目并引入element-ui(具体可参考第二种方法中的介绍)

安装工具

  1. 安装主题工具
npm i element-theme -g
  1. 安装chalk主题,可以从 npm 安装或者从 GitHub 拉取最新代码
  • 从 npm
npm i element-theme-chalk -D
  • 从 GitHub
npm i https://github.com/ElementUI/theme-chalk -D

初始化变量文件

et -i [可以自定义变量文件,默认为element-variables.scss]

> ✔ Generator variables file

这时根目录下会产生element-variables.scss(或自定义的文件),大致如下:

$--color-primary: #409EFF !default;
$--color-primary-light-1: mix($--color-white, $--color-primary, 10%) !default; /* 53a8ff */
$--color-primary-light-2: mix($--color-white, $--color-primary, 20%) !default; /* 66b1ff */
$--color-primary-light-3: mix($--color-white, $--color-primary, 30%) !default; /* 79bbff */
$--color-primary-light-4: mix($--color-white, $--color-primary, 40%) !default; /* 8cc5ff */
$--color-primary-light-5: mix($--color-white, $--color-primary, 50%) !default; /* a0cfff */
$--color-primary-light-6: mix($--color-white, $--color-primary, 60%) !default; /* b3d8ff */
$--color-primary-light-7: mix($--color-white, $--color-primary, 70%) !default; /* c6e2ff */
$--color-primary-light-8: mix($--color-white, $--color-primary, 80%) !default; /* d9ecff */
$--color-primary-light-9: mix($--color-white, $--color-primary, 90%) !default; /* ecf5ff */

$--color-success: #67c23a !default;
$--color-warning: #eb9e05 !default;
$--color-danger: #fa5555 !default;
$--color-info: #878d99 !default;

...

修改变量

直接编辑 element-variables.scss 文件,例如修改主题色为自己所需要的颜色(如: 紫色(purple))

$--color-primary: purple;

编译主题

修改完变量后,要编译主题(如果编译后,再次修改了变量,需要重新编译)

et

> ✔ build theme font
> ✔ build element theme

引入自定义主题

最后一步,将编译好的主题文件引入项目(编译的文件默认在根目录下的theme文件下,也可以通过 -o 参数指定打包目录),在入口文件main.js中引入

import '../theme/index.css'
import ElementUI from 'element-ui'
import Vue from 'vue'

Vue.use(ElementUI)

在项目中写些样式,看下主题色是否改变:(主题色变为紫色)

<div>
      <el-button>默认按钮</el-button>
      <el-button type="primary">主要按钮</el-button>
      <el-button type="success">成功按钮</el-button>
      <el-button type="info">信息按钮</el-button>
      <el-button type="warning">警告按钮</el-button>
      <el-button type="danger">危险按钮</el-button>
    </div>

直接修改element样式变量

在项目中直接修改element的样式变量,(前提是你的文档也是使用scss编写)

首先用vue-cli安装一个新项目:

  1. 安装vue:
npm i -g vue
  1. 在项目目录下安装vue-cli:
npm i -g vue-cli
  1. 基于webpack建立新项目( vue-project)
vue init webpack vue-project
  1. 依次输入以下命令行,运行vue-project
cd vue-project
npm i
npm run dev

安装elementUI以及sass-loader,node-sass(项目中使用scss编写需要依赖的插件)

  1. 安装element-ui
npm i element-ui -S
  1. 安装sass-loader,node-sass
npm i sass-loader node-sass -D

在这里说一下,不需要配置webpack.base.conf.js文件,vue-loader会根据不同类型文件来配置相应loader来打包我们的样式文件(感兴趣的可看下vue-loader的核心代码)

改变element样式变量

  1. 在src下建立element-variables.scss文件(名字可以自定义),写入如下代码:
/* 改变主题色变量 */
$--color-primary: teal;

/* 改变 icon 字体路径变量,必需 */
$--font-path: '../node_modules/element-ui/lib/theme-chalk/fonts';

@import "../node_modules/element-ui/packages/theme-chalk/src/index";
  1. 在入口文件main.js中引入上面的文件即可
import Vue from 'vue'
import Element from 'element-ui'
import './element-variables.scss'

Vue.use(Element)

看下效果吧,在文件里引入些样式看看,如button

<div>
      <el-button>默认按钮</el-button>
      <el-button type="primary">主要按钮</el-button>
      <el-button type="success">成功按钮</el-button>
      <el-button type="info">信息按钮</el-button>
      <el-button type="warning">警告按钮</el-button>
      <el-button type="danger">危险按钮</el-button>
    </div>

默认的颜色已经变为我们自定义的了,有其他的改变在element-variable.scss文件中改变变量即可

Gulp 通过gulp-preprocess实现简单的环境配置部署

胖蔡阅读(711)

通过Gulp可以对于前端代码进行一个代码的多页面构建,避免不必要的重复性工作,提高代码的可读性,为了实现通过参数控制不同环境的打包编译,我们引入了gulp-preprocess插件,该插件在gulp代码编译期间就实现了分环境配置的功能。

配置

  1. 安装gulp-preprocess插件
$ yarn add -D gulp-preprocess 
# or
$ npm install --save-dev gulp-preprocess 
  1. gulpfile.js配置
const preprocess = require('gulp-preprocess');

......
/* 打包babel的js文件 */
gulp.task('js:dev', async function () {
  return await gulp.src(['src/js/**/*.js'])
    .pipe(preprocess({
      context: {
        // 此处可接受来自调用命令的 NODE_ENV 参数,默认为 development 开发测试环境
        NODE_ENV: process.env.NODE_ENV || 'development',
      },
    }))
    .pipe(babel())
    .pipe(uglifyJs())
    .pipe(gulp.dest('dist/js'))
})

gulp.task('dev', gulp.series(gulp.parallel('js:dev', 'css:dev', 'img:dev',  'html:dev'), function (done) {
  done()
}))
  1. package.json 添加脚本配置

...
  "scripts": {
    "dev": "gulp dev",
  },

...

添加配置文件

//config.js gulp-preprocess 通过注解方式实现了编译配置

var config = {
    // @if NODE_ENV = 'production'
    apiUrl:'http://production.api.cn',
    // @endif
    // @if NODE_ENV = 'development'
    apiUrl:'http://development.api.cn',
    // @endif
}

window.config = config;


//request.js
 $.ajax({
    type: "POST",
    dataType: "json",
    url: config.apiUrl + url,
    success: function (res) {
        console.log('请求成功:',res)
    },
    error: function (error) {
            console.log('请求失败:',error)
    },
    });

运行

$yarn run dev
#or
$npm run dev

【JavaScript】Pseudo-classical模式

胖蔡阅读(317)

在pseudo-classical模式中,对象是通过构造函数创建的,并且它的方法是直接被放到prototype中的。

pseudo-classical模式也应用在一些框架中,例如Google Closure Library, Native JavaScript objects等。

 
Pseudo-class 声明

“pseudo-class”这个词其实不是很准确,因为与PHP, Java,C++等语言相比,javaScript事实上没有类,但是pseudo-class模式在某种程度上是与它们相近的。

这篇文章假设你已经对原型继承的机制很了解了,如果不了解请查看原型继承这篇文章。

一个pseudo-class由构造函数和方法组成。例如下面的 Animal pseudo-class,有一个sit方法和两个属性。

function Animal(name){    
this.name = name;
}
Animal.prototype = {
canWalk: true,
sit: function(){
this.canWalk = false;
alert(this.name + ' sits down.');
} }
var animal = new Animal('Pet');
alert(animal.canWalk); //true animal.sit(); //pet sits down alert(animal.canWalk); //false

1.   当new Animal(name) 被调用时,生成新对象的__proto__指向Animal.prototype,请看下面图片的左边部分。

2. animal.sit方法改变了实例中的animal.canWalk,因此该animal对象不能走,而其他对象仍可以走。

总结pseudo-class:

(1)方法和默认的属性是在原型中的。

(2)在prototype中的方法使用的this, 指的是当前对象,因为this的值仅仅依赖调用的上下文,因此animal.sit()将this设为animal。

在以上的两点中隐藏着一些使用的危险,请看下面的例子。

你是仓鼠农场的老大,让你下面的程序员接受一个任务去创建Hamster构造函数和原型。

Hamster应该有一个food数组来存储和found方法来操作food数组,这两个方法都被添加到了原型中。

你的程序员给你带来了以下的解决方案。代码看起来还挺好的,当你创建两个Hamster时,本来饲料是属于其中一个的,但现在两个都有了。

那该怎么解决这种问题呢?

function Hamster(){}; 
Hamster.prototype = {     
food: [],     
found: function(something){         
this.food.push(something);     
} } 

//创建一个懒惰的hamster,和一个勤快的hamster,勤快的有饲料 
 speedy = new Hamster();
lazy = new Hamster(); 
speedy.found("apple");
speedy.found("orange"); 
alert(speedy.food.length);   
 
//2alert(lazy.food.length);    
//2

解决方法:

让我们仔细分析下,speedy.found(“apple”)执行时发生了什么事?

(1)解释器查找在speedy中查找found,但是speedy是一个空对象,因此会失败。

(2)解释器到speedy.__proto__(==Hamster.prototype)查找,然后找到了found方法后执行。

(3)在先前的执行阶段,由于调用speedy.__proto__,this是被设置成speedy对象。

(4)this.food在speedy中没有被找到,但是可以在speedy.__proto__中被找到。

(5)”apple”被加到了speedy.__proto__.food中。

在__proto__中的food被改变时,却在两个hamster对象中共享了,必须要解决这个问题。

解决这个问题

为了解决这个问题,我们要确定每个hamster都有他们自己的食物,可以通过在构造函数中赋值做到。

function Hamster(){     
this.food = [],};
 Hamster.prototype = {    
 found: function(something){        
 this.food.push(something);   
  } }  
speedy = new Hamster();
lazy = new Hamster();
 speedy.found("apple");
speedy.found("orange"); 
alert(speedy.food.length);    //2alert(lazy.food.length);    //0
继承

让我们创建一个新的从animal继承的一个Rabbit类。

function Rabbit(name){     
this.name = name; 
} 
Rabbit.prototype.jump = function(){     
this.canWalk = true;    
 alert(this.name + ' jumps!');
}  
var rabbit = new Rabbit('John');

正如你所看到的,结构与Animal很相似。

为了从Animal中继承,我们需要Rabbit.prototype.__proto__ == Animal.prototype. 这是一个自然的想法,因为如果一个方法不能在Rabbit.prototype中找到,我们可以在Animal.prototype中去寻找。具体看下图说明。

768998df1a9f841
1453496fb616ebf

为了实现这条链,我们需要创建一个从Animal.prototype继承来的Rabbit.prototype空对象,然后再向其中添加方法。

function Rabbit(name){    
 this.name = name; }
Rabbit.prototype = inherit(Animal.prototype); 
Rabbit.prototype.jump = function(){...}

在上面的代码中,inherit是一个通过给定的__proto__属性来创建空对象的方法。

function inherit(proto){     
function F(){};    
 F.prototype = proto;     
return new F; }

最后两个对象完整的代码如下所示。

function Animal(name){   
  this.name = name;} 
Animal.prototype = {    
 canWalk: true,     
sit: function(){        
 this.canWalk = false;        
alert(this.name + ' sits down.');     
} }  
 function Rabbit(name){    
 this.name = name; } 
//继承
Rabbit.prototype = inherit(Animal.prototype); //Rabbit方法Rabbit.prototype.jump = function(){       
 this.canWalk = true;     
alert(this.name + ' jumps!'); } 
var rabbit = new Rabbit('Sniffer'); 
rabbit.sit();    //Sniffer sitsrabbit.jump()    //sniffer jumps!    
function inherit(proto){  
   function F(){};    
 F.prototype = proto;   
  return new F; 
}

不要通过new Animal()来继承。这种方法应用得非常多,但是通过Rabbit.prototype = new Animal()是一种错误的继承方式。因为new Animal()这种方式并没有传递参数name,构造函数也许严格限制需要传递参数才行,所以这种情况下使用这种继承方式是不行的。
事实上,我们只想从Animal中继承,而不是去创建一个Animal实例吧?这才符合继承的实质。所以Rabbit.prototype = inherit(Animal.prototype)是更好点。

调用父类的构造函数

”superclass”构造函数不会被自动调用,我们可以通过apply来实现。

function Rabbit(name){  
   Animal.apply(this,arguments);
}

在当前对象的上下文执行Animal构造函数,从而达到改变name值的目的。

重写方法(多态)

为了重写父类方法,在子类的原型中代替它。

Rabbit.prototype.sit = function(){    
 alert(this.name + ' sits in a rabbity way.');
}

当调用rabbit.sit()时,搜索链为:rabbit -> Rabbit.prototype -> Animal.prototype,如果在Rabbit.prototype中找到,就会停止去Animal.prototype寻找。

当然我们也可以直接在对象中重写方法。

rabbit.sit = function(){       
 alert('a special sit of this very rabbit '+ this.name); 
}
重写后再调用父类的方法

当一个方法被重写时,我们还想调用之前老的方法,如果可能我们可以直接请求父类的prototype来实现。

Rabbit.prototype.sit = function() {    
alert('calling superclass sit:')    
Animal.prototype.sit.apply(this, arguments)
}

所有父类方法通过apply/call方法传递当前对象this来调用,Animal.prototype.sit()调用将 Animal.prototype作为this。

去除对父类的直接引用

在上面的例子当中,我们可以通过Animal.apply...或者Animal.prototype.sit.apply....直接调用父类。

正常情况下,我们不应该那样做。以后重构代码也许会改变父类的名字,或者在层级中引入新的类。

一般来说,程序语言允许调用父类方法通过使用特别的关键字,如parent.method()或者 super()。

javaScript没有这样的特性,但我们可以模拟它。

下面的extend方法可以实现继承,却不需要直接指向父类的引用。

function extend(child, parent) {     
child.prototype = inherit(parent.prototype);     
child.prototype.constructor = child;     
child.parent = parent.prototype; 
}

用法:

function Rabbit(name){     
Rabbit.parent.constructor.apply(this.arguments);    //调用父类构造器} extend(Rabbit,Animal); 
Rabbit.prototype.run = function(){     Rabbit.parent.run.apply(this,arguments); //调用父类方法 
}

事实上现在我们可以重命名Animal或者创建一个中间类GrassEatingAnimal,但是实际情况的改变只会涉及到Animal和extend(...)。

private或protected方法(封装)

可保护的方法和属性通过命名的习惯来支持。因此,一个以下划线开始的方法不应该被外面直接调用。

768998df1a9f841
9e5cc780fb8fe9f

private方法经常不支持。

requireJS缓存问题

胖蔡阅读(334)

可以在requirejs配置的入口文件里配置参数设置版本缓存:

// html

  <script type="text/javascript" data-main="/js/main" src="/js/lib/require.js"></script>

// main.js,v是版本号

require.config({
    urlArgs: "r="+v,
    map: {
        '*': {
            'css': '/js/lib/css.min.js'
        },
    },
    paths: {
        "jquery": '/js/lib/jquery-3.4.1.min',
        "es6-promise": '/js/lib/es6-promise.auto.min',
    },
    shim: {
        //     'jquery': ['css!../../css/public.css', 'css!../../css/index.css']
        "paging": {
            deps: ['query']
        }
    },
    waitSeconds: 0
});

如上,通过设置v来控制版本缓存。

RequireJs加载程序插件

胖蔡阅读(307)

RequireJS支持加载程序插件。这是一种支持依赖关系的方法,该依赖关系不是普通的JS文件,但对于脚本在执行工作之前已加载仍然很重要。RequireJS Wiki有一个插件列表。本节讨论与RequireJS一起维护的一些特定插件:

指定文本文件依赖性

使用常规HTML标记构建HTML很好,而不是在脚本中构建DOM结构。但是,没有很好的方法将HTML嵌入JavaScript文件中。最好的方法是使用HTML字符串,但这可能很难管理,尤其是对于多行HTML。

RequireJS有一个插件text.js,可以帮助解决此问题。如果文本将自动加载!前缀用于依赖项。有关更多信息,请参见 text.js自述文件

页面加载事件支持/ DOM准备就绪

使用RequireJS足够快地加载脚本以使其在DOM准备好之前完成时,这是可能的。任何尝试与DOM交互的工作都应等待DOM准备就绪。对于现代浏览器,这是通过等待DOMContentLoaded事件来完成的。

但是,并非所有使用中的浏览器都支持DOMContentLoaded。domReady模块实现了跨浏览器方法来确定DOM准备就绪的时间。下载模块,并在您的项目中使用它,如下所示:

require(['domReady'], function (domReady) {
  domReady(function () {
    //This function is called once the DOM is ready.
    //It will be safe to query the DOM and manipulate
    //DOM nodes in this function.
  });
});

由于DOM ready是常见的应用程序需求,因此理想情况下可以避免上面API中的嵌套函数。domReady模块还实现了Loader Plugin API,因此您可以使用loader插件语法(注意domReady依赖项中的)来强制require()回调函数在执行之前等待DOM准备就绪。

domReady

用作加载程序插件时,将返回当前文档:

require(['domReady!'], function (doc) {
    //This function is called once the DOM is ready,
    //notice the value for 'domReady!' is the current
    //document.
});

注意:如果文档加载时间较长(可能是非常大的文档,或者HTML脚本标签加载了大型JS文件,这些文件会阻止DOM完成直到完成),那么将domReady用作加载程序插件可能会导致RequireJS“超时”错误。如果这是一个问题,请增加waitSeconds配置,或者仅使用domReady作为模块并在require()回调内调用domReady()。

定义一个I18N捆绑

一旦您的Web应用达到了一定的大小和受欢迎程度,在界面中本地化字符串并提供其他特定于语言环境的信息就变得更加有用。但是,制定一个可以很好地扩展以支持多个语言环境的方案可能很麻烦。

RequireJS允许您设置具有本地化信息的基本模块,而无需强制您预先提供所有特定于语言环境的信息。它可以随时间添加,并且只能在特定于语言环境的文件中定义在语言环境之间更改的字符串/值。

i18n.js插件提供了对i18n包的支持。当模块或依赖项指定i18n时,它将自动加载!前缀(下面有更多信息)。下载插件,并将其放在应用程序的主JS文件所在的目录中。

要定义包,请将其放在名为“ nls”的目录中-i18n!插件假定模块名称中带有“ nls”,表示一个i18n软件包。名称中的“ nls”标记告诉i18n插件在哪里可以看到语言环境目录(它们应该是nls目录的直接子级)。如果要在“我的”模块集中提供一组颜色名称,请按以下方式创建目录结构:

  • 我/nls/colors.js

该文件的内容应如下所示:

//my/nls/colors.js contents:
define({
    "root": {
        "red": "red",
        "blue": "blue",
        "green": "green"
    }
});

属性为“ root”的对象文字定义了此模块。您要做的就是为以后的本地化工作奠定基础。

然后,可以在另一个模块(例如,my / lamps.js文件)中使用上述模块:

//Contents of my/lamps.js
define(["i18n!my/nls/colors"], function(colors) {
    return {
        testMessage: "The name for red in this locale is: " + colors.red
    }
});

my / lamps模块具有一个名为“ testMessage”的属性,该属性使用colors.red来显示红色的本地化值。

稍后,当您想向文件添加特定的翻译时,例如使用fr-fr语言环境,请将my / nls / colors更改为如下所示:

//Contents of my/nls/colors.js
define({
    "root": {
        "red": "red",
        "blue": "blue",
        "green": "green"
    },
    "fr-fr": true
});

然后在my / nls / fr-fr / colors.js定义一个文件,该文件包含以下内容:

//Contents of my/nls/fr-fr/colors.js
define({
    "red": "rouge",
    "blue": "bleu",
    "green": "vert"
});

RequireJS将使用浏览器的navigator.languages,navigator.language或navigator.userLanguage属性来确定要用于my / nls / colors的语言环境值,因此您的应用程序不必更改。如果您希望设置语言环境,则可以使用模块配置将语言环境传递给插件:

requirejs.config({
    config: {
        //Set the config for the i18n
        //module ID
        i18n: {
            locale: 'fr-fr'
        }
    }
});

请注意,RequireJS将始终使用小写版本的语言环境,以避免出现大小写问题,因此,i18n软件包的磁盘上的所有目录和文件都应使用小写语言环境。

RequireJS也足够聪明,可以选择正确的语言环境包,该语言包与my / nls / colors提供的语言包最匹配。例如,如果语言环境是“ en-us”,则将使用“ root”捆绑软件。如果区域设置为“ fr-fr-paris”,则将使用“ fr-fr”捆绑包。

RequireJS还将捆绑包合并在一起,因此,例如,如果法式捆绑包是这样定义的(将红色值省略):

//Contents of my/nls/fr-fr/colors.js
define({
    "blue": "bleu",
    "green": "vert"
});

然后将使用“ root”中的red值。这适用于所有语言环境。如果定义了下面列出的所有捆绑包,那么RequireJS将按照以下优先级顺序使用值(顶部的优先级最高):

  • 我/nls/fr-fr-paris/colors.js
  • 我/nls/fr-fr/colors.js
  • 我/nls/fr/colors.js
  • 我/nls/colors.js

如果您不希望在顶级模块中包含根包,则可以像普通的语言环境包一样定义它。在这种情况下,顶层模块如下所示:

//my/nls/colors.js contents:
define({
    "root": true,
    "fr-fr": true,
    "fr-fr-paris": true
});

根束看起来像:

//Contents of my/nls/root/colors.js
define({
    "red": "red",
    "blue": "blue",
    "green": "green"
});

RequireJs 高级用法

胖蔡阅读(673)

从包中加载模块

RequireJS支持加载CommonJS Packages目录结构中的模块,但是需要指定一些其他配置才能使其正常工作。具体来说,它支持以下CommonJS Packages功能:

  • 软件包可以与模块名称/前缀关联。
  • 程序包配置可以为特定程序包指定以下属性:
    • name:程序包的名称(用于模块名称/前缀映射)
    • location:磁盘上的位置。位置相对于baseUrl配置值,除非它们包含协议或以反斜杠(/)开头。
    • main:某人对“ packageName”的要求时应使用的包内模块的名称。默认值为“ main”,因此仅当它不同于默认值时才指定它。该值是相对于包文件夹的。

重要笔记

  • 尽管软件包可以具有CommonJS目录布局,但模块本身应采用RequireJS可以理解的模块格式。规则的例外:如果您使用的是r.js节点适配器,则这些模块可以采用传统的CommonJS模块格式。如果需要将传统的CommonJS模块转换为RequireJS使用的异步模块格式,则可以使用CommonJS转换工具
  • 一次只能在项目上下文中使用软件包的一个版本。您可以使用RequireJS多版本支持来加载两个不同的模块上下文,但是如果要在一个上下文中使用程序包A和B,并且它们依赖于程序包C的不同版本,那么这将是一个问题。将来可能会改变。

如果您使用《入门指南》中指定的类似项目布局,则Web项目的开始将类似于以下内容(基于Node / Rhino的项目是类似的,只需使用scripts目录的内容作为顶级项目目录):

  • 项目目录/
    • project.html
    • 脚本/
      • require.js

这是带有两个包cartstore的示例目录布局的外观:

  • 项目目录/
    • project.html
    • 脚本/
      • 大车/
        • main.js
      • 店铺/
        • main.js
        • util.js
      • main.js
      • require.js

project.html将具有如下脚本标记:

<script data-main="scripts/main" src="scripts/require.js"></script>

这将指示require.js加载脚本/main.js。main.js使用“ packages”配置来设置与require.js相关的软件包,在这种情况下,它们是源文件“ cart”和“ store”:

//main.js contents
//Pass a config object to require
require.config({
    "packages": ["cart", "store"]
});

require(["cart", "store", "store/util"],
function (cart,   store,   util) {
    //use the modules as usual.
});

“ cart”的需求意味着它将从scripts / cart / main.js加载,因为“ main”是RequireJS支持的默认主模块设置。将从scripts / store / util.js加载“ store / util”的需求。

如果“ store”包未遵循“ main.js”约定,则看起来更像这样:

  • 项目目录/
    • project.html
    • 脚本/
      • 大车/
        • main.js
      • 店铺/
        • store.js
        • util.js
      • main.js
      • package.json
      • require.js

然后,RequireJS配置将如下所示:

require.config({
    packages: [
        "cart",
        {
            name: "store",
            main: "store"
        }
    ]
});

为避免冗长,强烈建议始终使用在结构中使用“主要”约定的软件包。

多版本支持

配置选项中所述,可以使用不同的“上下文”配置选项将模块的多个版本加载到页面中。require.config()返回一个使用上下文配置的require函数。这是一个加载两个不同版本的alpha和beta模块的示例(此示例摘自其中一个测试文件):

<script src="../require.js"></script>
<script>
var reqOne = require.config({
  context: "version1",
  baseUrl: "version1"
});

reqOne(["require", "alpha", "beta",],
function(require,   alpha,   beta) {
  log("alpha version is: " + alpha.version); //prints 1
  log("beta version is: " + beta.version); //prints 1

  setTimeout(function() {
    require(["omega"],
      function(omega) {
        log("version1 omega loaded with version: " +
             omega.version); //prints 1
      }
    );
  }, 100);
});

var reqTwo = require.config({
      context: "version2",
      baseUrl: "version2"
    });

reqTwo(["require", "alpha", "beta"],
function(require,   alpha,   beta) {
  log("alpha version is: " + alpha.version); //prints 2
  log("beta version is: " + beta.version); //prints 2

  setTimeout(function() {
    require(["omega"],
      function(omega) {
        log("version2 omega loaded with version: " +
            omega.version); //prints 2
      }
    );
  }, 100);
});
</script>

注意,“ require”被指定为模块的依赖项。这允许传递给函数回调的require()函数使用正确的上下文正确加载模块以支持多版本。如果未将“ require”指定为依赖项,则可能会出现错误。

页面加载后加载代码

上面“ Multiversion支持”部分中的示例显示了以后如何通过嵌套的require()调用来加载代码。

网络工作者支持

从0.12版开始,RequireJS可以在Web Worker中运行。只需在网络工作者中使用importScripts()来加载require.js(或包含require()定义的JS文件),然后调用require。

您可能需要设置baseUrl 配置选项,以确保require()可以找到要加载的脚本。

通过查看单元测试中使用的文件之一,可以看到其用法示例。

犀牛支持§4.5

RequireJS可以通过r.js适配器在Rhino中使用。有关更多信息,请参见r.js自述文件

Nashorn支持

从RequireJS 2.1.16开始,RequireJS可以通过r.js适配器在Java 8+的JavaScript引擎Nashorn中使用。有关更多信息,请参见r.js自述文件

处理错误

错误的一般类别是脚本(未找到),网络超时或所加载脚本中的错误的404。RequireJS有一些用于处理它们的工具:特定于需求的errback,“ paths”数组配置以及全局的requirejs.onError。

传递给errbacks的错误对象和全局requirejs.onError函数通常将包含两个自定义属性:

  • requireType:具有一般分类的字符串值,例如“ timeout”,“ nodefine”,“ scripterror”。
  • requireModules:超时的模块名称/ URL的数组。

如果您在requireModules中遇到错误,则可能意味着未定义依赖于requireModules数组中的模块的其他模块。

捕获IE中的负载故障

Internet Explorer存在一系列问题,使得难以检测到错误/路径后备的加载失败:

  • script.onerror在IE 6-8中不起作用。无法知道加载脚本是否会生成404,更糟糕的是,即使在404情况下,它也会触发具有完整状态的onreadystatechange。
  • script.onerror确实可以在IE 9+中运行,但是存在一个错误,即在执行脚本后不立即触发script.onload事件处理程序,因此它不支持允许匿名AMD模块的标准方法。因此仍使用script.onreadystatechange。但是,在script.onerror函数启动之前,onreadystatechange会以完整状态启动。

因此,使用IE很难同时允许匿名的AMD模块和可靠的检测错误,匿名的AMD模块是AMD模块的核心优势。

但是,如果您知道在一个项目中使用define()声明其所有模块,或者它使用shim config为不使用define()的任何内容指定字符串导出,那么如果您设置了defineDefine配置值确实,加载器可以通过检查define()调用或填充程序的导出全局值的存在来确认脚本是否已加载。

因此,如果要支持Internet Explorer,捕获负载错误并通过直接define()调用或shim config获得模块化代码,请始终将forcedDefine设置为true。有关示例,请参见下一部分。

注意:如果您确实设置了forceDefine:true,并且使用data-main =“”来加载主JS模块,则该主JS模块必须调用define()而不是require()来加载所需的代码。JS主模块仍然可以调用require / requirejs来设置配置值,但是对于加载模块,它应该使用define()。

如果然后您还使用杏仁来构建没有require.js的代码,请确保使用insertRequire构建设置为主模块插入一个require调用-达到与最初的require()调用相同的目的,即调用data-main做。

require([])错误

当与requirejs.undef()一起使用时,Errbacks将允许您检测模块是否无法加载,取消定义该模块,将配置重置到另一个位置,然后重试。

一个常见的用例是使用CDN托管的库版本,但是如果失败,请切换到本地加载文件:

requirejs.config({
    enforceDefine: true,
    paths: {
        jquery: 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min'
    }
});

//Later
require(['jquery'], function ($) {
    //Do something with $ here
}, function (err) {
    //The errback, error callback
    //The error has a list of modules that failed
    var failedId = err.requireModules && err.requireModules[0];
    if (failedId === 'jquery') {
        //undef is function only on the global requirejs object.
        //Use it to clear internal knowledge of jQuery. Any modules
        //that were dependent on jQuery and in the middle of loading
        //will not be loaded yet, they will wait until a valid jQuery
        //does load.
        requirejs.undef(failedId);

        //Set the path to jQuery to local path
        requirejs.config({
            paths: {
                jquery: 'local/jquery'
            }
        });

        //Try again. Note that the above require callback
        //with the "Do something with $ here" comment will
        //be called if this new attempt to load jQuery succeeds.
        require(['jquery'], function () {});
    } else {
        //Some other error. Maybe show message to the user.
    }
});

使用`requirejs.undef()`,如果您稍后设置其他配置并尝试加载相同的模块,则加载器仍会记住哪些模块需要该依赖关系,并在新配置的模块加载时完成加载。

注意:errbacks仅适用于回调样式的require调用,而不适用define()调用。define()仅用于声明模块。

路径配置后备

上面的用于检测负载故障,对模块进行undef(),修改路径和重新加载的模式是一个足够常见的请求,它也有一个简写。路径配置允许使用数组值:

requirejs.config({
    //To get timely, correct error triggers in IE, force a define/shim exports check.
    enforceDefine: true,
    paths: {
        jquery: [
            'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min',
            //If the CDN location fails, load from this location
            'lib/jquery'
        ]
    }
});

//Later
require(['jquery'], function ($) {
});

上面的代码将尝试CDN位置,但是如果失败,则退回到本地lib / jquery.js位置。

注意:路径回退仅适用于确切的模块ID匹配。这与可应用于模块ID前缀段的任何部分的常规路径配置不同。后备的目标更多是针对异常错误的恢复,而不是通用的路径搜索路径解决方案,因为它们在浏览器中效率低下。

全局requirejs.onError函数

要检测本地错误未捕获的错误,可以覆盖requirejs.onError():

requirejs.onError = function (err) {
    console.log(err.requireType);
    if (err.requireType === 'timeout') {
        console.log('modules: ' + err.requireModules);
    }

    throw err;
};