Fork me on GitHub

课程导论

# 课程导论

课程导论:本章主要介绍课程的知识大纲,每个章节的解决顺序和主要内容。

1- 1 导学

1-2 课程重要提示

1-3 架构

# 课程概述

做什么? —— 讲解前端 JS 高级面试题

哪些部分? —— 高级基础,框架原理,app 混合开发

技术? ——JS、ES6、虚拟 DOM、vueReact、hybrid

mark

# 课程安排

  • 高级基础
  • 框架原理
  • App 混合开发
  • 热爱编程

# 课程安排 - 高级基础

  • ES6 常用语法:Class ,Module Promise 等
  • 原型高级应用:结合 jQuery 和 zepto 源码
  • 异步全面讲解:从原理到 jQuery 再到 Primise

# 课程安排 - 框架原理

  • 虚拟 DOM:存在价值,如何使用,diff 算法
  • vue:MVVM,vue 响应式、模板解析、渲染
  • React:组件化,JSX、VDOM,setState
  • 对比:有主见,自圆其说

# 课程安排 - App 混合开发

  • hybrid:基础、和 h5 对比,上线流程
  • 通讯:通讯原理,JS-Bridge 封装

# 课程安排 - 热爱编程

  • 读书
  • 博客
  • 开源

# 课程收获

  • 应对 JS 高级面试题
  • 从深度和广度都扩充了自己的知识体系
  • 学会如何高效学习
  • 深入理解常用框架的实现原理和 hybrid 应用

# 学习前提

  • 有 JavaScript 基础
  • 用过 node.js 和 npm 开发环境
  • 了解 vue 和 React (至少看过文档,做过 demo)
  • 热爱前端开发,有学习的欲望

# 课程优势

  • 针对高级 JS 面试中,面试官爱问 “源码” “实现”
  • 介绍常用框架实现原理的视频,网上稀缺
  • 会介绍 hybrid 原理和应用的视频,网上稀缺
  • 全部由实际工作经验总结而来,书上看不到

前端JS基础面试技巧上

# 前端 JS 基础面试技巧

讲解 JS 基础语法相关的面试题,分析原理以及解答方法。这一章节讲解了基础知识的第一部分:变量的类型和计算。以及 JS “三座大山” —— 原型、作用域和异步中的: 原型和原型链、作用域和闭包。

知识点:

2-1 变量类型和计算

2-2 原型和原型链

2-3 函数声明和函数表达式

2-4 作用域和闭包

mark

关于面试

  • 基层工程师 - 基础知识
  • 高级工程师 - 项目经验
  • 架构师 - 解决方案

# 先从几道面试题入手

  • JS 中使用 typeof 能得到的哪些类型?
    • 考点:JS 变量类型
  • 何时使用 === 何时使用 **== **?
    • 考点:强制类型转换
  • window.onloadDOMContentLoaded 的区别?
    • 考点:浏览器的渲染过程
  • 用 JS 创建 10 个 a 标签,点击的时候弹出来对应的序号
    • 考点:作用域
  • 简述如何实现一个模块加载器,实现类似 require.js 的基本功能
    • 考点:JS 模块化
  • 实现数组的 随机排序
    • 考点:JS 基础算法

mark

mark

# 知识体系

题目

知识点

解答

# 2-1 变量类型和计算

2-1 变量类型和计算

题目

知识点

解答

# 题目

  • JS 中使用 typeof 能得到的哪些类型?
  • 何时使用 === 何时使用 **== **?
  • JS 中有哪些 内置函数
  • JS 变量按照 存储方式 分为哪些类型,并描述其特点
  • 如何理解 JSON

# 知识点

  • 变量类型
    • 值类型 vs 引用类型
    • typeof 运算符 详解
  • 变量计算

# 变量类型

值类型 vs 引用类型

# 值类型

值类型 (基本数据类型) 的值是按值访问的。

基本类型的值是不可变的,基本类型的比较是它们的值的比较,基本类型的变量是存放在 栈内存(Stack)里的

JavaScript 数据类型类型 (基本类型):字符串(String)、数字 (Number)、布尔 (Boolean)、对空(Null)、未定义(Undefined)、Symbol (ES6 提供的新的类型)。

6 种基本数据类型:stringnumberbooleanundefinednullSymbol

mark

# 引用类型

引用类型的值是按引用访问的。

引用类型的值是可变的,引用类型的比较是引用的比较,引用类型的值是保存在 堆内存(Heap)中的对象(Object)

特点:无限制扩展属性

3 种 主要引用类型:对象(Object)、数组(Array)、函数(Function

细分的话,有: Object 类型Array 类型Date 类型RegExp 类型Function 类型 等。

mark

# 数据类型

** 值类型 (基本类型) **+ 引用数据类型

7 种数据类型numberstringbooleanundefinednullSymbolObject (Object、Array、Function)

# typeof 运算符

7 种类型:undefinedstringnumberbooleanobjectfunctionsymbol(ES6 提供的新的类型)

注意:typeof null // object

typeof 运算符 只能 区分 值类型 的 类型,对于引用类型的 对象数组 区分不出来

mark

# 变量计算

这个主要针对值类型 - 强制类型转换

4 种强制类型转换:

  • 字符串拼接
  • == 运算符
  • if 语句
  • 逻辑运算

# 字符串拼接

mark

# == 运算符

mark

# if 语句

if 语句

false 情况0NaN’<空字符串>’nullundefinefalse

mark

# 逻辑运算符

mark

# 何时使用 === 和 ==

何时使用 === 和 ==?

解答:参考 jQuery 源码中推荐的写法,除了判断对象属性是否为空 和 ** 看是否函数的参数为空 ** 的情况 ,其余的都用 ===

== : 只进行值的比较

=== : 不仅进行值得比较,还要进行数据类型的比较

mark

# JS 中的内置函数

JS 中的内置函数的作用

mark

# JS 按存储方式区分变量类型

参考 1

参考 2

基本类型的值是不可变的

mark

# 如何理解 JSON

JS 内置对象,Math 也是内置对象

注意:JSON 既是一个 JS 内置对象,也是一种 数据格式

mark

# 2-2 原型和原型链

2-2 原型和原型链

题目

知识点

解答

# 题目

  • 如何准确判断一个变量是 数组类型
  • 写一个原型链继承的例子
  • 描述 new 一个对象的过程
  • zepto (或其他框架) 源码中如何使用原型链

# 知识点

  • 构造函数
  • 构造函数 - 扩展
  • 原型规则和示例
  • 原型链
  • instanceof

# 构造函数

函数名 习惯 第一个字母大写( 高级程序员规范)

mark

# 构造函数扩展

构造函数扩展

函数扩展 ---- 语法糖

mark

# 5 条原型规则和示例

5 条原型规则

原型规则 是学习 原型链 的基础

5 条原型规则 :

  1. 所有的 引用类型 (对象,数组,函数),都具有对象特性,即可 自由扩展 属性(除了 null 以外)。

  2. 所有的 引用类型 (对象,数组,函数),都有一个 __proto__隐式原型 )属性,属性值都是一个普通对象。

    mark

  3. 所有的函数都有一个 prototype显示原型 )属性,属性值是一个普通对象。

  4. 所有的引用类型 (对象,数组,函数), __proto__ 属性值指向它的构造函数的 prototype 属性值。

    mark

  5. 当试图得到一个引用类型的某个属性时,如果这个对象本身没有这个属性,那么会去它的 __proto__ (即它的构造函数的 prototype ) 中去找。

示例

mark

循环自身的属性:

mark

# 原型链

这种搜索的轨迹,形似一条长链,又因 prototype 在这个游戏规则中充当链接的作用,于是我们把这种实例与原型的链条称作 原型链

参考

mark

mark

# instanceof

用于 判断 引用类型 属于哪个 构造函数的方法

**instanceof 运算符 ** 用于测试构造函数的 prototype 属性是否出现在对象的原型链中的任何位置

参看 MDN

mark

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
var auto = new Car("Honda", "Accord", 1998);

console.log(auto instanceof Car); //  true

console.log(auto instanceof Object); // true

# 解答

  • 如何准确判断一个变量是 数组类型

    • arr instanceof Arrar
  • 写一个原型链继承的例子、

function Elem(id) {
  this.elem = document.getElementById(id);
}

Elem.prototype.html = function (val) {
  var elem = this.elem;
  if (val) {
    elem.innerHTML = val;
    return this; //链式操作
  } else {
    return elem.innerText;
  }
};
Elem.prototype.on = function (type, fn) {
  var elem = this.elem;
  elem.addEventListener(type, fn);
  return this;
};

var div1 = new Elem("div1");
div1.html("<p>hello world</p>").on("click", function () {
  alert("clicked");
});
  • 描述 new 一个对象的过程

    mark

  • zepto (或其他框架) 源码中如何使用原型链

    mark

# 2-3 函数声明和函数表达式

函数声明和函数表达式

# 函数声明

fn(); //执行
function fn() {
  //声明
}

# 函数表达式

把 var 定义的变量提前:相当于:先定义 var fn — > 然后执行 fn()

fn() // TypeError: fn is not a function
var fn=function(){
    // 表达式
}

相关的例子(函数执行的顺序):

console.log(a); // undefined
var a = 100;
fn("zhouchen");
function fn(name) {
  age = 20;
  console.log(name, age);
  var age;
}
//output:zhouchen 20
fn("zhouchen");
function fn(name) {
  console.log(arguments); // 参数的集合
  age = 20;
  console.log(name, age);
  var age;

  bar(100);
  function bar(num) {
    console.log(num);
  }
}
/*
    { '0': 'zhouchen' }
    zhouchen 20
    100
*/

# 2-4 作用域和闭包

作用域和闭包

题目

知识点

解答

# 题目

  • 说一下对变量提升的理解
  • 说明 this 几种 不同的使用场景
  • 创建 10 个 a 标签,点击的时候弹出来对应的序号
  • 如何理解作用域
  • 实际开发中闭包的应用

# 知识点

  • 执行上下文

  • this

  • 作用域

  • 作用域链

  • 闭包

# 执行上下文

执行上下文

mark

mark

# this

this 要在 执行时 才能确定值,定义时 无法确认

mark

mark

# 块级作用域

任何一对花括号中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。

JS不支持 块级作用域,它只支持 函数作用域而且在一个函数中的任何位置定义的变量 在该函数中的 任何地方都是可见的

if (true) {
  var name = "zhouchen";
}
console.log(name); // zhouchen

# 链式作用域

如何从外部读取局部变量?

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。

那就是在函数的内部,再定义一个函数。

function f1() {
  var n = 999;

  function f2() {
    alert(n); // 999
  }
}

在上面的代码中,函数 f2 就被包括在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来就不行,f2 内部的局部变量,对 f1 就是不可见的。这就是 Javascript 语言特有的 "链式作用域" 结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

# 闭包

上一节代码中的 f2 函数,就是闭包。

简单来说:闭包就是 能够读取其他函数内部变量的函数

由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成 定义在一个函数内部的函数

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁

参考阮一峰的网络日志

# 闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

# 实际开发中闭包的应用

实际开发中闭包的应用

mark

# 解题

  • 说一下对变量提升的理解

    • 变量定义
    • 函数声明 ( 注意和 函数表达式 的区别)
  • 说明 this 几种 不同的使用场景

  • 创建 10 个 a 标签,点击的时候弹出来对应的序号

    mark

    mark

  • 如何理解作用域

    • 自由变量
    • 作用域连,即自由变量的查找
    • 闭包的两个场景
  • 实际开发中闭包的应用

    mark

前端JS基础面试技巧下

# 前端 JS 基础面试技巧

前端 JS 基础面试技巧 – JS 基础知识下

JS 三座大山:原型原型链作用域闭包异步和单线程

知识点:

3-1 异步和单线程

3-2 日期和 math

  • 日期
  • Math
  • 数组 API
  • 对象 API

# 3-1 异步和单线程

异步和单线程

题目

知识点

解答

# 题目

  • 同步和异步的区别是什么?分别举一个同步和异步的例子
  • 一个关于 setTimeout 的笔试题
  • 前端使用异步的场景有哪些

# 知识点

  • 什么是异步(对比同步)
  • 前端使用异步的场景
  • 异步个单线程

# 什么是异步

异步(Asynchronous)

一般而言,操作分为发出调用和得到结果两步。发出调用后一直等待,直到拿到结果(这段时间不能做任何事)为 同步 ;发出调用后不等待,继续执行下一个任务,就是 异步任务

参考

console.log(100);
setTimeout(function () {
  console.log(200);
}, 1000);
console.log(300);
/*
    100
    300
    200
*/

# 何时需要异步?

  • 在可能发生等待的情况

  • 等待过程中不能像 alert 一样阻塞程序运行

  • 因此,所有的 “等待情况” 都需要异步

# 前端使用异步的场景

  • 定时任务:setTimeoutsetInterval

  • 网络请求:ajax 请求动态 <img> 加载

  • 事件绑定

# 单线程

Javascript 是单线程的 参考

