Front-end(1)


Front-end Design(1)

写在前面

本章节的产生原因在于博主需要参加的大创(全称: 大学生创新创业竞赛)项目需要相关的前端界面设计, 而博主恰好负责这一部分较多, 因此在此新开一坑, 衔接此前HTML&CSS的章节, 并在此之上继续向前, 目的在于在博文的最后能够设计出一个较为完整的前端界面出来.

1. JavaScript 概述

JS, 全称 JavaScript, 是一个使用十分广泛, 应用较广的 脚本语言 , 被广泛运用在前端的设计上, 通过JS的种种 行为 , 能够实现前端界面的动画, 以及一些较为合理的交互逻辑.

1.1 ECMAScript & JavaScript

通俗而言, ECMAScript(后文简称ES)是由国际组织(ECMA)指定的一个脚本语言规范, 而JavaScript(后文简称JS)是由Netscape公司开发的, 基于 ES 的脚本语言.

ES的广为流传的版本为ES6, 它于2015年发布.

1.2 JS 语句 / 标识符

JS作为单独的脚本语言, 它有其自己的一套语法, 一套单独的规范.

JS的标识符可以容纳的字符包括 字母, 数字, 下划线, $ 这四种, 数字不能用在标识符开头.

除此之外, 其实中文也可以作为标识符, 但本文中不推荐这样做. 中文进入代码行中出现的各种编码问题, 适配问题已经能搞得博主焦头烂额了.

除此之外, JS也有一套关键字, 类似于C中的 break, return, … , 它们有自己的作用, 因此不能作为单独的标识符使用. 这些关键字我们会在后文中的使用中慢慢引出, 读者在此大可不必过于在意.

1.3 变量

在JS中, 变量可以非常简单的利用统一的符号进行定义:

<script>
    var num = 10;
</script>

关于这个前后的<script>, 有HTML基础的读者应该看出这是个标签了, 确实, 由于JS的重要应用就是前端的设计, 因此常常需要插在HTML中进行书写, 具体方式即在相应的html文件中插入对应的标签即可. 后文会将这前后的标签省略, 还请读者记住这个事情.

1.3.1 变量的监视

但不同于C, Java的编译器提供的良好环境, 我们如何监视JS中的各种变量呢? 一个比较直接的方式是通过控制台显示出来.

var num = 10;
console.log(num);

通过这样的语句, 我们可以通过浏览器中 检查->控制台 的界面查看变量的当前值.

Console.log

1.3.2 变量提升

在JS中, 为了增加兼容性, 会将脚本中所有声明的变量提前至代码运行前优先声明.

这句话不是很好理解, 我们举个例子:

console.log(num);
var num = 10;

如果以C语言输出的角度来看, 上面这段代码应当报错, 但实际上, 在浏览器中显示的状态:

变量提升

相当于, 浏览器在运行这段脚本时, 其实相当于这样:

var num;
console.log(num);
num = 10;

这即所谓变量提升的意义.


在当今的开发过程中, var的使用已经比较少, 这是因为var变量提升这个特性往往会引起一些额外的错误, 因此ES6中引入了新的变量声明方式, 即let和const.

let, const与var的区别在于, let和const声明的变量不会进行变量提升, 这代表着开发者可以 更加稳定的控制变量的作用域 .

console.log(num);
let num = 10;

上述代码将会报错, 因为let声明的变量不会进行变量提升.


1.4 JS的引入

与CSS类似, JS除开在HTML中单独书写外, 还可以通过外部文件引入的方式进行.

<script type = "text/javascript" src = "./my_first_js.js"></script>

<script src = "http://..."></script>

上述内容分别代表了从本地引入js, 以及从网络外部连接引入js的方法.

1.5 JS的注释

JS中的注释可以通过两种方式进行:

// 这是一条注释

/*
这是一条多行注释
*/

明确, 这种注释是只能在JS中进行的. 相对应, 读者别忘记HTML与CSS中的注释形式.

当然, 比较方便的快捷键是 Ctrl + /

1.6 JS的输出

这里简要提供三种方式

// Method 1
alert("This is Method 1.");

// Method 2
Document.write("This is Method 2.");

// Method 3
console.log("This is Method 3");

第一种方式以弹出框的形式来输出内容, 用户可见:

Method 1

第二种方式在页面上单独显示一行字体, 用户同样可见:

第三种方式我们之前说过了, 在控制台中进行相关输出, 用户不可见:

写入页面 / 控制台

1.7 JS的数据类型

1.7.1 原始数据类型

原始数据类型, 也被称为基本数据类型, 一般有三个:

  • 数值
  • 字符串
  • 布尔值
