深入理解JS | 青训营笔记

87 阅读4分钟

什么是JavaScript?

  • 高级解释语言

    • 高级:很多抽象。
    • 解释:不是直接执行,不需通过编译器运行,是被解释的脚本语言
  • 遵从ECMAScript规范

  • 多范式(多种编写代码方式)

    • 面向对象编程
    • 函数式编程
  • 运行在浏览器和客户端的语言,也可以运行服务器端(Node.js)

为何要学JavaScript?

  • 运行在浏览器的编程语言
  • 可以使用 JS 框架构建交互式页面
  • 用于构建非常快速的服务器端全栈应用程序
  • 用于移动开发(React Native、NativeScript、lonic)
  • 用于桌面应用开发(Electron JS)

变量

var - 全局变量

全局作用域。

let - 局部变量

可以重新赋值。

const - 常量

不能重新赋值

数据类型

String - 字符串

const name = 'Kevin';

字符串拼接

// 可以用 ‘+’ 拼接
console.log('My name is ' + name + ' and I am ' + age);

模板字符串(ES6)

// 可以用反引号 ``、$符号和大括号写入变量名
console.log(`My name is ${name} and I am ${age}`);

字符串属性和方法

  • 属性(没有括号)

    • 长度:s.length
  • 方法(有括号)

    • 变大/小写:s.toUpperCase() / s.toLowerCase()
    • 子字符串:s.substring(0, 5)
    • 分割到数组:s.split(',')
    • 获取指定值的索引:s.indexOf('.')
    • 提取某个字符串的一部分: s.slice(BeginIndex[, endIndex])

nubmer - 数值

const age = 30;
const rating = 4.5;

boolean - 布尔

const isCool = true;

null - 空

const x = null;

undefined - 未定义

const y = undefined;
let z;

typeof:可以判断变量的数值类型

数组

包含多个变量值。

JS中同一个数组可以保存不同类型值

创建数组

直接声明创建

// 数组结构体创建方式
const numbers = new Array(1,2,3,4,5);
​
// 变量创建方式
const fruits = ['apples', 'oranges', 'pears', 10, true];

数组方法创建

  • Array.fill(value[, start[, end]]): 用一个固定值填充一个数组中从起始索引到终止索引内的全部元素
  • Array.from(arrayLike[, mapFn]) : 对一个类似数组或可迭代对象创建一个新的、浅拷贝的数组实例。如Array.from([1, 2, 3], x => x + x));

数组元素操作 ☆

访问: 通过下标访问。

添加元素:

  • 可以直接通过下标在末尾添加,fruits[5] = 'grapes'

  • 可以通过push方法添加

    • 在末尾:fruits.push('mangos')
    • 在开头:fruits.unshift('strawberries')

删除元素: 可以通过pop方法在末尾删除,fruits.pop()

获取某个元素的索引: fruits.indexOf

将数组元素连接成一个字符串: Array.join('')

删除或替换现有元素或者原地添加新的元素Array.splice(start[, deleteCount[, item1[, ...]]])

const类型的数组可以在数组中添加元素,可以操作它,也可以使用方法,唯一不能做的是初始化为空fruits = []

数组操作

判断是否为数组Array.isArray(fruits)

获取数组的子串: Array.slice([begin[, end]])

slice() 返回一个新的数组对象,这一对象是一个由 beginend 决定的原数组的浅拷贝(包括 begin,不包括end)不改变原数组。

数组排序: Array.sort([compareFunction])

sort() 默认将数组元素转换为字符串比较,可以通过比较函数来定义排序,如 sort((a, b) => { return a-b; }) 数组将会比较数字并升序排列

对象字面量

对象就是键值对

创建对象中可以有变量、数组、对象。

const person = {
	firstName: 'Kevin',
	lastName: 'Zhang',
	hobbies: ['music', 'movies', 'sports'],
	address: {
		street: '50 main st',
		city: 'WenZhou',
		country: 'China'
	}
}

对象可以解构赋值,重新定义新的变量去取出对象中属性值。(ES6)

const { firstName, lastName, address: { city } } = person;

// console.log(firstName);
// console.log(city);

添加新的属性,可以直接添加。

person.email = 'zkc@example.com';

对象数组

对象数组也是很常用的形式。

const todos = [
	{
		id: 1,
		text: 'Take out trash',
		inCompleted: true
	},
	{
		id: 2,
		text: 'Meeting with boss',
		inCompleted: true
	},
	{
		id: 3,
		text: 'Dentist appt',
		inCompleted: false
	}
]