JS 的单线程是指一个浏览器进程中只有一个 JS 的执行线程,同一时刻内只会有一段代码在执行

在某个特定的时刻只有特定的代码能够被执行,并 阻塞 其它的代码。

console.log(100);
setTimeout(function () {
  console.log(200);
});
console.log(300);

上面代码执行过程:

mark

# 重点总结

  • 异步和同步的区别
  • 异步和单线程的关系
  • 异步在前端的引用场景

# 解答

  • 同步和异步的区别是什么?分别举一个同步和异步的例子

    • 同步阻塞模式异步非阻塞模式

      同步 就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会 一直等待下去 ,知道收到返回信息才继续执行下去;

      异步 是指进程 不需要一直等下去 ,而是 继续执行下面的操作,不管其他进程的状态。当有消息返回式系统会通知进程进行处理,这样可以提高执行的效率。

    • alert 是 同步, setTimeout 是 异步

  • 一个关于 setTimeout 的笔试题

console.log(1);
setTimeout(function () {
  console.log(2);
}, 0);
console.log(3);

setTimeout(function () {
  console.log(4);
}, 1000);
console.log(5);
/* 
	1
    3
    5
    2
    4
*/
  • 前端使用异步的场景有哪些
    • 定时任务:setTimeoutsetInterval
    • 网络请求:ajax 请求动态 <img> 加载
    • 事件绑定

# 3-2 日期和 math

题目

知识点

解答

# 题目

  • 获取 2017 -06 -10 格式的日期
  • 获取 随机数,要求是长度一致 的字符串格式
  • 写一个 能遍历对象 和数组 的通用 forEach 函数

# 知识点

  • 日期
  • Math
  • 数组 API
  • 对象 API

# 日期

日期对象用于处理日期和时间。

JavaScript Date 对象 API

mark

# Math

Math 是一个内置对象, 它具有数学常数和函数的属性和方法。不是一个函数对象。

Math Api

描述:与其它全局对象不同的是, Math 不是一个构造器. Math 的所有属性和方法都是静态的。你用到的常数 pi 可以用 Math.PI 表示,用 x 作参数 Math.sin (x) 调用 sin 函数. JavaScript 中的常数,是以全精度的实数定义的.

Math.random () 在前端的作用:随时改变,清除缓存。

# 数组 API

参考

详情可以参考我的博客:JavaScript 数组那些事

面试官最常问的几个

  • forEach 遍历所有元素
  • every 判断所有元素是否都符合条件
  • some 判断是否有至少一个元素符合条件
  • sort 排序
  • map 对元素重新组装,生成新数组
  • filter 过滤符合条件的元素

# 对象 API

Object.prototype API

Object.prototype 表示对象的原型对象
Object.prototype 属性的属性特征

参考

mark

# 解答

  • 获取 2019-07-17 格式的日期
function formatDate(dt) {
  if (!dt) {
    dt = new Date();
  }
  var dt = new Date();
  var year = dt.getFullYear();
  var month = dt.getMonth() + 1;
  var data = dt.getDate();
  if (month < 10) {
    month = "0" + month;
  }
  if (data < 10) {
    data = "0" + data;
  }
  return year + "-" + month + "-" + data;
}

var dt = new Date();
console.log(formatDate(dt));

//  2019-07-17
  • 获取 随机数,要求是长度一致 的字符串格式
var random = Math.random();
random = random + "0000000000";
random = random.slice(0, 10);
console.log(random);
  • 写一个 能遍历对象 和数组 的通用 forEach 函数
// 写一个 能遍历对象 和数组 的通用 forEach 函数
function forEach(obj, fn) {
  if (obj instanceof Array) {
    obj.forEach(function (item, index) {
      fn(item, index);
    });
  } else {
    for (let key in obj) {
      fn(key, obj[key]);
    }
  }
}

// 使用  forEach 函数
var arr = [1, 2, 3];
var obj = { x: 100, y: 200 };

forEach(arr, function (index, item) {
  console.log(index + "---" + item);
});

forEach(obj, function (key, value) {
  console.log(key, value);
});

/*
	2---1
    3---2
    x 100
    y 200
*/

JS-Web-API

# JS-Web-API

前端 JS 基础面试技巧 - - JS-Web-API 上

讲解 JS 在浏览器中具体应用的面试题。包括 DOM 操作BOM 操作事件绑定ajax存储 ,这些类别的题目。

# 从基础知识到 JS-Web-API

从基础知识 过渡 到 JS-Web-API

  • 回顾 JS 基础知识
  • JS-Web-API
  • 总结

# 回顾 JS 基础知识

特点:表面看来并不能哟用于工作中开发代码

内置函数:Object,Array,Boolean,String …

内置对象:Math,Json …

详情请参考:原型和原型链,闭包和作用域 异步和单线程

  • 我们连在网页上弹出一句 hello world 都不能实现

知识点

  • 变量类型和计算

  • 原型和原型链

  • 闭包和作用域

  • 异步和单线程

  • 其它(如日期,Math,各种常用 API)

  • JS 基础知识:ECMA 262 标准

  • JS-Web-API :W3C 标准

# JS-Web-API

JS-Web-API

W3C 标准中关于 JS 的规定有

  • DOM 操作
  • BOM 操作
  • 事件绑定
  • ajax 请求(包括 http 协议)
  • 储存

页面弹框 window.alert (123) ,浏览器需要做:

  • 定义一个 window 全局变量 ,对象类型
  • 给它定义一个 alert 属性,属性值是一个函数

获取元素 document.getElementById (id),浏览器需要做:

  • 定义一个 document 全局变量 ,对象类型
  • 给它定义一个 getElementById 属性,属性值是一个函数

W3C 标准:

  • W3C 标准没有规定任何 JS 基础相关的东西
  • 不管什么变量类型,原型,作用域和异步
  • 只管 定义用于 浏览器中 JS 操作页面的 API 和 全局变量

# 总结:

常说的 JS (浏览器执行的 JS )包含两部分:

  • JS 基础知识:ECMA 262 标准
  • JS-Web-API :W3C 标准

# 5-1 BOM 节点操作

BOM 操作: BrowserObjectModel ( 浏览器对象模型 )

BOM 节点操作 可以理解为:浏览器把拿到的 html 代码,结构化一个 浏览器 能识别并且 js 可操作的一个模型而已 。

javacsript 是通过 访问 BOM (Browser Object Model)对象来 访问、控制、修改 客户端 (浏览器),由于 BOM 的 window 包含了 document,window 对象的属性和方法是直接可以使用而且被感知的 ,因此可以直接使用 window 对象的 document 属性,通过 document 属性就可以访问、检索、修改 XHTML 文档内容与结构。因为 document 对象又是 DOM(Document Object Model)模型的根节点。可以说,BOM 包含了 DOM (对象) ,浏览器提供出来给予访问的是 BOM 对象,从 BOM 对象再访问到 DOM 对象,从而 js 可以操作浏览器以及浏览器读取到的文档。

mark

# 题目

  • 如何检测浏览器的类型
  • 拆解 url 的 各部分

# 知识点

  • navigator
  • screen
  • location
  • history

navigator & screen

mark

mark

# location & history

# location & history

mark

mark

# 解答

  • 如何检测浏览器的类型

mark

  • 拆解 url 的 各部分

mark

# 5-2 DOM 节点操作

DOM 操作的重点:找到节点对节点(元素 / 文本 / 属性节点)增删改查

各方法和属性之间的结合操作才会使得 DOM 文档活跃起来

注意方法与属性的不同。 注意属性的返回值

注意 javascript 操作样式的可读可写性

可参考

# DOM 节点操作:

  • 获取 DOM 节点
  • prototype
  • Attribute

# 获取 DOM 节点

获取 DOM 节点

document.getElementById("id");
document.getElementByClassName("classname");
document.getElementsByTagName("tag");
document.querySelector("#foo > div.bar");
document.querySelectorAll(".bar");

mark

# prototype

prototype

mark

# property

property 只是一个 JS 对象的属性的修改

mark

# Attribute

Attribute

Attribute 是对 html 标签属性 的修改 (获取)

mark

# DOM 结构操作

DOM 结构操作

  • 新增节点
  • 获取父元素
  • 获取子元素
  • 删除节点

# 新增节点

新增节点

移动已有 的节点

<script>
  var div1 = document.getElementById('div1') p = document.createElement('p')
  p.innerHTML = '<h3>hello world</h3>' div1.appendChild(p) var p2 =
  document.querySelector('.p2') div1.appendChild(p2) console.log(div1)
</script>

mark

# 获取父元素和子元素

获取父元素和子元素

<div id="div1">
    <div id="p1">this is p1</div>
    <div id="p2">this is p2</div>
</div>

<div id="div2">
    <div id="p3">this is p3</div>
    <div id="p4">this is p4</div>
</div>
<script>
    var div1=document.getElementById('div1')
    console.log(div1.parentElement)
    console.log(div1.childNodes)

    console.log(div1.childNodes[0].nodeType)  //3
    console.log(div1.childNodes[1].nodeType)  //1
    console.log(div1.childNodes[0].nodeName)  //#text
    console.log(div1.childNodes[1].nodeName)  // DIV
</script>

mark

# 解答

  • DOM 是哪种基本的数据结构

  • DOM 操作的常用 API 有哪些

    • 获取 DOM 节点,以及节点的 property 和 Attribute
    • 获取父节点,获取子节点
    • 新增节点,删除节点
  • DOM 节点的 Attribute 和 property 有何区别

    • property 只是一个 JS 对象的属性的修改
    • Attribute 是对 html 标签属性的修改

# 重点总结

  • DOM 本质
  • DOM 节点操作
  • DOM 结构操作

# 5-3 事件绑定

JavaScript 绑定事件的三种方式

# JavaScript 绑定事件的三种方式:

  • 使用内联
  • 使用 .onclick 的方式
  • 使用事件监听 addEventListener 的方式

# 内联

<input type="button" value="按钮" onclick="alert(1);" />

这种方式就是在一个元素上面直接绑定了一个点击 onclick 事件,此事件为 DOM 0 级标准。同时,这个事件的优先级是最高的。

# 使用对象。事件的形式

<input type="button" value="按钮">

<script type="text/javascript">
	var bt = document.getElementsBytagname("input")[0];
	bt.onclick = function(){
		alert(2)
	}
</script>

使用这种形式也是可以给一个 DOM 元素添加上一个事件。这个也是 DOM 0 级标准

# 以上的弊端

以上两种方式都是存在一个弊端的,就是一个元素只能添加一个事件。第一种就不用说了,写在行内就一个属性。至于第二种,有的网友可能会说我可以再写一个,比如:

<input type="button" value="按钮">

<script type="text/javascript">
    var bt = document.getElementsByTagName("input")[0];
    bt.onclick = function(){
        alert(2)
    }

    bt.onclick = function(){
        alert(3)
    }
</script>

mark

写是可以这么写。那么我们先来看一看这个写法的意思,这种写法的本质就是在一个对象上添加一个属性,就上面的例子,就是在 bt 这个对象上添加一个 onclick 属性。那么,如果在之后的代码中也存在 bt.onclcik ,只会吧前面的给覆盖了。所以这样的写法也只能添加一个事件。

那么,问题来了。我要给一个元素(DOM 对象)添加两个甚至是多个事件,使用什么呢?此时,就需要使用 addEventListener 的方式来添加事件。

<input type="button" value="按钮">

<script type="text/javascript">
	var bt = document.getElementsBytagname("input")[0];
	bt.addEventListener("click", function(){
		alert(1)
	})
	bt.addEventListener("click", function(){
		alert(2)
	})
</script>

mark

上面的方式就可以给一个 DOM 对象绑定一个或者是多个事件。强烈推荐使用这一种绑定事件的方式。 使用 addEventListener 的方式还可以拥有第三个参数。 参看

三个参数

  1. 事件类型,不需要添加上 on
  2. 事件函数
  3. 是否捕获(布尔值),默认是 false ,即不捕获,那就是冒泡。

# 捕获和冒泡

# 捕获

mark

<div id="a">
  a
  <div id="b">
    b
    <div id="c">c</div>
  </div>
</div>
<script>
  var a = document.getElementById("a");
  var b = document.getElementById("b");
  var c = document.getElementById("c");
  // 捕获
  a.addEventListener(
    "click",
    function () {
      alert("b-a");
    },
    true
  );
  b.addEventListener(
    "click",
    function () {
      alert("b-b");
    },
    true
  );
  c.addEventListener(
    "click",
    function () {
      alert("b-c");
    },
    true
  );

  //运行结果:点击c时,上面的代码的执行顺序:b-a,b-b,b-c
</script>

# 冒泡

mark

<div id="a">
    a
    <div id="b">
        b
        <div id="c">c</div>
    </div>
</div>
<script>
 	var a = document.getElementById("a");
    var b = document.getElementById("b");
    var c = document.getElementById("c");
      // 冒泡
    a.addEventListener("click", function(){
        alert("m-a")
    },false)
    b.addEventListener("click", function(){
        alert("m-b")
    },false)
    c.addEventListener("click", function(){
        alert("m-c")
    },false)

    //运行结果:点击c时,上面的代码的执行顺序:m-c,m-b,m-a