var number = 10;
var str = "string";
var bool = false;

1.7.2 合成数据类型

合成数据类型, 也被称为复合数据类型, 一般由多个原始数据类型组成(C中的结构体):

var user = {
    name: "Mug-chen",
    age: 1,
    admin: true
}

注意, 变量之间通过 , 隔开

1.7.3 特殊类型

null
undefined

1.7.4 类型判断

在数据类型的判断上, 常用typeof函数

var num = 123;
console.log(typeof num); //number

var str = "string";
console.log(typeof str); //string

var boo = true;
console.log(typeof boo); //boolean

var user = {};
console.log(typeof user); //object

//两个特殊类型的结果
console.log(typeof undefined); //undefined
console.log(typeof null); //object

null通常表示对象为空; undefined一般表示数值为空.

1.8 运算符

整体的运算符与C语言基本相同, 这里仅提及不同处.

1.8.1 除法

当然, 这里的除法是可以直接算出小数的, 不会像C, java那样整数除以整数只能得到整数解.

1.8.2 严格等于 & 严格不等于

在JS中, 等于运算符 “==” 出现了一个衍生, 叫 严格等于 “===”, 二者的区别在于, 前者会先进行类型转换再比较, 后者不会:

console.log(5 == "5") \\true
console.log(5 === "5") \\false

类似的, 也有 !=!== 的使用, 我们推荐使用 严格等于 / 严格不等于 .

1.8.3 布尔运算

这里主要提及取反 ! , 因为实际应用过程中常常对非布尔值进行取反(主要是方便), 我们这里给出6个取反后为真的例子.

!false;
!0;
!null;
!undefined;
!""; //空字符串取反
!NaN;

1.9 语句

1.9.1 条件语句

给三种

// if_else
if(){

}else{

}

//elseif
if(){

}else if(){

}else if(){

}else{

}

//switch
switch(var_name){
    case 1:
        ...
        break;
    case 2:
        ...
        break;
    default:
        ...
        break;
}

可见, 与C / Java极其类似.

1.9.2 三元运算符

即C / Java中都有的特殊的条件判断:

1>2 ? console.log(true): console.log(false);

1.9.3 循环语句

for(var i = 0;i<100;i++){
    console.log(i);
}

while(){
    ...
}

break;
continue;

1.10 字符串

字符串与Java中类似, 换行需要利用 \

1.10.1 字符串常用方法

(1) charAt

//指定位置字符
var str = "Hello World."
console.log(str.charAt(3));

(2) concat

//字符串连接
var str1 = "Hello";
var str2 = "World";
var str = str1.concat(str2);

//可以有多个参数, 参数之间利用逗号隔开
var str3 = "!";
var s = str1.concat(str2, str3);

当然, 熟悉java的读者, 应该知道, 直接用+也可以连接字符串, 也没毛病.

那用concat有什么区别?

事实是, +使用类型强转, 而concat调用它们的 toString() 方法.

一般情况下区别不大就是了.

var num = 1
var grade = "grade: "

var str = grade.concat(num);

(3) substring

//子字符串
var str = "Hello World";
var sub = str.substring(2, 6); //llo 

//第二个参数可不写
var sub_1 = str.substring(2); //llo World

//会自动排序两个参数的大小
var sub_2 = str.substring(9, 2);
//相当于
var sub_2 = str.substring(2, 9);

//负数会被自动转化成0
var sub_3 = str.substring(9, -2);
//相当于
var sub_3 = str.substring(0, 9);

与Java中相同, 含前不含后.

(4) substr

//子字符串, 但第二个参数是字符串长度
var str = "Hello World";
var sub = str.substr(2, 4); //llo 

//第二个参数不写, 则一直取到最后

//第一个参数写成负数, 则取逆序的字母值
//第二个参数写成负数, 则自动置为0(空字符串)
var sub = (-2, 1) //l

(5) indexOf

//查看一个字符串在另一个字符串中第一次出现的位置, 如果找不到返回0
var str = "Hello World";
var sub = "Wor";

console.log(str.indexOf(sub)); //6

//可以接受第二个参数, 表示开始查找的位置

console.log(str.indexOf(sub, 7)); //-1

(6) trim

//用于去除字符串两端的特殊字符, 包括空格, 制表符\t, \r, 回车符\n等
var str = "      Hello World      ";
var res = str.trim();

console.log(res);

//ES6新增扩展方法:
trimStart() //去掉头部特殊字符
trimEnd() //去掉尾部特殊字符

(7) split

//字符串分割函数, 返回一个由子字符串组成的数组
var str = "Hello World Here Is My Blog";
console.log(str.split(" ")); //["Hello", "World", "Here", "Is", "My", "Blog"]