JSON

JSON是一种数据格式,与对象语法很相似。在全栈开发中广泛使用,像服务发送数据会用到JSON格式,也是接受JSON格式。

JSON与对象语法很相似,并且可以直接用 JSON.stringify(object name)直接转换。

const todoJSON = JSON.stringify(todos);

/* 
属性名和字符串都为双引号!

const todoJSON = [
	{
		"id": 1,
		"text": "Take out trash",
		"inCompleted": true
	},
	{
		"id": 2,
		"text": "Meeting with boss",
		inCompleted: true
	},
	{
		"id": 3,
		"text": "Dentist appt",
		"inCompleted": false
	}
]
*/

循环

For循环

for (let i = 0; i < 10; i++) {
	// code
}

While循环

// 在循环外设置变量
let i = 0;
while (i < 10) {
	// code
    i++;
}

数组循环

// for循环
for (let i = 0; i < todos.length; i++) {
	// todos[i].text ...
}

// for of 循环
for(let todo of todos) {
    // todo ...
}

高阶数组方法

用于迭代数组,参数是函数(可以用箭头函数)。

不同方法可以链接起来用,利用函数式编程的思想,可以更强大。

// forEach 循环 
todos.forEach(function(todo) {
	// todo ...
})

todos.forEach(todo => {
    // todo ...
})

// map 从数组中创建新数组
const todoText = todos.map(function(todo) {
	return todo.text;
})

// filter 根据条件创建新数组
const todoCompleted = todos.filter(function(todo) {
	return todo.isCompleted === true;
})

条件

if else语句

if (x == 10) {
	// code ...
} else if(x > 10){
    // code ...
} else {
    // code ...
}

三元操作符

const x = 10;

const color = x > 10 ? 'red' : 'blue';

switch语句

switch(color) {
	case 'red':
		// code...
		break;
	case 'blue':
		// code...
		break;
	default:
		// code...
		break;
}

函数

function addNums(num1, num2) {
	// num1 + num2
}

addNums(5, 4);  // 9
addNums();		// NaN

可以给函数默认参数设置值,在有传参的情况下也会重写默认值。

function addNums(num1 = 1, num2 = 2) {
	// num1 + num2
}

addNums();		// 3
addNums(5, 5)	// 10

箭头函数

const addNums = (num1 = 1, num2 = 2) => {
	return num1 + num2;
}

// 不写{} ,就不写return
const addNums = (num1 = 1, num2 = 2) => num1 + num2;

// 若一个参数可以不写()
const addNums = num1 => num1 + 5;

面向对象编程OOP

可以通过构造函数来构造对象。

  • 原型实现构造函数
// 构造函数
function Person (firstName, lastName, dob) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.dob = new Date(dob);
    // 给对象添加函数
    this.getBirthYear = function(){
        return this.dob.getFullYear();
    } 
    this.getFullName = function() {
        return `${this.firstName} ${this.lastName}`;
    }
}
// 可以在原型中添加方法和属性,适合不是每个对象都添加的方法和属性
// 在原型中给对象添加函数
Person.prototype.getBirthYear1 = function() {
	return this.dob.getFullYear();
}
Person.prototype.getFullName1 = function() {
    return `${this.firstName} ${this.lastName}`;
}

// 实例化对象
const person1 = new Person('Kevin', 'Zhang', '4-18-1998');
const person2 = new Person('Kevin2', 'Zhang2', '4-18-1999');
  • ES6的
class Person {
    constructor(firstName, lastName, dob) {
        this.firstName = firstName;
    	this.lastName = lastName;
    	this.dob = new Date(dob);
    }
    
    getBirthYear(){
        return this.dob.getFullYear();
    } 
    getFullName() {
        return `${this.firstName} ${this.lastName}`;
    }
    
}

ES6中的class可以看作一个语法糖,它的绝大部分的功能,ES5都可以做到,新的class写法让原型的写法更加的清晰、更像面向对象编程的语法。

原型和继承

原型链和原型对象是js的核心,js以原型链的形式,保证函数或对象中的方法、属性可以让向下传递,使用了构造函数来实现继承机制。按照面向对象的说法,这就是继承,而js通过原型链才得以实现函数或对象的继承。

  • 每一个构造函数都有一个原型属性prototype指向他的原型对象
  • 每一个原型对象中都有一个constructor属性指向他的构造函数
  • 每一个实例(除了null)都有一个属性叫__proto__指向原型对象
  • 写在构造函数内的属性和方法只有构造函数本身可以调用,原型对象与实例都不可以调用;
  • 写在原型对象的属性和方法称为实例属性和实例方法,既可以通过实例调用,也可以通过原型对象调用
  • 构造函数可以创建多个实例,每个实例都有自己的属性和方法,实例也可以继承原型对象中的属性和方法
  • 所有实例对象都会从它的原型上继承一个 constructor ,指向原型对象