</script>

# 停止传播

使用 stopPropagation 可以阻止事件的传播。不能使用 return false ,阻止捕获也是一样,添加之后就不会在继续往下传递了。

// 阻止冒泡
c.addEventListener(
  "click",
  function (e) {
    alert("m-c");
    e.stopPropagation(); // 此处阻止传播
  },
  false
);

// 此时的顺序:b-a,b-c,m-c。不会传递,后面的不会执行了

# 关于使用 addEventListener

由于 addEventListener 单词太长: 所以封装一下:

function addEvent(ele, type, fn) {
  ele.addEventListener(type, function (e) {
    fn(e);
  });
}

# 关于事件代理(委托)

如果你要给每一个 li 标签添加一个点击事件,弹出每一个 li 的索引值

<ul id="box">
  <li>list-1</li>
  <li>list-2</li>
  <li>list-3</li>
  <li>list-4</li>
</ul>

闭包:

var oLis = document.getElementsByTagName("li");

for (var i = 0; i < oLis.length; i++) {
  (function (i) {
    addEvent(oLis[i], "click", function (e) {
      alert(i);
    });
  })(i);
}

因为你的 li 的个数可能发生改变,如果是这样的话,可能会出一些问题。

事件代理代码:

var oBox = document.getElementById("box");

addEvent(oBox, "click", function (e) {
  var target = e.target;
  // 判断点击的是li
  if (target.nodeName == "LI") {
    alert(target.innerHTML);
  }
});

这样也是可以的,不过此时的 addEvent 函数点击的时候就需要在 fn 里面判断点击的是哪一个标签。为了更好的使用 addEvent ,我们可以改进一下

function addEvent(ele, type, selector, fn) {
  // 如果只有三个参数,那么3,4互换
  if (fn == null) {
    fn = selector;
    selector = null;
  }
  ele.addEventListener(type, function (e) {
    var target;
    if (selector) {
      //  代理
      target = e.target;
      if (target.matches(selector)) {
        fn.call(target.e);
      }
    } else {
      // 不代理
      fn(e);
    }
  });
}

这时点击 li 弹出 innerHTML 就可以这样实现:

addEvent(oBox, "click", "li", function (e) {
  alert(this.innerHTML);
});

# 总结:

  1. 同时 存在捕获与冒泡时捕获的优先级是高于冒泡的
  2. 没有捕获的时候谁在前面先执行谁

如果要取消一个使用 addEventListener 绑定的事件函数,使用 removeEventListener 可以移除事件。

# 解答

  • 编写一个通用的事件监听函数
function bindEvent(elem, type, seletor, fn) {
  if (fn == null) {
    fn = selector;
    seletor = null;
  }
  elem.addEventListener(type, function (e) {
    var target;
    if (seletor) {
      target = e.target;
      if (target.matches(seletor)) {
        fn.call(target, e);
      }
    } else {
      fn(e);
    }
  });
}
  • 描述事件冒泡的流程

    • DOM 树形结构
    • 事件冒泡
    • 阻止冒泡
    • 冒泡的应用
  • 对于一个无线下拉加载图片的页面,如何给每个图片绑定事件

    • 使用代理
    • 知道代理的两个优点

# 重点总结

  • 通用事件绑定
  • 事件冒泡
  • 代理

# 5-4 Ajax

题目

知识点

题目

# 题目

  • 手动编写一个 ajax,不依赖第三方库
  • 跨域的几种实现方式

# 知识点

  • XMLHttpRequest
  • 状态码说明
  • 跨域

# XMLHttpRequest

使用 XMLHttpRequest (XHR) 对象可以与服务器交互。您可以从 URL 获取数据,而无需让整个的页面刷新。这使得 Web 页面可以只更新页面的局部,而不影响用户的操作。XMLHttpRequest 在 Ajax 编程中被大量使用

尽管名称如此,XMLHttpRequest 可以用于获取任何类型的数据,而不仅仅是 XML,它还支持 HTTP 以外的协议 (包括文件和 ftp)。

如果您的通信需要从服务器接收事件或消息数据,请考虑通过 EventSource 接口使用 server-sent events。对于 full-duplex 通信, WebSockets 可能是更好的选择。

可参考

var xhr = new XMLHttpRequest();
xhr.open("GET", "/api", false); //false 使用异步
xhr.onreadystatechange = function () {
  //这里是函数异步执行,可参考之前 JS 基础中的异步 模块
  if (xhr.readyState == 4) {
    if (xhr.status == 200) {
      alert(xhr.responseText);
    }
  }
};
xhr.send(null);

# IE 兼容性问题

有意向者可参考

  • IE 低版本使用 ActiveXObject,和 W3C 标准不一样
  • IE 低版本使用量非常少,很多网站都早已不支持
  • 建议对 IE 低版本的兼容性:了解即可,无需深究
  • 如果遇到对 IE 低版本要求苛刻的面试,果断放弃

# readyState 状态码说明

readyState 是 XMLHttpRequest 对象的一个属性,用来标识当前 XMLHttpRequest 对象处于什么状态。
readyState 总共有 5 个状态值,分别为 0~4,每个值代表了不同的含义

0:未初始化,还没有调用 send()方法
1:载入,已调用send()方法,XMLHttpRequest对象开始发送请求
2:载入完成,send()方法执行完成,已经接收到全部的相应内容
3:交互,正在解析响应内容
4:完成,响应内容解析完成,可以在客户端调用了

# status 状态码说明

status 是 XMLHttpRequest 对象的一个属性,表示响应的 HTTP 状态码

详细参考

1xx:信息响应类,表示接收到请求并且继续处理
2xx:处理成功响应类,表示动作被成功接收、理解和接受
3xx:重定向响应类,为了完成指定的动作,必须接受进一步处理
4xx:客户端错误,客户请求包含语法错误或者是不能正确执行
5xx:服务端错误,服务器不能正确执行一个正确的请求

200——交易成功
404——没有发现文件、查询或URl
...

# 5-5 跨域

定义 :跨域是指从一个域名的网页去请求另一个域名的资源。比如从 www.baidu.com 页面去请求 www.google.com 的资源。但是一般情况下不能这么做,它是由浏览器的同源策略造成的,是浏览器对 JavaScript 施加的安全限制。跨域的严格一点的定义是:只要 协议,域名,端口有任何一个的不同,就被当作是跨域

** 所谓同源是指,域名,协议,端口均相同。** 这里说的 js 跨域是指通过 js 在不同的域之间进行数据传输或通信,比如用 ajax 向一个不同的域请求数据,或者通过 js 获取页面中不同域的框架中 (iframe) 的数据。

概念:只要协议、域名、端口有任何一个不同 ,都被当作是不同的域。算作 跨域。

参考

http 默认端口:80

https 默认端口:443

# 问题

  • 说明什么是跨域
  • JSONP
  • 服务器端设置 http header

# 什么是跨域

跨域是指一个下的文档或脚本试图去请求另一个下的资源,这里跨域是广义的。 其实我们通常所说的跨域是狭义的,由浏览器同源策略限制的一类请求场景。

# 可以跨域的三个标签

  • <img src=xxx > 用于打点统计,统计网站可能是其它域
  • <link href=xxxx > 可以使用 CDN,CDN 也是其它域
  • <script src=xxx > 可以使用 CDN, 可以用于 JSONP

# 跨域注意事项

  • 所有的跨域请求都必须经过信息提供方允许
  • 如果未经允许即可获取,那是浏览器同源策略出现漏洞

# JSONP 实现原理

jsonp 是一种跨域通信的手段

参考 1 参考 2

jsonp 是一种跨域通信的手段,它的原理其实很简单:

  1. 客户端利用 script 标签可以跨域请求资源的性质,向网页中动态插入 script 标签,来向服务端请求数据。
  2. 服务端会解析请求的 url , 至少拿到一个回调函数 (比如 callback=myCallback ) 参数,之后将数据放入其中返回给客户端。
  3. 当然 jsonp 不同于平常的 ajax 请求,它仅仅支持 get 类型的方式

# 实现流程

  1. 设定一个 script 标签

    <script src="http://jsonp.js?callback=xxx"></script>
  2. callback 定义了一个函数名,而远程服务端通过调用指定的函数并传入参数来实现传递参数,将 fn(response) 传递回客户端

    $callback = !empty($_GET['callback']) ? $_GET['callback'] : 'callback';
    echo $callback.'(.json_encode($data).)';
  3. 客户端接收到返回的 js 脚本,开始解析和执行 fn(response)

# 服务端设置 http header

  • 另外一个解决跨域的简洁方法,需要服务器端来做
  • 但是作为交互方,我们必须知道这个方法
  • 是将来解决跨域问题的一个趋势

mark

# 重点总结

  • XMLHttpRequest
  • 状态码说明
  • 跨域

# 5-6 存储

题目

知识点

解答

# 题目

  • 请描述一下 cookie,sessionStorage 和 localStorage 的区别?

# 知识点

  • cookie
  • sessionStorage 和 localStorage