//可以进行空字符串分割, 返回一个由字符串中每个字符单独组成的字符串组成的数组
console.log(str.split(""));

//省略参数, 则返回原字符串组成的数组
console.log(str.split());

//可以接受第二个参数, 限定返回数组的最大成员数
console.log(str.split("", 3)); //["H", "e", "l"]

上面是主要使用的字符串方法, 但其实JS中字符串方法远不止这么多, 希望读者使用时可以有限查一查有没有对应的函数. 毕竟直接调库比手搓一个函数可方便多了(笑)

1.11 数组

var arr = ['Hello', 'World', '!'];

在JS中, 数组的声明一般直接使用var进行. 不同的是, 由于没有了类型的区分, 因此一个数组中可以存在各种类型的数据(整数 / 浮点数 / 字符串 / 数组).

1.11.1 数组操作

(1) 求长

var arr = ['', '', '', ''];
console.log(arr.length);

(2) 遍历

//传统的遍历方法(即利用for / while进行循环)在此不在叙述
var arr = ['1', '2', 3, false]
for(var i in arr){
    console.log(arr[i]);
}

(3) 判断类型

var arr = [];
console.log(Array.isArray(arr)); //返回一个布尔值

(4) 快捷添加 / 删除元素

var arr = [];
arr.push(1, 'World', false); //在数组后方添加一条或多条数据

var temp = arr.pop(); //删除数组的最后一个元素, 并返回它

var temp = arr.shift(); //删除数组的第一个元素, 并返回它

关于 shift 这个方法, 有一种很常见的写法, 即利用它来清空数组(顺便正序遍历也可以):

//var arr = [...];
var temp;
while(temp = arr.shift()){
    console.log(temp);
}//当数组为空时, 相当于将一个"undefined"赋值给temp, 会返回false, 循环自然结束

相对应的, 也可以快捷的向头部添加元素:

//var arr = [...];
arr.unshift(1, false, "World"); //向头部添加一个或多个元素

(5) 将数组输出

方法 join 可以指定分隔符来将整个数组转变为字符串输出.

var arr = [10, 20, 30, 40];
console.log(arr.join()); //默认用逗号
console.log(arr.join(" ")); //用空格

联系前面提供的字符串split方法, 可以将数组和字符串实现轻松的互相转换.

(6) 数组合并

var arr1 = [1, 2, 3];
var arr2 = ['world', 'hello'];
//var arr3 = ...

var temp = arr1.concat(arr2, arr3, ...);
//可以将一个或多个数组的内容合并到一起并返回, 不会改变原数组

(7) 逆序

var arr1 = [1, 2, 3];

arr.reverse(); //逆序排列原数组, 会改变原数组

这种方法可以用于翻转字符串, 具体而言, 先将字符串转为数组, 再用reverse反转数组, 最后转为字符串即可.

(8) 查找

与字符串相同, 查找某一个元素在数组中第一次出现的位置, 不存在则返回-1;

不再详述.

1.12 函数

在JS中, 函数的声明如下:

function function_name(x, y, z){
    ...
    return ...;
}
//利用function进行声明, 可以传入参数, 并传出返回值

//亦或通过函数表达式的方法进行声明

const function_name = function(x, y, z){
    ...
    return ...;
}

上两种方法都好理解(对于此前有编程基础的读者而言), JS中额外引入了箭头函数:

const function_name = (x, y, z) => {
    ...
    return ...;
}

//箭头函数的语法, 可以省略function关键字, 并将函数体写在箭头后侧

//此外, 如果箭头函数只有一个参数, 可以省略参数列表的括号
const function_name = x => {
    ...
    return ...;
}

//如果箭头函数的函数体只有一行, 可以省略花括号, 并将return关键字一并省略
const function_name = x => ...;

//最后, 如果箭头函数的返回值只有一个, 可以省略return关键字, 并将返回值直接写在箭头后侧
const function_name = x => x+1;

要明确, 箭头函数简略写返回值时, 无法返回用{}包裹的对象, 因为JS会将{}解析为函数体, 而不是对象.

1.13 对象

对象是一个非常重要的概念, 同时这里与Java中的一些语法差距较大, 我们重点叙述一下:

类似Java, 对象中包含属性和方法.

var user = {
    name: "Mug-chen",
    job: "",
    age: "?",
    ...
    getName:function(){
        return this.name;
    } 
}

与Java中的对象极其类似, 只不过声明方法时需要通过 方法名:function 进行声明, 同时属性之间通过 , 隔开即可.

此外, 还可以在对象内套对象:

var Reader{
    Re1{
        name: "Bob",
        age: "18",
        ...
    },
    Re2{
        ...
    }
}

可以通过多层引用进行信息调用.如: Reader.Re1.name ;

此外, 如果希望检查对象中是否包含某个属性, 可以使用 in 运算符, 可以返回一个布尔值:

if("name" in user){
    ...
}

事实上, 在JS中, 属性名可以非常的复杂, 但此时使用属性名时不能使用一般的 . 进行引用, 而需要使用 [] 进行引用, 如:

var user = {
    name: "Mug-chen",
    job: "",
    age: "?",
    12345@#$%: "???"

    getName:function(){
        return this.name;
    } 
}

user["12345@#$%"]; //调用时需要使用[]进行引用

使用 [] 来进行属性的添加和修改与使用.有什么不同?

除了上文中提到的, [] 可以用于非常复杂的属性名外, 其还可以提取变量名来进行属性的定义:

var obj = new Object();

var str = "name";
obj[str] = "Mug-chen";

console.log(obj.name); //Mug-chen, 这里的属性名为变量str的值, 即name

//假设更改为obj.str = "Mug-chen", 则相当于添加了一个属性名为 "str" 的属性, 属性值为"Mug-chen"

此时, obj[str] 等价于 obj[“name”], 也就是 obj.name.


1.13.1 通过Symbol添加属性

Symbol是ES6中引入的一种新的数据类型, 可以用于创建独一无二的属性名.

在ES6中, 可以通过Symbol来添加属性, 具体如下:

var user = new Object();

var mysymbol = Symbol();

user[mysymbol] = "Mug-chen";

console.log(user[mysymbol]); //Mug-chen

通常来说, 使用Symbol添加属性的目的在于不让外界访问到该属性, 因为Symbol是独一无二的, 外界无法通过常规方式访问到该属性.

1.14 类

在ES6中, 对于 这个概念有了引入.

class User{
    constructor(name){
        this.name = name;
    }

    getName(){
        return this.name;
    }

    addAge(age){
        this.age = age;
    }

    getAge(){
        return this.age;
    }
}

var User1 = new User("Alice");
console.log(User1.getName());
User1.addAge(13);
console.log(User1.getAge());

与Java显著的不同在于, JS中不需要再类前明确声明类的所有属性, 而是通过构造器(constructor)或其他方法直接添加即可.

1.15 继承 / 多态 / 原型

JS中, 同样有类继承的操作, 同样使用 extends 关键字:

class Student extends User{
    constructor(name, age){
        super(name);
        this.addAge(age);
    }
}

在JS中, 类的继承是单继承的, 即一个类只能继承自一个父类.


类似的, 多态我们此前也提过, 意为在子类中可以重写父类的方法(包括构造方法).

class Student extends User{
    constructor(name, age){
        super(name);
        this.addAge(age);
    }

    getName(){
        return "Student: " + super.getName();
    }
}

请明确, 当重写子类的构造函数时, 第一行必须是super(), 即调用父类的构造函数, 这点的意义在于子类想要构造一个实例, 必须首先构造一个父类实例.


原型

在JS中, 直接通过console的方式打印一个对象时, 只会打印对象的一部分属性, 这被称为对象自身的属性, 包含:

  • 直接通过对象添加的属性
  • 在类中通过x = y的方式添加的属性(包括通过函数表达式书写的函数)

但还有另一类对象不会显式地出现在console中, 这就是 原型对象 . 原型对象存储的位置(内存)与对象自己并不一定相邻, 源对象可以通过 __proto__ 的方式来引用原型对象中的内容, 或者通过方法: Object.getPrototypeOf(obj) 来获取原型对象.

  • 当访问对象中的内容时, 会先从对象自身查找, 如果找不到, 则会去原型对象中查找
  • 原型对象中存储的属性:
    • 类中通过fun_name(){}添加的方法
    • 通过this.x = y的方式添加的属性
    • 对象的构造函数constructor

原型链

显然, 原型对象也是对象, 也就有原型对象的原型对象…(套娃.jpg), 这一条捋下去, 就能形成一个原型链(最终一定会捋到 null ), 也就是JS中对象继承的原理(我们称之为 原型继承 ).

因此, 我们具象化对象调用属性的过程:

  1. 先从对象自身查找
  2. 如果找不到, 则去原型对象中查找
  3. 如果找不到, 则去原型对象的原型对象中查找
  4. 直到找到Object.prototype(因为Object.prototype的原型对象是null), 如果仍然找不到, 则返回undefined

为啥要有原型这个玩意?