相关Object 和 Function

Object.__proto__Object.prototypeObject.prototype.__proto__

  • Object是构造函数,所有的函数都是通过new Function创建了,因此Object相当于Function的实例,即Object.__proto__ --> Function.prototype
  • Object.prototypeObject构造函数的原型,处于原型链的顶端Object.prototype.__proto__已经没有可以指向的上层原型,因此其值为null
Object.__proto__ --> Function.prototype
Object.prototype.__proto__ --> null

Function.__proto__Function.prototypeFunction.prototype.__proto__

  • Function.prototypeFunction的原型,是所有函数实例的原型,例如上面讲的Object.__proto__
  • Function.prototype是一个普通对象,因此Function.prototype.__proto__ --> Object.prototype
  • Function.__proto__: __proto__指向创造它的构造函数的原型,Function.__proto__ --> Function.prototype

Function函数不通过任何东西创建,JS引擎启动时,添加到内存中

Funtion.__proto__ === Object.__proto__ //f() { [native code] }

总结如下:

  1. 所有函数(包括Function)的__proto__指向Function.prototype
  2. 自定义对象实例的__proto__指向构造函数的原型
  3. 函数的prototype__proto__指向Object.prototype
  4. Object.prototype.__proto__ --> null

原型链

实例对象在查找属性时,如果查找不到,就会沿着__proto__去与对象关联的原型上查找,如果还查找不到,就去找原型的原型,直至查到最顶层,这也就是原型链的概念。

所有的原型对象__proto__属性都是指向function Object原型对象function Object原型对象在上图中我们可以得知是不存在__proto__这个属性的,它指向了null我们就得知了原型链的尽头是null

DOM文档树结构

window对象:是浏览器的父对象,一般也可以不写。

单元素选择器

  • getElementById: 通过ID来获取元素

  • querySelector: 可以通过类、标签本身等等选中元素。是单元素选择器,如果有多个元素符合,只选中第一个

    • 选择id'#'
    • 选择类名'.'
document.getElementById('my-form');
document.querySelector('.container');
document.querySelector('#my-form');

多元素选择器

  • querySelectorAll:选择所有符合的元素。
  • getElementsByTagName:通过标签来获取元素。
  • getElementsByClassName:通过类名来获取元素。
document.querySelectorAll('.item');
document.getElementsByTagName('li');
document.getElementsByClassName('item');

query... 选择器得到的结果为NodeList,可以使用数组方法

getElements... 选择器得到的结果为HTMLCollection,不能使用数组方法,需要转换为数组后使用。

循环遍历

const items = document.querySelectorAll('.item');

items.forEach(item => console.log(item));

操控和修改DOM

HTML页面操控

  • remove():删除元素
  • textContent:文本内容
  • children[index]:通过索引在节点列表中获取节点
  • innerHTML:动态修改HTML内容
const ul = document.querySelector('items');
// ul.remove();
// ul.lastElementChild.remove();			// 删除最后一个子项
ul.firstElementChild.textContent = 'Hello';	// 设置第一个子项文本内容为'Hello'
ul.children[1].innerText = 'Kevin';
ul.lastElementChild.innerHTML = '<h1>Hello</h1>'

CSS样式操控

可以写在事件函数里,使其动态处理样式,在页面中实时操控。

const btn = document.querySelector('.btn');
// btn.style.background = 'red';

事件

addEventListener('event', e => {...}

  • event是发生的事件(click、input、mouseover、mouseout、submit)
  • e是事件发生时运行的函数。
const btn = document.querySelector('.btn');

// 点击事件
btn.addEventListener('click', e => {
  e.preventDefault();
  console.log(e.target.className);	// btn
  document.getElementById('my-form').style.background = '#ccc';
  document.querySelector('body').classList.add('bg-dark');
  ul.lastElementChild.innerHTML = '<h1>Changed</h1>';
});

// 键盘事件
const nameInput = document.querySelector('#name');
nameInput.addEventListener('input', e => {
  document.querySelector('.container').append(nameInput.value);
});