可参考

  • 本身用于客户端和服务器端通信
  • 但是它有本地储存的功能,于是就被 借用
  • 使用 document.cookie = … 获取和修改即可
  • 储存量太小 ,Cookie` 数量和长度的限制。每个 domain 最多只能有 20 条 cookie,每个 cookie 长度不能超过 4KB,否则会被截掉。在当今新的浏览器和客户端设备版本中,支持 8192 字节的 Cookie 大小已愈发常见。

  • 所有 http 请求都带着,会影响获取资源的效率

  • 用户配置为禁用。有些用户禁用了浏览器或客户端设备接收 Cookie 的能力,因此限制了这一功能

  • 由于在 HTTP 请求中的 cookie 是明文传递的,潜在的安全风险,Cookie 可能会被篡改

  • 有些状态不可能保存在客户端

  • cookie 会被附加在每个 HTTP 请求中,所以无形中增加了流量

  • cookie 一般不可跨域使用

  • 没有封装好的 setCookie 和 getCookie 方法,需要开发者自省封装

# sessionStorage 和 localStorage

  • HTML5 专门为储存而设计,最大容量 5M
  • API 简答易用
  • localStorage.setItem(key, value); localStorage.getItem(key, value);

# 区别:

localStorage 生命周期是永久,这意味着除非用户显示在浏览器提供的 UI 上清除 localStorage 信息,否则这些信息将永远存在。

sessionStorage 生命周期为当前窗口或标签页,一旦窗口或标签页被永久关闭了,那么所有通过 sessionStorage 存储的数据也就被清空了。

不同浏览器无法共享 localStorage 或 sessionStorage 中的信息。相同浏览器的不同页面间可以共享相同的 localStorage(页面属于相同域名和端口),但是不同页面或标签页间无法共享 sessionStorage 的信息。这里需要注意的是,页面及标 签页仅指顶级窗口,如果一个标签页包含多个 iframe 标签且他们属于同源页面,那么他们之间是可以共享 sessionStorage 的。

# 注意

  • iOS safari 隐藏模式下
  • localStorage.getItem 会报错
  • 建议 统一使用 try-catch 封装

# 解答

  • 请描述一下 cookie,sessionStorage 和 localStorage 的区别?
    • 容量
    • 是否携带到 ajax 中
    • API 易用性

运行环境

# 运行环境

讲解 JS 代码在浏览器中运行的相关问题,例如 页面加载和渲染性能优化安全性 ,这些类别的题目。

知识点:

8-1 页面加载过程

8-2 性能优化

  • 浏览器就可以通过访问链接来得到页面内容
  • 通过绘制和渲染,显示出页面的最终的样子

# 整个过程中,我们需要考虑什么问题?

# 3 个重点

  • 页面加载过程
  • 性能优化
  • 安全性

# 8-1 页面加载过程

题目

知识点

解答

# 题目

  • 从输入 url 到得到 html 的详细过程
  • window.onload 和 DOMContentLoaded 的区别?

# 知识点

  • 加载资源的形式
  • 加载一个资源的过程
  • 浏览器渲染页面的过程

# 加载资源的形式

  • 输入 url (或跳转页面)加载 html
  • url: https://www.imooc.com/
  • 加载 html 中的静态资源
  • script 标签中资源的加载: <script src="/static/jsjquery.js"></script >

# 加载一个资源的过程

  1. 浏览器根据 DNS 服务器得到域名的 IP 地址
  2. 向这个 IP 的机器发送 http 请求
  3. 服务器收到、处理并返回 http 请求
  4. 浏览器得到返回内容

# 浏览器渲染页面的过程:

1. 根据 HTML 结构生成 DOM Tree 2. 根据 CSS 生成 CSSOM 3. 将 DOM 和 CSSOM 整合形成 RenderTree 4. 根据 RenderTree 开始渲染和展示 5. 遇到 <script > 时,会执行并阻止渲染。

# 思考

  • 为何要把 css 放在 head 中?
    • css 放在 body 标签尾部时,DOMTree 构建完成之后便开始构建 RenderTree, 并计算布局渲染网页,等加载解析完 css 之后,开始构建 CSSOMTree, 并和 DOMTree 重新构建 RenderTree, 重新计算布局渲染网页
    • css 放在 head 标签中时,先加载 css, 之后解析 css 构建 CSSOMTree, 于此同时构建 DOMTree, CSSOMTree 和 DOMTree 都构建完毕之后开始构建 RenderTree, 计算布局渲染网页

对比两者,css 放在 head 标签中比 css 放在 body 标签尾部少了一次构建 RenderTree, 一次计算布局和一次渲染网页,因此性能会更好;并且 css 放在 body 标签尾部时会在网页中短暂出现 "裸奔" 的 HTML, 这不利于用户体验


  • 为何要把 js 放在 body 最下面?既然 Dom 树完全生成好后才能显示 “没有图片的首屏”,浏览器又必须读完全部 HTML 才能生成完整的 Dom 树,script 标签不放在 body 底部是不是也一样?

    • —— JS 放在底部可以保证让浏览器优先渲染完现有的 HTML 内容,让用户先看到内容,体验好。另外,JS 执行如果涉及 DOM 操作,得等待 DOM 解析完成才行,JS 放在底部执行时,HTML 肯定都解析成了 DOM 结构。JS 如果放在 HTML 顶部,JS 执行的时候 HTML 还没来得及转换为 DOM 结构,可能会报错。 JS 一定要放在 Body 的最底部么?聊聊浏览器的渲染机制

# window.onload 和 DOMContentLoaded

mark

# 解答

  • 从输入 url 到得到 html 的详细过程

    • 浏览器根据 DNS 服务器得到域名的 IP 地址
    • 向这个 IP 的机器发送 http 请求
    • 服务器收到、处理并返回 http 请求
    • 浏览器得到返回内容
  • window.onload 和 DOMContentLoaded 的区别?

    • window.onload: 页面的全部资源加载完才会执行,包括图片、视频等
    • DOMContentLoaded: DOM 渲染完即可执行,此时图片、视频还没有加载完

# 8-2 性能优化

2018 前端性能优化清单

关于 性能优化 是个大的面,这篇文章主要涉及到 前端 的几个点,如 前端性能优化 的流程、常见技术手段、工具等。

提及 前端性能优化 ,大家应该都会想到 雅虎军规,本文会结合 雅虎军规 融入自己的了解知识,进行的总结和梳理 😜

# 我们先来看看 👀 雅虎军规 的 35 条 :

  • 尽量减少 HTTP 请求个数 —— 须权衡
  • 使用 CDN(内容分发网络)
  • 为文件头指定 Expires 或 Cache-Control ,使内容具有缓存性。
  • 避免空的 src 和 href
  • 使用 gzip 压缩内容
  • 把 CSS 放到顶部
  • 把 JS 放到底部
  • 避免使用 CSS 表达式
  • 将 CSS 和 JS 放到外部文件中
  • 减少 DNS 查找次数
  • 精简 CSS 和 JS
  • 避免跳转
  • 剔除重复的 JS 和 CSS
  • 配置 ETags
  • 使 AJAX 可缓存
  • 尽早刷新输出缓冲
  • 使用 GET 来完成 AJAX 请求
  • 延迟加载
  • 预加载
  • 减少 DOM 元素个数
  • 根据域名划分页面内容
  • 尽量减少 iframe 的个数
  • 避免 404
  • 减少 Cookie 的大小
  • 使用无 cookie 的域
  • 减少 DOM 访问
  • 开发智能事件处理程序
  • 用 代替 @import
  • 避免使用滤镜
  • 优化图像
  • 优化 CSS Spirite
  • 不要在 HTML 中缩放图像 —— 须权衡
  • favicon.ico 要小而且可缓存
  • 保持单个内容小于 25K
  • 打包组件成复合文本

  • 这本身就是一个综合性的问题
  • 没有标准答案,如果要非常全面,能写一本书
  • 只关注核心点,针对面试

# 原则

  • 多使用内存、缓存或其他方法
  • 减少 CPU 计算、减少网络请求

# 从哪里入手

  • 加载页面和静态资源
  • 页面渲染

# 加载资源优化

  • 静态资源的压缩合并
  • 静态资源缓存
  • 使用 CDN 让资源加载更快
  • 使用 SSR 后端渲染,数据直接输出到 HTML 中

# 渲染优化

  • CSS 放前面 ,JS 放后面
  • 懒加载 (图片懒加载、下拉加载更多)
  • 减少 DOM 查询 ,对 DOM 查询做缓存
  • 减少 DOM 操作,多个操作尽量合并 在一起执行
  • 事件节流
  • 尽早执行操作 (如 DOMContentLoaded)

# 优化示例

展示几个优化示例

# 资源合并

<script src="a.js"></script>
<script src="b.js"></script>
<script src="c.js"></script>

//资源合并
<script src="abc.js"></script>

# 缓存

  • 通过连接名字控制缓存
  • <script src=“adc_1.js”></script >
  • 只有内容改变的时候,连接名称才会改变
  • <script src=“adc_2.js”></script >

# CDN

2 个在线的好用的 CDN 网站:

https://www.bootcdn.cn/

https://cdnjs.com/

mark

# 使用 SSR 后端渲染

  • 现在 Vue React 提出了这样的概念
  • 其实 jsp php asp 都属于后端渲染

# 懒加载

mark

# 缓存 DOM 查询

mark

# 合并 DOM 插入

mark

# 事件节流

mark

# 尽早操作

mark

开发环境

# 开发环境

讲解在面试过程中,面试官可能会问及的前端开发环境的问题,例如 IDEGit模块化打包工具上线流程 ,这些类别的题目。

知识点:

7-1 IDE

7-2 Git

7-3 模块化

7-4 构建工具

7-5 上线和回滚

# 关于开发环境

  • 面试官想通过开发环境了解面试者的经验
  • 开发环境,最能体现工作产出的效率
  • 会以聊天的形式为主,而不是出具体的问题

# 知识点

IDEGitJS 模块化打包工具上线流程

  • IDE ( 写代码的效率 )
  • git ( 代码版本管理 ,多人协作开发 )
  • JS 模块化
  • 打包工具
  • 上线回滚的流程

# 7-1 IDE

  • webstorm ( 我平常基本用的就是 webstorm
  • sublime
  • vscode
  • atom
  • 插件 插件 插件 !!!

mark

mark

注意:

  • 千万不要说你使用 Dreamweaver 或者 notpad ++
  • 不做 .net 也不要用 Visual Studio
  • 不做 java 也不要用 eclipse

# 7-2 Git

Git 详细介绍:可参考的的博客:Git 篇

  • 正式项目都需要代码版本管理
  • 大型项目需要多人协作开发
  • Git 和 linux 是一个作者

# 7-3 模块化

模块化编程就是通过组合一些相对独立可复用的模块来进行功能的实现,其最核心的两部分是定义模块引入模块

  • 定义模块时,每个模块内部的执行逻辑是不被外部感知的,只是导出(暴露)出部分方法和数据;
  • 引入模块时,同步 / 异步去加载待引入的代码,执行并获取到其暴露的方法和数据;

模块化的发展情况 :无模块化–>CommonJS 规范–>AMD 规范–>CMD 规范–>ES6 模块化

Javascript 模块化指北

这一次,我要弄懂 javascript 的模块化

# 知识点

  • 不使用模块化的情况
  • 使用模块化
  • AMD
  • CommonJS

# 无模块化

script 标签引入 js 文件,相互罗列,但是被依赖的放在前面,否则使用就会报错。如下:

<script src="jquery.js"></script>
  <script src="jquery_scroller.js"></script>
  <script src="main.js"></script>
  <script src="other1.js"></script>
  <script src="other2.js"></script>
  <script src="other3.js"></script>

​ 即简单的将所有的 js 文件统统放在一起。但是这些文件的顺序还不能出错,比如 jquery 需要先引入,才能引入 jquery 插件,才能在其他的文件中使用 jquery。缺点很明显:

  • 污染全局作用域
  • 维护成本高
  • 依赖关系不明显

# AMD 规范

异步模块定义(AMD)API 指定了一种定义模块的机制,以便可以异步加载模块及其依赖项。这特别适用于浏览器环境,其中模块的同步加载会导致性能,可用性,调试和跨域访问问题。

  • require.js https://requirejs.org/
  • 全局 define 函数
  • 全局 require 函数
  • 依赖 JS 会自动、异步加载

AMD 标准中,定义了下面三个 API:

require([module], callback);
define(id, [depends], callback);
require.config();

即通过 define 来定义一个模块,然后使用 require 来加载一个模块,使用 require.config () 指定引用路径。

# 举例说明:

<script
  src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"
  data-main="./main.js"
></script>

main.js:

define(["./a.js"], function (a) {
  var data = new Date();
  a.printDate(data);
});

a.js:

define(["./a-util.js"], function (aUtil) {
  var a = {
    printDate: function (data) {
      console.log(aUtil.aGetFormatDate(data));
    },
  };
  return a;
});

a-util.js:

define(["./util.js"], function (util) {
  var aUtil = {
    aGetFormatDate: function (data) {
      return util.getFormatDate(data, 2);
    },
  };
  return aUtil;
});

util.js:

define(function () {
  var util = {
    getFormatDate: function (data, type) {
      if (type === 1) {
        return "2019-07-19";
      }
      if (type === 2) {
        return "2019年6月20日";
      }
    },
  };
  return util;
});

mark

# CommonJS

2009 年 ry 发布 Node.js 的第一个版本,CommonJS 作为其中最核心的特性之一,适用于服务端下的场景;历年来的考察和时间的洗礼,以及前端工程化对其的充分支持,CommonJS 被广泛运用于 Node.js 和浏览器;

// Core Module
const cp = require("child_process");
// Npm Module
const axios = require("axios");
// Custom Module
const foo = require("./foo");

module.exports = { axios };
exports.foo = foo;

规范

  • module (Object): 模块本身
  • exports (*): 模块的导出部分,即暴露出来的内容
  • require (Function): 加载模块的函数,获得目标模块的导出值(基础类型为复制,引用类型为浅拷贝),可以加载内置模块、npm 模块和自定义模块。

nodejs 模块化规范,现在被大量用前端,原因

  • 前端开发依赖的插件课库,都可以从 npm 中获取
  • 构建工具的高度自动化,使得使用 npm 的成本非常低
  • CommonJS 不会异步加载 JS ,而是同步一次性加载出来

# 使用 CommonJS

module (Object)、exports (*)、require (Function)

//util.ls
module.export = {
  getFormatDate: function (data, type) {
    if (type === 1) {
      return "2019-07-19";
    }
    if (type === 2) {
      return "2019年6月20日";
    }
  },
};

//a-util.js
var util = require("util.js");
module.export = {
  aGetFormatDate: function (data) {
    return util.getFormatDate(data, 2);
  },
};

# 特性总结

  • 同步执行模块声明和引入逻辑,分析一些复杂的依赖引用(如循环依赖)时需注意;
  • 缓存机制,性能更优,同时限制了内存占用;
  • Module 模块可供改造的灵活度高,可以实现一些定制需求(如热更新、任意文件类型模块支持);

# AMD 和 CommonJS 的使用场景

  • 需要异步加载 JS ,使用 AMD
  • 使用了 npm 之后建议使用 CommonJS

# 重点总结

  • AMD
  • CommonJS
  • 两者的区别

# 7-4 构建工具

我们一定会感叹前端技术发展之快,各种可以提高开发效率的新思想和框架层出不穷。但是他们都有一个共同特点:源代码无法直接运行,必须通过转换后才能正常运行。

比如:Grunt 、Gulp、FIS 3、Webpack

前端构建工具发展及其比较

webpack 学习可参考我的博客 我的另一篇博客 均有 涉及。

构建工具就是做这件事,将源代码转换成可以执行的 JavaScript、CSS、HTML 代码,包括如下内容:

  • 代码转换:将 TypeScript 编译成 JavaScript、将 SCSS 编译成 CSS 等。
  • 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。
  • 代码分割:提取多个页面的公共代码,提取首屏不需要执行部分代码让其异步记在。
  • 模块合并:在采用模块化的项目里会有很多个模块和文件,需要通过构建功能将模块分类合并成一个文件。
  • 自动刷新:监听本地源代码变化,自动重新构建、刷新浏览器。
  • 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
  • 自动发布:更新代码后,自动构建出线上发布代码并传输给发布系统。

构建其实是工程化、自动化思想在前端开发中的体现,将一系列流程用代码去实现,让代码自动化地执行这一系列复杂的流程。构建为前端开发注入了更大的活力,解放了我们的生产力。

历史上先后出现了一系列构建工具,他们各有优缺点。由于前端工程师很熟悉 JavaScript,Node.js 又可以胜任所有构建需求,所以大多数构建工具都是用 Node.js 开发的。

# 7-5 上线和回滚

  • 不会有具体的问题,交流询问的方式

# 知识点

  • 上线和回滚的基本流程
  • linux 基本命令

# 上线和回滚的基本流程

  • 是非常重要的开发环节

  • 各个公司的具体流程不同

  • 由专门的工具后者系统完成,我们无需关心细节

  • 如果你没有参与过,面试时也要说出要点

  • 只讲要点,具体实现无法讲解

# 上线流程要点

  • 将测试完成的代码提交到 git 版本库的 master 分支
  • 将当前服务器的代码全部打包并记录版本号,备份
  • 将 master 分支的代码提交覆盖到线上服务器,生成新版本号

# 回滚的流程要点

  • 将当前服务器的代码打包并记录版本号,备份
  • 将备份的上一个版本号解压,覆盖到线上服务器,并生成新的版本号

# linux 基本命令

  • 服务器使用 linux 居多,server 版,只有命令行
  • 测试环境要匹配线上环境,因此也是 linux
  • 经常需要登录测试机来自己配置,获取数据

Git 学习

# Git 学习

Git 学习

Git 的优势

Git 与 SVN 的主要区别

安装 Git

初始化 Git 仓储 /(仓库)

配置使用者的用户名和邮箱

把代码 存储到仓库中

Git 查看日志

Git 版本回退

Git 分支的新建与合并

上传至 github

通过 ssh 方式 上传 代码

push 和 pull 简写

# 什么是 Git?

git – fast 版本控制

Git 是由 “Linux 之父” Linus Torvalds 创建的。因为他发现找不到满意的方案来管理 Linux Kernel 联合开发的版本控制,就自己写了 Git。

官方网站

# Git 的优势

说到优势,那么自然是相对与 SVN 而言的

  1. ** 版本库本地化,支持离线提交,相对独立不影响协同开发。** 每个开发者都拥有自己的版本控制库,在自己的版本库上可以任意的执行提交代码、创建分支等行为。例如,开发者认为自己提交的代码有问题?没关系,因为版本库是自己的,回滚历史、反复提交、归并分支并不会影响到其他开发者。

  2. ** 更少的 “仓库污染”。**git 对于每个工程只会产生一个.git 目录,这个工程所有的版本控制信息都在这个目录中,不会像 SVN 那样在每个目录下都产生.svn 目录。

  3. ** 把内容按元数据方式存储,完整克隆版本库。** 所有版本信息位于.git 目录中,它是处于你的机器上的一个克隆版的版本库,它拥有中心版本库上所有的东西,例如标签、分支、版本记录等。

  4. ** 支持快速切换分支方便合并,比较合并性能好。** 在同一目录下即可切换不同的分支,方便合并,且合并文件速度比 SVN 快。

  5. ** 分布式版本库,无单点故障,内容完整性好。** 内容存储使用的是 SHA-1 哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。

# Git 与 SVN 的主要区别

Git 是分布式 SCM,而 SVN 是基于服务器的,也就是说每个开发者本地都有一套 git 库,每个人维护自己的版本(或者合并其他人的版本),而 SVN 是每个人写完代码后都及时的 checkin 到服务器上,进行合并。

# 安装 Git

廖雪峰的官方网站 git 安装

鼠标 右键,选中 Git Bash Here 在里面输入相关命令:

mark

# 使用 Git

# 初始化 Git 仓储 /(仓库)

  1. 新建一个文件夹

mark

  1. 进入文件夹,鼠标右键 选中 Git Bash Here ,输入命令 git init 回车。

mark

# 配置使用者的用户名和邮箱

配置使用者的用户名和邮箱 (自报家门), 每一次备份都会把当前备份者的信息存储起来。

配置用户名git config --global user.name "zhouchen"

配置邮箱git config --global user.email "1583741285@qq.com"

  1. 配置使用者的用户名进入文件夹,鼠标右键 选中 Git Bash Here

    输入命令 git config --global user.name "zhouchen" 回车 。

mark

  1. 同样的配置使用者的 邮箱进入文件夹,鼠标右键 选中 Git Bash Here

    输入命令 git config --global user.email "1583741285@qq.com" 回车 。

mark

# 把代码 存储到仓库中

把代码 存储到仓库中

需求:将 readme.md 文件 存储到仓库中

mark

  1. 进入文件夹,鼠标右键 选中 Git Bash Here

    输入命令 git add ./readme.md 回车 。

mark

  1. 紧接上一步,输入命令 git commit -m "add Introductions" 回车 。

add Introductions----- 可以自定义,一个辅助的解释 说明

mark

# 查看提交状态

命令: git status

mark

# add 补充 – 添加到大门口

命令 : git add .

# 一次性 – 放入仓库

合并 add 和 commit 命令

命令: git commit --all -m "这是一次性的操作"

# Git 查看日志

查看日志 ---- 能够查看自己提交的信息

git log :查看历史提交的日志

git log --oneline 可以看到精简版的日志

命令: git log

mark

命令: git log --oneline

mark

# Git 版本回退

git 版本回退

场景:如果最后一次提交的代码有误,可以通过 git 版本回退,回到代码无误的那一个状态。

# 根据索引回退

  1. 使用命令 git log --oneline 查看状态

mark

  1. 命令: git reset --hard Head~0 ( 向后退 0 次)

mark

  1. 命令: git reset --hard Head~1 (向后退 1 次);此时代码回退了一次

mark

mark

# 通过版本号回退

通过版本号回退

mark

命令: git reset --hard ac97cbc 回车执行

mark

mark

# git reflog

可以看到每一次切换版本记录

命令: git reflog 查看总体记录

mark

# Git 分支的新建与合并

git 创建分支

运用场景:放开发者只开发完成部分代码,想保存代码,为了能够后续继续开发,可以创建分支。

现在让我们来看一个简单的分支与合并的例子,实际工作中大体也会用到这样的工作流程:

  1. 开发某个网站。
  2. 为实现某个新的需求,创建一个分支。
  3. 在这个分支上开展工作。

假设此时,你突然接到一个电话说有个很严重的问题需要紧急修补,那么可以按照下面的方式处理:

  1. 返回到原先已经发布到生产服务器上的分支。
  2. 为这次紧急修补建立一个新分支,并在其中修复问题。
  3. 通过测试后,回到生产服务器所在的分支,将修补分支合并进来,然后再推送到生产服务器上。
  4. 切换到之前实现新需求的分支,继续工作。

# Git 分支的新建

新建分支 :命令: git branch <name>

mark

查看分支信息 :命令: git branch绿色 --> 代表正处于此分支)

mark

切换分支 :命令 git checkout dev

mark

# 在分支中提交部分代码

类似于主分支 提交(一样)

mark

# Git 合并分支

合并分支

需求:将 dev 分支 合并到主分支(master 分支)上。

  1. 切换到 master 分支:命令: git checkout master

mark

  1. 合并 dev 分支 到 master 分支上:命令: git merge dev

mark

  1. 查看自己的提交记录 命令: git log --oneline

mark

# 上传至 github

git push [地址] master

参考廖雪峰的官方网站 github 篇

【地址】:例如:https://github.com/ZhChen7/Travel.git

git push 推项目到 github: git push [地址] master

# 从 github 拉下项目

github 拉下项目

【地址】:例如:https://github.com/ZhChen7/Travel.git

git pull 从 github 拉下项目 : git pull [地址] master

git clone 从 github 复制项目(往往在第一次使用): git clone [地址]

# 通过 ssh 方式 上传 代码

公钥 和 私钥 ;两者之间是有关联的。

  1. 生成 公钥私钥

    命令: ssh-keygen -t rsa -C <邮箱>

mark

mark

找到生成的文件

mark

给自己的 github 配置 ssh

mark

# 提交冲突

pull 还是 先 push

应用场景:多人共同提交时,产生提交冲突时。

解决方案先 pull 然后 后 push

# push 和 pull 简写

push 和 pull 简写

【地址简写】: git remote add <变量名> <远程地址>

配置远程地址(设置别名):方便每一次不用输入很长的地址

命令: git remote add origin git@github.com:ZhChen7/Travel.git

即:为 git@github.com:ZhChen7/Travel.git 设置别名: **origin **

# 默认关联

当我们在 push 时,加上 -u 参数 ,那么在下次 push 时;只用 写 git push 即可上传代码。

(加上 -u 之后,git 会把当前分支 与 远程指定的分支 进行关联

下次 直接写 git push 相当于 写 git push origin master

数组API

# JavaScript 数组那些事

JavaScript 数据结构与算法 — 数组

迭代器函数

ECMAScript 6 和 数组 新功能

# 数组

数组对象的作用是:使用单独的变量名来存储一系列的值 **.**

总的方法参考

# 数组相关操作:

# 添加元素

  • 添加末尾元素

    arr.push(11);
    arr.push(12, 13);
  • 添加首位元素

    arr.unshift();

# 删除元素

  • 删除末尾元素

    arr.pop();
  • 删除首位元素

    arr.shift();

# 在任意位置添加或删除元素

splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。

参考

var months = ["Jan", "March", "April", "June"];
months.splice(1, 0, "Feb");

// 插入到索引为1的位置
console.log(months);
// 结果: Array ['Jan', 'Feb', 'March', 'April', 'June']

months.splice(4, 1, "May");
// 从索引为4开始 删除一个数据并添加一个 May数据
console.log(months);
// 结果: Array ['Jan', 'Feb', 'March', 'April', 'May']

# 迭代器函数

处理集合中的每个项是很常见的操作。JavaScript 提供了许多迭代集合的方法,从简单的 for 循环到 map() filter() 。迭代器和生成器将迭代的概念直接带入核心语言,并提供了一种机制来自定义 for...of 循环的行为 。

# every:

**every()** 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。

参考

描述: every 方法为数组中的每个元素执行一次 callback 函数,直到它找到一个会使 callback 返回 falsy 的元素。如果发现了一个这样的元素, every 方法将会立即返回 false 。否则, callback 为每一个元素返回 trueevery 就会返回 truecallback 只会为那些已经被赋值的索引调用。不会为那些被删除或从未被赋值的索引调用。

注意:若收到一个空数组,此方法在一切情况下都会返回 true

function isBigEnough(element, index, array) {
  return element >= 10;
}
[12, 5, 8, 130, 44].every(isBigEnough); // false
[12, 54, 18, 130, 44].every(isBigEnough); // true

# some:

**some()** 方法测试是否至少有一个元素可以通过被提供的函数方法。该方法返回一个 Boolean 类型的值。

参考

some() 为数组中的每一个元素执行一次 callback 函数,直到找到一个使得 callback 返回一个 “真值”(即可转换为布尔值 true 的值)。如果找到了这样一个值, some() 将会立即返回 true 。否则, some() 返回 falsecallback 只会在那些” 有值 “的索引上被调用,不会在那些被删除或从来未被赋值的索引上调用。

callback 被调用时传入三个参数:元素的值,元素的索引,被遍历的数组。

function isBelowThreshold(currentValue) {
  return currentValue < 40;
}

var array1 = [1, 30, 41, 29, 10, 13];
var array2 = [41, 42, 43, 44, 45];
array1.some(isBelowThreshold); //true
array2.some(isBelowThreshold); //false

# foreach:

**forEach()** 方法对数组的每个元素执行一次提供的函数。

forEach 方法按升序为数组中含有效值的每一项执行一次 callback 函数,那些已删除或者未初始化的项将被跳过(例如在稀疏数组上)。

参考

注意: foreach 没有办法中止或者跳出 forEach() 循环,除了抛出一个异常。如果你需要这样,使用 forEach() 方法是错误的。

若你需要提前终止循环,你可以使用:

这些数组方法可以对数组元素判断,以便确定是否需要继续遍历: every() some() find() findIndex()

译者注:若条件允许,也可以使用 filter() 提前过滤出需要遍历的部分,再用 forEach() 处理。

var array1 = ["a", "b", "c"];

array1.forEach(function (element) {
  console.log(element);
});

// expected output: "a"
// expected output: "b"
// expected output: "c"

# map 和 filter 方法

JavaScript 还有两个会 返回新数组 的遍历方法.

# map

**map()** 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

参考

map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。 callback 每次执行后的返回值(包括 undefined )组合起来形成一个新数组。 callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。

var array1 = [1, 4, 9, 16];

// pass a function to map
const map1 = array1.map((x) => x * 2);

console.log(map1);
// expected output: Array [2, 8, 18, 32]

# filter

**filter()** 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素。

参考

filter 为数组中的每个元素调用一次 callback 函数,并利用所有使得 callback 返回 true 或等价于 true 的值的元素创建一个新数组。 callback 只会在已经赋值的索引上被调用,对于那些已经被删除或者从未被赋值的索引不会被调用。那些没有通过 callback 测试的元素会被跳过,不会被包含在新数组中.

var words = ["spray", "limit", "elite", "exuberant", "destruction", "present"];

const result = words.filter((word) => word.length > 6);

/* 上面是ES6里面的箭头函数,下面是完整版
* const result = words.filter(function (e) {
    return e.length>6
});
**/

console.log(result);
// expected output: Array ["exuberant", "destruction", "present"]

# reduce:

**reduce()** 方法对数组中的每个元素执行一个由您提供的 reducer 函数 (升序执行),将其结果汇总为单个返回值。

参考

reduce 为数组中的每一个元素依次执行 callback 函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:

  • accumulator 累计器
  • currentValue 当前值
  • currentIndex 当前索引
  • array 数组
const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;

// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10

// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15

假如运行下段 reduce() 代码

[0, 1, 2, 3, 4].reduce(function (
  accumulator,
  currentValue,
  currentIndex,
  array
) {
  return accumulator + currentValue;
});

callback 被调用四次,每次调用的参数和返回值如下表

mark

# ECMAScript 6 和 数组 新功能

ECMAScript 6 和 数组 新功能

这里主要介绍下 数组 ES6 新特性

Iterator 和 for…of 循环

# for …of 循环迭代

强大的 for-of 循环

还记得在《深入浅出 ES6(一):ES6 是什么》中我向你们承诺过的话么?ES6 不会破坏你已经写好的 JS 代码。目前看来,成千上万的 Web 网站依赖 for-in 循环,其中一些网站甚至将其用于数组遍历。如果想通过修正 for-in 循环增加数组遍历支持会让这一切变得更加混乱,因此,标准委员会在 ES6 中增加了一种新的循环语法来解决目前的问题。

参考


# 关于 for…in

绝对是一个糟糕的选择

for (var index in myArray) {
  // 千万别这样做
  console.log(myArray[index]);
}

绝对是一个糟糕的选择,为什么呢?

  • 在这段代码中,赋给 index 的值不是实际的数字,而是字符串 “0”、“1”、“2”,此时很可能在无意之间进行字符串算数计算,例如:“2” + 1 == “21”,这给编码过程带来极大的不便。
  • 作用于数组的 for-in 循环体除了遍历数组元素外,还会遍历自定义属性。举个例子,如果你的数组中有一个可枚举属性 myArray.name,循环将额外执行一次,遍历到名为 “name” 的索引。就连数组原型链上的属性都能被访问到。
  • 最让人震惊的是,在某些情况下,这段代码可能按照随机顺序遍历数组元素。
  • 简而言之,for-in 是为普通对象设计的,你可以遍历得到字符串类型的键,因此不适用于数组遍历。

# 强大的 for-of 循环

我们将要探究一下 for-of 循环的外表下隐藏着哪些强大的功能。

参考

优势:

  • 这是最简洁、最直接的遍历数组元素的语法
  • 这个方法避开了 for-in 循环的所有缺陷
  • 与 forEach () 不同的是,它可以正确响应 breakcontinuereturn 语句
for (var value of myArray) {
  console.log(value);
}

# 使用 ES6 新的迭代器

使用 ES6 新的迭代器

参考

# Iterator(遍历器)的概念

JavaScript 原有的表示 “集合” 的数据结构,主要是数组( Array )和对象( Object ),ES6 又添加了 MapSet 。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是 MapMap 的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator 的作用有三个

  • 一是为各种数据结构,提供一个统一的、简便的访问接口;

  • 二是使得数据结构的成员能够按某种次序排列;

  • 三是 ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费。

Iterator 的遍历过程是这样的 :

(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

(2)第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员。

(3)第二次调用指针对象的 next 方法,指针就指向数据结构的第二个成员。

(4)不断调用指针对象的 next 方法,直到它指向数据结构的结束位置。

每一次调用 next 方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含 valuedone 两个属性的对象。其中, value 属性是当前成员的值, done 属性是一个布尔值,表示遍历是否结束

var it = makeIterator(["a", "b"]);

it.next(); // { value: "a", done: false }
it.next(); // { value: "b", done: false }
it.next(); // { value: undefined, done: true }

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function () {
      return nextIndex < array.length
        ? { value: array[nextIndex++], done: false }
        : { value: undefined, done: true };
    },
  };
}

上面代码定义了一个 makeIterator 函数,它是一个遍历器生成函数,作用就是返回一个遍历器对象。对数组 ['a', 'b'] 执行这个函数,就会返回该数组的遍历器对象(即指针对象) it

# 默认 Iterator 接口

Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即 for...of 循环(详见下文)。当使用 for...of 循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。

一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是 “可遍历的”(iterable)。

ES6 的有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被 for...of 循环遍历。原因在于,这些数据结构原生部署了 Symbol.iterator 属性(详见下文),另外一些数据结构没有(比如对象)。凡是部署了 Symbol.iterator 属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。

原生具备 Iterator 接口的数据结构如下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

下面的例子是数组的 Symbol.iterator 属性。

let arr = ["a", "b", "c"];
let iter = arr[Symbol.iterator]();

iter.next(); // { value: 'a', done: false }
iter.next(); // { value: 'b', done: false }
iter.next(); // { value: 'c', done: false }
iter.next(); // { value: undefined, done: true }

上面代码中,变量 arr 是一个数组,原生就具有遍历器接口,部署在 arrSymbol.iterator 属性上面。所以,调用这个属性,就得到遍历器对象。

# 数组的 entries、keys 和 values 方法

ES6 提供三个新的方法 —— entries()keys()values() —— 用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用 for...of 循环进行遍历,唯一的区别是 keys() 方法是对 键名 的遍历、 values() 方法是对 键值 的遍历, entries() 方法是对 键值对 的遍历。

for (let index of ["a", "b"].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ["a", "b"].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ["a", "b"].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

如果不使用 for...of 循环,可以手动调用遍历器对象的 next 方法,进行遍历。

let letter = ["a", "b", "c"];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']

# Array.from () 方法

Array.from() 方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

参考

let arrayLike = {
  0: "a",
  1: "b",
  2: "c",
  length: 3,
};

// ES5的写法 ==> [].slice.call(arguments)能将具有length属性的对象转成数组:
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

你遇到过 [].slice.call () 吗?

实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的 arguments 对象。 Array.from() 方法都可以将它们转为真正的数组。

// NodeList对象
let ps = document.querySelectorAll("p");
Array.from(ps).forEach(function (p) {
  console.log(p);
});

// arguments对象
function foo() {
  var args = Array.from(arguments);
  // ...
}

上面代码中, querySelectorAll 方法返回的是一个类似数组的对象,可以将这个对象转为真正的数组,再使用 forEach 方法。

只要是部署了 Iterator 接口的数据结构, Array.from() 方法都能将其转为数组。

Array.from("hello");
// ['h', 'e', 'l', 'l', 'o']

let namesSet = new Set(["a", "b"]);
Array.from(namesSet); // ['a', 'b']

值得提醒的是,扩展运算符( ... )也可以将某些数据结构转为数组。

// arguments对象
function foo() {
  const args = [...arguments];
}

// NodeList对象
[...document.querySelectorAll("div")];

扩展运算符背后调用的是遍历器接口( Symbol.iterator ),如果一个对象没有部署这个接口,就无法转换。 Array.from() 方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有 length 属性。因此,任何有 length 属性的对象,都可以通过 Array.from() 方法转为数组,而此时扩展运算符就无法转换。

对于还没有部署该方法的浏览器,可以用 Array.prototype.slice 方法替代。

const toArray = (() =>
  Array.from ? Array.from : (obj) => [].slice.call(obj))();

Array.from() 方法还可以接受第二个参数,作用类似于数组的 map` 方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