在JS中, 同类型对象的原型对象是相同的, 因此, 当我们通过原型对象来添加方法时, 所有的对象都可以调用这个方法, 而不必为每个对象都添加一次, 这就是原型链的意义.

2. JS与网页的交互: DOM

DOM是Document Object Model的缩写, 意为文档对象模型, 是一种用于表示和操作HTML或XML文档的编程接口.

事实上, DOM属于Web API的一部分, 它提供了许多方法, 可以通过这些方法来访问和操作HTML文档中的元素.

DOM的原理在于将整个HTML文件视作一整个对象树, 而HTML中的每个标签都是树的一个节点, JS通过DOM对于HTML中的节点进行访问 / 操作, 达到控制HTML行为的效果.

2.1 DOM的入口节点(文档节点): document

document是DOM的入口节点, 代表整个HTML文档, 可以通过document来访问HTML中的所有元素.

通过此前提及的 原型 相关内容, 我们可以逐级回溯到document的原型链:

Document原型链

$$ HTMLDocument -> Document -> Node -> EventTarget -> Object.prototype $$

为什么要梳理这条原型链?

有这条原型链, 代表着这一条链上的所有属性 / 方法, 对于document对象都是可用的. 这也代表着读者此后遇到不理解的内容, 有迹可循.

部分属性举例:

  • document.documentElement: 返回文档的根元素, 即标签
  • document.body: 返回文档的元素
  • document.title: 返回文档的标题
  • document.URL: 返回文档的URL

2.2 DOM的元素节点: element

element是DOM的元素节点, 代表HTML中的每一个标签, 可以通过element来访问和操作HTML中的标签.

2.2.1 元素节点的获取

  • 通过document对象来获取元素节点
    • document.getElementById(“id”): 通过id来获取 单个 元素节点
    • document.getElementsByClassName(“class”): 通过类名来获取 一组 元素节点;
      • 返回一个 类数组对象 , 这代表这个返回的对象 不能用数组的某些方法 , 比如forEach等…
      • 返回的这个类数组对象是 实时更新的 , 代表着查询一次后, 数组中会自动将后续添加的符合要求的元素加进去
    • document.getElementsByTagName(“tag”): 通过标签名来获取 一组 元素节点
      • 类似, 返回类数组 / 实时更新
      • 可以通过 document.getElementByTagName(“*”) 来获取文档中的所有节点
    • document.querySelectorAll(“.class”): 通过CSS选择器来获取 一组 元素节点
      • 引号内为一个 Css选择器
      • 返回一个类数组
      • 不会实时更新
    • document.querySelector(“.class”): 通过CSS选择器来获取 单个 元素节点
      • 如果文档中有多个符合要求的元素, 则之后返回第一个符合条件的元素
  • 通过document对象来创建元素节点
    • document.createElement(“tag”): 通过标签名来创建一个元素节点

2.2.2 元素节点的操作

我们相似的捋一下元素节点的原型链, 以div为例:

$$ HTMLDivElement -> HTMLElement -> Element -> Node -> … $$


1.通过元素节点查找 范围内的符合要求的节点

这一点与2.2.1几乎一致, 只需要将document更改为对应的对象即可, 不再赘述.


2.获取其他节点

  • element.childNodes : 获取element的所有子节点(不常用)
    • 返回一个类数组对象
    • 类数组对象中包含所有子节点, 包括文本节点和注释节点(换行等空白节点也会包括进去)
    • 还有一些fistchild, lastchild等属性, 由于不实用, 这里不再详述
  • element.children : 获取element的所有子元素节点
    • 返回一个类数组对象
    • 类数组对象中只包含子元素节点
    • firstElementChild: 获取第一个子元素
    • lastElementChild: 获取最后一个子元素
    • nextElementSibling: 获取下一个兄弟元素
    • previousElementSibling: 获取上一个兄弟元素
    • parentElement: 获取父元素
  • element.tagName : 获取当前元素的标签名

3.修改元素内文本

  • element.innerHTML : 获取或设置元素内的HTML内容
    • 可以读取到元素内的所有内容(包括标签)
    • 利用innerHTML插入内容时, 有被xss注入的风险
  • element.textContent : 获取或设置元素内的文本内容
    • 只能读取到元素内的文本内容(不包括标签), 代表着不会考虑css样式, 显示标签中的原始结果
    • 插入的内容会被转义
  • element.innerText : 获取或设置元素内的文本内容
    • 会忽略掉元素内的空白节点(包括换行等空白节点), 会考虑css样式(比如一个标签中有display: none的css设置, 则这个标签的内容不会被innerText读取到)
    • 插入的内容会被转义

文章作者: MUG-chen
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 MUG-chen !
  目录
加载中...