Array.from(arrayLike, (x) => x * x);
// 等同于
Array.from(arrayLike).map((x) => x * x);

arr1 = Array.from([1, 2, 3], (e) => e * e);
console.log(arr1); //  [ 1, 4, 9 ]

# Array.of () 方法

Array.of() 方法用于将一组值,转换为数组。

Array.of(3, 11, 8); // [3,11,8]
Array.of(3); // [3]
Array.of(3).length; // 1

这个方法的主要目的,是弥补数组构造函数 Array() 的不足。因为参数个数的不同,会导致 Array() 的行为有差异。

Array(); // []
Array(3); // [, , ,]
Array(3, 11, 8); // [3, 11, 8]

let arr = new Array(3);
console.log(arr); //[ <3 empty items> ]

let arr = new Array(3);
console.log(arr.length); // 3

上面代码中, Array 方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于 2 个时, Array() 才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。

Array.of() 方法基本上可以用来替代 Array()new Array() ,并且不存在由于参数不同而导致的重载。它的行为非常统一。

Array.of(); // []
Array.of(undefined); // [undefined]
Array.of(1); // [1]
Array.of(1, 2); // [1, 2]

Array.of() 方法总是返回参数值组成的数组。如果没有参数,就返回一个空数组。

Array.of() 方法可以用下面的代码模拟实现。

function ArrayOf() {
  return [].slice.call(arguments);
}

# 数组实例的 fill () 方法

fill() 方法使用给定值,填充一个数组。

参考

["a", "b", "c"].fill(7); // [7, 7, 7]

let arr = new Array(3).fill(7);
console.log(arr); // [7, 7, 7]

上面代码表明, fill() 方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。

fill() 方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

["a", "b", "c"]
  .fill(7, 1, 2) // ['a', 7, 'c']
  [("a", "b", "c", 1, 2, 3)].fill(7, 1, 4); // [ 'a', 7, 7, 7, 2, 3 ]

上面代码表示, fill() 方法从 1 号位开始,向原数组填充 7,到 2 号位之前结束。

# 数组实例的 copyWithin () 方法

数组实例的 copyWithin() 方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。

Array.prototype.copyWithin(target, start = 0, end = this.length)

  • 它接受三个参数。
    • target(必需):从该位置开始替换数据。
    • start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
    • end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。

这三个参数都应该是数值,如果不是,会自动转为数值。

[1, 2, 3, 4, 5].copyWithin(0, 3); // [4, 5, 3, 4, 5]

上面代码表示将从 3 号位直到数组结束的成员(4 和 5),复制到从 0 号位开始的位置,结果覆盖了原来的 1 和 2。

// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5]

// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1) // [4, 2, 3, 4, 5]

// 将3号位复制到0号位
[].copyWithin.call({length: 5, 3: 1}, 0, 3) // {0: 1, 3: 1, length: 5}

// 将2号位到数组结束,复制到0号位
let i32a = new Int32Array([1, 2, 3, 4, 5]);

i32a.copyWithin(0, 2);// Int32Array [3, 4, 5, 4, 5]

// 对于没有部署 TypedArray 的 copyWithin 方法的平台
// 需要采用下面的写法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]

# 扩展运算符

扩展运算符(spread)是三个点( ... )。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

console.log(...[1, 2, 3]) // 1 2 3

console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5

[...document.querySelectorAll('div')] // [<div>, <div>, <div>]

该运算符主要用于函数调用。

function push(array, ...items) {
  array.push(...items);
}

function add(x, y) {
  return x + y;
}

const numbers = [4, 38];
add(...numbers); // 42

上面代码中, array.push(...items)add(...numbers) 这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。

扩展运算符与正常的函数参数可以结合使用,非常灵活。

function f(v, w, x, y, z) {}
const args = [0, 1];
f(-1, ...args, 2, ...[3]);

# 替代数组的 apply 方法

由于扩展运算符可以展开数组,所以不再需要 apply 方法,将数组转为函数的参数了。

// ES5 的写法
function f(x, y, z) {}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的写法
function f(x, y, z) {}
let args = [0, 1, 2];
f(...args);

下面是扩展运算符取代 apply 方法的一个实际的例子,应用 Math.max 方法,简化求出一个数组最大元素的写法。

// ES5 的写法
Math.max.apply(null, [14, 3, 77]);

// ES6 的写法
Math.max(...[14, 3, 77]);

// 等同于
Math.max(14, 3, 77);

上面代码中,由于 JavaScript 不提供求数组最大元素的函数,所以只能套用 Math.max 函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用 Math.max 了。

另一个例子是通过 push 函数,将一个数组添加到另一个数组的尾部。

// ES5的 写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6 的写法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);

# 扩展运算符的应用

扩展运算符的应用

# 复制数组

扩展运算符提供了复制数组的简便写法

const a1 = [1, 2];

const a2 = [...a1]; // 写法一

const [...a2] = a1; // 写法二

上面的两种写法, a2 都是 a1 的克隆。

# 合并数组

扩展运算符提供了数组合并的新写法。

// ES5
[1, 2].concat(more)

// ES6
[1, 2, ...more]
var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];

// ES5的合并数组
arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ]

// ES6的合并数组
[...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]

# 字符串

扩展运算符还可以将字符串转为真正的数组。

[..."hello"]; // [ "h", "e", "l", "l", "o" ]

上面的写法,有一个重要的好处,那就是能够正确识别四个字节的 Unicode 字符。

'x\uD83D\uDE80y'.length // 4
[...'x\uD83D\uDE80y'].length // 3

# 数组实例的 find () 和 findIndex () 方法

数组实例的 find() 方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为 true 的成员,然后返回该成员。如果没有符合条件的成员,则返回 undefined

ECMAScript6—find () 和 findIndex () 方法 ------ 搜索

[1, 4, -5, 10].find((n) => n < 0); // -5

上面代码找出数组中第一个小于 0 的成员。

[1, 5, 10, 15].find(function (value, index, arr) {
  return value > 9;
}); // 10

上面代码中, find() 方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。

这两个方法都可以接受第二个参数,用来绑定回调函数的 **this 对象 ** 。

另外,这两个方法都可以发现 NaN ,弥补了数组的 indexOf() 方法的不足。

[NaN]
  .indexOf(NaN) // -1

  [NaN].findIndex((y) => Object.is(NaN, y)); // 0

上面代码中, indexOf 方法无法识别数组的 NaN 成员,但是 findIndex() 方法可以借助 Object.is 方法做到。

# 数组实例的 includes () 方法

Array.prototype.includes() 方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes() 方法类似。ES2016 引入了该方法。

ECMAScript7 —includes () 方法 ------ 搜索

[1, 2, 3]
  .includes(2) // true
  [(1, 2, 3)].includes(4) // false
  [(1, 2, NaN)].includes(NaN); // true

该方法的第二个参数表示搜索的起始位置,默认为 0 。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为 -4 ,但数组长度为 3 ),则会重置为从 0 开始。

[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true

[1, 2, 3, 4, 5]
  .includes(2, 1) //true
  [(1, 2, 3, 4, 5)].includes(2, 2); //false

没有该方法之前,我们通常使用数组的 indexOf() 方法,检查是否包含某个值。

if (arr.indexOf(el) !== -1) {
  // ...
}

indexOf() 方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于 -1 ,表达起来不够直观。二是,它内部使用严格相等运算符( === )进行判断,这会导致对 NaN 的误判。

[NaN].indexOf(NaN); // -1

另外,Map 和 Set 数据结构有一个 has 方法,需要注意与 includes() 区分。

  • Map 结构的 has 方法,是用来查找键名的,比如 Map.prototype.has(key)WeakMap.prototype.has(key)Reflect.has(target, propertyKey)
  • Set 结构的 has 方法,是用来查找值的,比如 Set.prototype.has(value)WeakSet.prototype.has(value)

# 数组的空位

数组的空位,是指数组的某一个位置没有任何值。比如, Array 构造函数返回的数组都是空位。

Array(3); // [, , ,]

上面代码中, Array(3) 返回一个具有 3 个空位的数组。

注意,空位不是 undefined ,一个位置的值等于 undefined ,依然是有值的。空位是没有任何值, in 运算符可以说明这一点。

0 in [undefined, undefined, undefined]; // true
0 in [, , ,]; // false

上面代码说明,第一个数组的 0 号位置是有值的,第二个数组的 0 号位置没有值。

# ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。

  • forEach() , filter() , reduce() , every()some() 都会跳过空位。
  • map() 会跳过空位,但会保留这个值
  • join()toString() 会将空位视为 undefined ,而 undefinednull 会被处理成空字符串。
// forEach方法
[,'a'].forEach((x,i) => console.log(x+'---'+i)); // a---1

// filter方法
['a',,'b'].filter(x => true) // ['a','b']

// every方法
[,'a'].every(x => x==='a') // true

// reduce方法
[1,,2].reduce((x,y) => return x+y) // 3

// some方法
[,'a'].some(x => x !== 'a') // false

# ES6 则是明确将空位转为 undefined

Array.from 方法会将数组的空位,转为 undefined ,也就是说,这个方法不会忽略空位。

Array.from(["a", , "b"]); // [ "a", undefined, "b" ]

扩展运算符( ... )也会将空位转为 undefined

[...["a", , "b"]]; // [ "a", undefined, "b" ]

copyWithin () 会连空位一起拷贝

[, "a", "b", ,].copyWithin(2, 0); // [,"a",,"a"]

fill() 会将空位视为正常的数组位置。

new Array(3).fill("a"); // ["a","a","a"]

for...of 循环也会遍历空位。

let arr = [, ,];
for (let i of arr) {
  console.log(1);
}
// 1
// 1

上面代码中,数组 arr 有两个空位, for...of 并没有忽略它们。如果改成 map 方法遍历,空位是会跳过的。

entries()keys()values()find()findIndex() 会将空位处理成 undefined

// entries()
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]

// keys()
[...[,'a'].keys()] // [0,1]

// values()
[...[,'a'].values()] // [undefined,"a"]

// find()
[,'a'].find(x => true) // undefined

// findIndex()
[,'a'].findIndex(x => true) // 0

# 组数排序

组数排序

# sort

**sort()** 方法用原地算法对数组的元素进行排序,并返回数组。排序算法现在是稳定的。默认排序顺序是根据字符串 Unicode 码点。

由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。

sort() 用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的 Unicode 位点进行排序。

参考

var months = ['March', 'Jan', 'Feb', 'Dec'];
months.sort();
console.log(months);
// expected output: Array ["Dec", "Feb", "Jan", "March"]

var array1 = [1, 30, 4, 21, 100000];
array1.sort();
console.log(array1);
// expected output: Array [1, 100000, 21, 30, 4]

# 描述:

​ 如果没有指明 compareFunction ,那么元素会按照转换为的字符串的诸个字符的 Unicode 位点进行排序。例如 “Banana” 会被排列到 “cherry” 之前。当数字按由小到大排序时,9 出现在 80 之前,但因为(没有指明 compareFunction ),比较的数字会先被转换为字符串,所以在 Unicode 顺序上 “80” 要比 “9” 要靠前。

如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:

  • 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;

  • 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);

  • 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。

  • compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。

function compare(a, b) {
  if (a < b) {
    // 按某种排序标准进行比较, a 小于 b
    return -1;
  }

  if (a > b) {
    return 1;
  }

  // a must be equal to b
  return 0;
}

要比较数字而非字符串,比较函数可以简单的以 a 减 b,如下的函数将会将数组升序排列

function compareNumbers(a, b) {
  return a - b;
}

sort 方法可以使用 函数表达式 方便地书写:

var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
  return a - b;
});
console.log(numbers);

也可以写成:
var numbers = [4, 2, 5, 1, 3];
numbers.sort((a, b) => a - b);
console.log(numbers);

// [1, 2, 3, 4, 5]

# 输出数组为字符串

输出数组为字符串

# toString 和 join

将数组转为字符串

# toString

**toString()** 返回一个字符串,表示指定的数组及其元素。

Array 对象覆盖了 Object toString 方法。对于数组对象, toString 方法连接数组并返回一个字符串,其中包含用逗号分隔的每个数组元素。

当一个数组被作为文本值或者进行字符串连接操作时,将会自动调用其 toString 方法。

var array1 = [1, 2, "a", "1a"];

console.log(array1.toString()); // "1,2,a,1a"

# join

**join()** 方法通过连接数组(或类数组对象)中的所有元素(由逗号或指定的分隔符字符串分隔)来创建并返回新字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。

指定用于分隔数组的每对相邻元素的字符串。如有必要,分隔符将转换为字符串。如果省略,则数组元素用逗号(“,”)分隔。如果 separator 是空字符串,则连接所有元素,它们之间没有任何字符。

var elements = ["Fire", "Air", "Water"];

console.log(elements.join()); //  "Fire,Air,Water"

console.log(elements.join("")); // "FireAirWater"

console.log(elements.join("-")); //  "Fire-Air-Water"

下面的示例创建一个 a 包含三个元素的数组,然后将数组连接四次:使用默认分隔符,然后是逗号和空格,然后是加号和空字符串

var a = ["Wind", "Water", "Fire"];
a.join(); // 'Wind,Water,Fire'
a.join(", "); // 'Wind, Water, Fire'
a.join(" + "); // 'Wind + Water + Fire'
a.join(""); // 'WindWaterFire'

加入类似数组的对象

以下示例 arguments 通过调用 Function.prototype.call on 来连接类似于 array 的对象() Array.prototype.join

function f(a, b, c) {
  var s = Array.prototype.join.call(arguments);
  console.log(s); // '1,a,true'
}
f(1, "a", true);
//expected output: "1,a,true"

操作系统课程设计

# 操作系统课程设计

mark

mark

课题 操作系统
院系 计算机与信息工程学院
班级 1701
姓名 周琛
学号 2017115010124

# 实验一 处理器调度

# 一、实习内容

​ 选择一个调度算法,实现处理器调度。

# 二、实习目的

​ 在采用多道程序设计的系统中,往往有若干个进程同时处于就绪状态。当就绪进程个数大于处理器数时,就必须依照某种策略来决定哪些进程优先占用处理器。本实习模拟在单处理器情况下的处理器调度,帮助学生加深了解处理器调度的工作。

# 三、实习题目

​ 本实习有两个题,学生可选择其中的一题做实习。

​ 第一题:设计一个按优先数调度算法实现处理器调度的程序。

# 提示:

  1. 假定系统有五个进程,每一个进程用一个进程控制块 PCB 来代表,进程控制块的格式为

    mark

其中,进程名 —— 作为进程的标识,假设五个进程的进程名分别为 P1,P2,P3,P4,P5。

  • 指针 —— 按优先数的大小把五个进程连成队列,用指针指出下一个进程的进程控制块的首地址,最后一个进程中的指针为 “0”。
  • 要求运行时间 —— 假设进程需要运行的单位时间数。
  • 优先数 —— 赋予进程的优先数,调度时总是选取优先数大的进程先执行。
  • 状态 —— 可假设有两种状态,“就绪” 状态和 “结束” 状态。五个进程的初始状态都为 “就绪”,用 “R” 表示,当一个进程运行结束后,它的状态为 “结束”,用 “E” 表示。
  1. 在每次运行你所设计的处理器调度程序之前,为每个进程任意确定它的 “优先数” 和 “要求运行时间”。

  2. 为了调度方便,把五个进程按给定的优先数从大到小连成队列。用一单元指出队首进程,用指针指出队列的连接情况。例:

    队首标志

mark

  1. 处理器调度总是选队首进程运行。采用动态改变优先数的办法,进程每运行一次优先数就减 “1”。由于本实习是模拟处理器调度,所以,对被选中的进程并不实际的启动运行,而是执行:

mark

** 提醒注意的是:** 在实际的系统中,当一个进程被选中运行时,必须恢复进程的现场,让它占有处理器运行,直到出现等待事件或运行结束。在这里省去了这些工作。

  1. 进程运行一次后,若要求运行时间 ¹0,则再将它加入队列(按优先数大小插入,且置队首标志);若要求运行时间 = 0,则把它的状态修改成 “结束”(E),且退出队列。
  2. 若 “就绪” 状态的进程队列不为空,则重复上面(4)和(5)的步骤,直到所有进程都成为 “结束” 状态。
  3. 在所设计的程序中应有显示或打印语句,能显示或打印每次被选中进程的进程名以及运行一次后进程队列的变化。
  4. 为五个进程任意确定一组 “优先数” 和 “要求运行时间”,启动所设计的处理器调度程序,显示或打印逐次被选中进程的进程名以及进程控制块的动态变化过程。

源代码:

#include <stdio.h>
#include <string.h> //strcpy()
#include<stdlib.h>//malloc()
void insertQuestion();
void insertNode(char ProcessName[5],int Priority,int Time);
void view();
void arithmetic();
typedef struct process
{
    char ProcessName[5];
    int Priority;
    int Time;
    int processState;
    struct process *next;
}nodelist;

nodelist *pHead=NULL;//存放调度的首节点地址

int main(){
    insertQuestion();
    arithmetic();
    // view();
    return 0;
}

//添加问题
void insertQuestion()
{
    void insertNode(char ProcessName[5],int Priority,int Time);
    char ProcessName[5];
    int Priority;
    int Time;
    int i=0;
    int n;
    scanf("%d",&n);
    for (i = 0; i < n; i++)
    {
      scanf("%s %d %d",ProcessName,&Priority,&Time);
      insertNode(ProcessName,Priority,Time);
    }
}

//将数据插入链表
void insertNode(char ProcessName[5],int Priority,int Time)
{
    //申请存储空间
    nodelist *pNew=(nodelist *)malloc(sizeof(nodelist));
    nodelist *p,*q;
    strcpy(pNew->ProcessName,ProcessName);
    pNew->Priority = Priority;
    pNew->Time = Time;
    pNew->processState=1;
    pNew->next=NULL;
        if(pHead==NULL) //插入前链表为空,新插入的节点为头节点
        {
            pHead=pNew;
        }
        else
        {
            p=pHead;
            if(p->next!=0)
            {
                while(p->next!=0)
                {
                q=p->next;
                p=q;
                }
                p->next=pNew;
                pNew->next=NULL;
            }
            else if (p->next==0)
            {
                p->next=pNew;
                p=pNew;
                pNew->next=NULL;
            }

        }
}

//显示链表中的数据
void view()
{
    /* 显示所有的结果 */
    nodelist *p=pHead;
    if(pHead!=NULL)
    {
    	    printf("进程名\t优先数\t时间\t就绪状态\n");
            while(p!=NULL)
            {
                printf("%s\t",p->ProcessName);
                printf("%d\t",p->Priority);
                printf("%d\t",p->Time);
                 printf("%d\t",p->processState);
                p=p->next;
                printf("\n");
            }
    }
    else
    {
      printf("链表中啥都没有!\n");
    }
}


void arithmetic()
{
    //相关算法实现
    nodelist *p=pHead;
    nodelist *q=pHead;
    nodelist *m=pHead;
    int max=p->Priority;
    int flag=0;
    int i;
    int sum=0;
    char firstName[5];
    char ReturnProcessName[5];
    strcpy(ReturnProcessName,p->ProcessName);

    if(pHead!=NULL)
    {

    while(m!=0){
        sum+=m->Time;
        m=m->next;
    }


        for (i = 0; i < sum; ++i)
        {
            while(p!=NULL)
            {
                if( max < (p->Priority) && (p->Time>0)){
                    max= p->Priority;
                    strcpy(ReturnProcessName,p->ProcessName);
                    flag=1;
                }


                p=p->next;

            }
                      while(q!=NULL)
                        {
                            if(strcmp(q->ProcessName,ReturnProcessName) == 0){
                            	printf("\n");
                            	printf("被选中进程的进程名:");
                                printf("%s\n",q->ProcessName);
                                q->Priority--;
                                q->Time--;
                            }
	                              if (q->Time==0)
					                {
					                	q->processState=0;
					                }
                            q=q->next;
                        }
                    p=pHead;
                    q=pHead;
                    max=p->Priority;
                    printf("\n");
                    printf("运行一次后进程队列的变化:\n");
                    printf("----------------------------------\n");
                    view();
                    printf("\n");

                    strcpy(ReturnProcessName,p->ProcessName);
        }
    }
}

运行结果:

mark

mark

mark

# 实验二 主存储器空间的分配和回收

# 一、实习内容

​ 主存储器空间的分配和回收。

# 二、实习目的

​ 一个好的计算机系统不仅要有一个足够容量的、存取速度高的、稳定可靠的主存储器,而且要能合理地分配和使用这些存储空间。当用户提出申请存储器空间时,存储管理必须根据申请者的要求,按一定的策略分析主存空间的使用情况,找出足够的空闲区域分配给申请者。当作业撤离或主动归还主存资源时,则存储管理要收回作业占用的主存空间或归还部分主存空间。主存的分配和回收的实现虽与主存储器的管理方式有关的,通过本实习帮助学生理解在不同的存储管理方式下应怎样实现主存空间的分配和回收。

# 三、实习题目

​ 模拟在分页式管理方式下采用位示图来表示主存分配情况,实现主存空间的分配和回收。

# 提示:

  1. 分页式存储器把主存分成大小相等的若干块,作业的信息也按块的大小分页,作业装入主存时可把作业的信息按页分散存放在主存的空闲块中,为了说明主存中哪些块已经被占用,哪些块是尚未分配的空闲块,可用一张位示图来指出。位示图可由若干存储单元来构成,其中每一位与一个物理块对应,用 0/1 表示对应块为空闲 / 已占用。

  2. 假设某系统的主存被分成大小相等的 64 块,则位示图可用 8 个字节来构成,另用一单元记录当前空闲块数。如果已有第 0,1,4,5,6,9,11,13,24,31,共 10 个主存块被占用了,那么位示图情况如下:

    mark

  3. 当要装入一个作业时,根据作业对主存的需要量,先查当前空闲块数是否能满足作业要求,若不能满足则输出分配不成功。若能满足,则查位示图,找出为 “0” 的一些位,置上占用标志 “1”,从 “当前空闲块数” 中减去本次占用块数。

mark

其中,j 表示找到的是第 n 个字节,I 表示对应的是第 n 位。

根据分配给作业的块号,为作业建立一张页表,页表格式:

mark

  1. 当一个作业执行结束,归还主存时,根据该作业的页表可以知道应归还的块号,由块号可计算出在位示图中的对应位置,把对应位的占用标志清成 “0”,表示对应的块已成为空闲块。归还的块数加入到当前空闲块数中。由块号计算在位示图中的位置的公式如下:

    mark

  2. 设计实现主存分配和回收的程序。假定位示图的初始状态如(2)所述,现有一信息量为 5 页的作业要装入,运行你所设计的分配程序,为作业分配主存且建立页表(格式如(3)所述)。然后假定有另一作业执行结束,它占用的块号为第 4,5,6 和 31 块,运行你所设计的回收程序,收回作业归还的主存块。

要求能显示和打印分配或回收前后的位示图和当前空闲块数,对完成一次分配后还要显示或打印为作业建立的页表。

# 四、实习报告

  1. 实习题目。
  2. 程序中使用的数据结构及符号说明。
  3. 流程图。
  4. 打印一份源程序并附上注释。
  5. 打印程序运行时的初值和运行结果,要求如下:

# 输出要求:

​ 打印位示图和当前空闲块数的初值;要求装入的作业对主存的申请量,为作业分配后的位示图、当前空闲块数和页表;作业归还的块号、回收作业所占主存后的位示图和当前空闲块数。

源代码:

#include "stdio.h"
int main()
{
  int arr[64]={0};
  int n=0;
  int sumsheng=54;
  int i=0,j=0;
  int count=0;
  int indexI=0;
  int flag=0;
  int arr1[64];
  int finallyIndex=0;
  int arrindex=0;
  int arrindex2=0;
  int indexflag=0;
  int x[64]={0};
  int ReturnNum=0;
  int term;
  arr[0]=1;
  arr[1]=1;
  arr[4]=1;
  arr[5]=1;
  arr[6]=1;
  arr[9]=1;
  arr[11]=1;
  arr[13]=1;
  arr[24]=1;
  arr[31]=1;


  printf("装入的作业对主存的申请量:");
  scanf("%d",&n);

  // printf("%d\n", sumsheng);
printf("\n");
printf("\n");
printf("-------------------------------------\n");
printf("初始位示图:\n");
for (i = 0; i < 64; i++)
{
  if (i%8==0)
  {
    printf("\n");
  }
  printf("%d ",arr[i]);
}
printf("\n");
printf("\n");
printf("当前空闲块数的初值:%d\n",sumsheng );
printf("-------------------------------------\n");
printf("\n");
printf("\n");
printf("\n");
sumsheng=sumsheng-n;

if (n>sumsheng)
    {
      printf("分配不成功\n");
    }else{
        for (i = 0; i < 64; i++)
        {
          if (arr[i]==0)
          {
            indexI=i;
            goto LOOP;
          }
        }

       LOOP:for(i=indexI;i<64;i++){

          if (arr[i]==0)
          {
            arr[i]=1;
            arr1[indexflag]=i;
            indexflag++;
            count++;
            if(count==n){
                 finallyIndex=i;
                 goto A;

            }
          }
        }
       A:printf("\n");

    }



printf("-------------------------------------\n");
printf("为作业分配后的位示图:\n");
for (i = 0; i < 64; i++)
{
  if (i%8==0)
  {
    printf("\n");
  }
  printf("%d ",arr[i]);
}
printf("\n");
printf("\n");
printf("为作业分配后的空闲块数:%d\n",sumsheng);
printf("\n");
printf("页表:\n");
printf("页号\t块号\n");
for (i = 0; i < n; i++)
{
  printf("%d\t%d\n",i,arr1[i]);
}
printf("-------------------------------------\n");


printf("\n");
printf("\n");
printf("\n");
printf("-------------------------------------\n");
printf("输入作业归还的块号的数量:");
scanf("%d",&ReturnNum);
printf("输入作业归还的块号:");
 for (i = 0; i <ReturnNum ; i++)
    {
        scanf("%d",&x[i]);
    }

    for (i = 0; i < ReturnNum; i++)
    {
       term=x[i];
       arr[term]=0;
    }
printf("回收作业所占主存后的位示图:\n");
for (i = 0; i < 64; i++)
{
  if (i%8==0)
  {
    printf("\n");
  }
  printf("%d ",arr[i]);
}
printf("\n");
printf("\n");
printf("当前空闲块数:%d\n",sumsheng+ReturnNum);
printf("-------------------------------------\n");
    return 0;
}

运行结果:

mark

mark

mark

mark

大二数据库实验

# 大二数据库实验

# 介绍

数据库课程实验综合小实验(做一个项目实现增删改查功能)

大二下学期期末大作业!

# 技术运用:

运用到的技术bootstrap+ art-template + node.js + mysql

课题 数据库
班级 1701
作者姓名 周琛
学号 2017115010124
所在院系 计算机信息与工程学院
学科专业名称 计算机科学与技术
导师及职称 童强

# 一、实验目的与要求

  1. 用 node 设计一个应用程序,实现对平时实验数据库的增、删、改、查。
  2. 说明文档中有 node 连接数据库的关键代码说明。
  3. 给了一个实例代码,实现了对一张表的增删改查功能(说明文档中可以看到数据库及表的名称,和有关连接数据库和实现增删改查的关键代码)。
  4. 可以运行,实现对学生课程数据库的操作;

# 二。步骤操作

# 1. 用 node 设计一个应用程序,实现对平时实验数据库的增、删、改、查。

效果图:

用图说话:

mark

# mark 视频:

# 2. 说明文档中有 node 连接数据库的关键代码

文件 : mysql.js

作用:连接数据库,执行数据操作、 封装、暴露方法.

mysql.js:

const mysql = require("mysql");

//执行数据操作、 封装、暴露方法
module.exports = {
  query: function (sql, params, callback) {
    //1.创建链接
    const connection = mysql.createConnection({
      host: "localhost",
      user: "root",
      password: "123456",
      database: "zc",
    });
    //每次使用的时候需要创建链接,数据操作完成之后要关闭连接
    connection.connect(function (err) {
      if (err) {
        console.log("数据库链接失败");
        throw err;
      }
      //开始数据操作
      //传入三个参数,第一个参数sql语句,第二个参数sql语句中需要的数据,第三个参数回调函数
      connection.query(sql, params, function (err, results, fields) {
        if (err) {
          console.log("数据操作失败");
          throw err;
        }
        //将查询出来的数据返回给回调函数
        callback && callback(results, fields);
        //results作为数据操作后的结果,fields作为数据库连接的一些字段
        //停止链接数据库,必须再查询语句后,要不然一调用这个方法,就直接停止链接,数据操作就会失败
        connection.end(function (err) {
          if (err) {
            console.log("关闭数据库连接失败!");
            throw err;
          }
        });
      });
    });
  },
};

# 3. 实现了对一张表的增删改查功能

给了一个实例代码,实现了对一张表的增删改查功能(说明文档中可以看到数据库及表的名称,和有关连接数据库和实现增删改查的关键代码)。

# 添加功能实现:

router.post("/add", function (req, res, next) {
  let body = req.body;
  console.log(body);
  let addSql = "INSERT INTO user(id,username,tel) VALUES(?,?,?)";
  let addSqlParams = [body.id, body.username, body.tel];
  db.query(addSql, addSqlParams, function (result, fields) {
    console.log("添加成功");
    return res.status(200).json({
      err_code: 0,
      message: "OK",
    });
  });
});

效果图:

mark

# 删除功能实现 :

router.get("/delete", function (req, res, next) {
  let deleteSql = "DELETE FROM user  WHERE id = ?";
  let deleteSqlParams = req.query.id;
  db.query(deleteSql, deleteSqlParams, (err, results) => {
    if (err) {
      console.log(err);
    }
    res.redirect(302, "/");
  });
});

效果图:

mark

# 修改功能实现

router.get("/fix", function (req, res, next) {
  fixsql = "SELECT * FROM user WHERE id = ?";
  fixSqlParams = req.query.id;
  db.query(fixsql, fixSqlParams, (result) => {
    res.render("fix.html", {
      result: result[0],
    });
  });
});

router.post("/fix", function (req, res, next) {
  console.log(req.body);
  updatasql = "UPDATE user SET username = ?,tel=? WHERE id = ?";
  updataSqlParams = [req.body.username, req.body.tel, req.body.id];
  db.query(updatasql, updataSqlParams, (err, results) => {
    if (err) {
      console.log(err);
    }
    return res.status(200).json({
      err_code: 0,
      message: "OK",
    });
  });
});

效果图:

mark

# 查找功能实现

router.post("/search", function (req, res, next) {
  console.log(req.body);
  arr = [];
  db.query("select * from user", [], function (result, fields) {
    console.log(result);
    result.forEach(function (e) {
      if (
        e.id.indexOf(req.body.value) >= 0 ||
        e.username.indexOf(req.body.value) >= 0 ||
        e.tel.indexOf(req.body.value) >= 0
      ) {
        arr.push(e);
      }
    });
    return res.status(200).json({
      err_code: 0,
      message: arr,
    });
  });
});

效果图:

mark

# 4. 运行程序

相关代码源代码以上传至码云,地址为:

源码地址

  • Copyrights © 2015-2021 zhou chen
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信