引入 JS
HTML中引入JS的四种方式:
头部引入js
<head>
<title>Document</title>
<script type="text/javascript">
// ... js 代码
</script>
</head>
页面主体引入js
<!DOCTYPE html>
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
<title>Document</title>
</head>
<script type="text/javascript">
// ...js 代码
</script>
</html>
元素事件中直接编写
<input type="button" onClick="alert(Hello world)" value="Click me"/>
引入外部js
<script src="./js/index.js" type="text/javascript"></script>
一、严格模式
在全局或者函数的第一条语句写:'usestrict'
语法行为就变为:
- 声明变量必须带上
var
等关键字 - 自定义函数中的
this
指向window对象 - 对象不能有重名的属性
二、数据类型
2.1 7种基本类型
2.2 对象类型
Object
:任意对象,用于存储对象的数据,是一种无序数据集合。
属性的访问:.属性名
[属性名]
,属性名本质就是字符串!
Function
:一种特别的对象,用于存储代码Array
:有序的数据集合
2.3 判断
typeof
:返回数据类型的字符串表达式 | 类型 | tyeof | | —- | —- | | object | object | | function | function | | array | object | | string | string | | number | number | | boolean | boolean | | undefined | undefined | | null | object |instanceof
:用于判断一个对象实例的具体类型==
:值是否相等===
:值和类型是否相等
2.4 拓展运算符
两个作用:
打包。将一系列数据集合到一个数组中,常用于函数传递参数
// args中集合了传入fn函数的所有数据
function fn(...args) {
// ...
}
解包。将一个数据集合拆分成一系列逗号分隔的独立数据。
三、基本语法
3.1 变量解构赋值
解构失败的时候得到的值是
undefined
. 注意null
和undefined
的区别:
undefined
是指某个变量没有初始值,本身就是undefined
类型。原本应该有值但是没有赋值,或者本身就没有定义;null
是指一个空对象,类型为object
。
3.1.1 数组解构
let [x, y] = [1, 2, 3]
x // 1
y // 2
只要某种数据解构具有Iterator接口就都可以使用数组解构
let [x, y, z] = new Set(['a', 'b', 'c'])
3.1.2 对象解构
数组的解构是按照次序排列的,变量的值由其位置决定;对象的解构没有次序之分,按照变量名进行匹配取得正确的值。
let {bar, foo} = {foo: 'aaa', bar: 'bbb'}
const {log} = console
log('hello')
对象解构的本质就是对象拓展的简写:src:dst
let {foo: foo, bar: bar} = {
foo: 'aaa',
bar: 'bbb'
}
let {foo: bar} = {
foo: 'aaa',
bar: 'bbb'
}
bar // 'aaa'
foo // error: foo is not defined
四、函数
4.1 函数定义
关键字声明
function fn() {...}
表达式声明 ```javascript let fn = function() {…}
function Fn() {…} let fn = new Fn()
3. 箭头函数
```javascript
let fn = () => {...}
- Immediately Invoked Function Expression
立即调用函数,内部的变量外部不可见(即使是var
类型)let a = (function() {
var name = "Beney"
console.log(name)
return name;
})()
a // Beney
name // throws "Uncaught ReferenceError..."
4.2 函数调用
函数名()
this问题👏
this
指向最终调用这个函数的对象,一切皆对象,函数必然是由某个对象调用。(更具体的可以说是,一切都是函数对象,所有对象都是由构造函数创建)
bind,apply,call 区别
都是用于绑定函数的this
对象,即绑定函数的调用者。
- bind:
fn.bind(x)
,返回一个绑定到x对象的函数。 - apply:
fn.apply(x, [argsArray])
,接收一个参数数组,绑定到x对象,并执行函数。 - call:
fn.call(x, [arg1, [arg2, [...]]])
,接收一系列参数,绑定到x对象,并执行函数。
箭头函数的this
ES6 中的箭头函数 this 始终指向函数定义时的 this,而非执行时。
箭头函数没有this绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则this绑定到最近一层非箭头函数的this,否则this为undefined。
let a = {
name: "Beney",
fn2: function() {
console.log(this.name)
},
fn2: function() {
setTimeout(() => {
this.fn1() // 最近一层的函数的this为a对象
}, 100)
}
}
a.fn2() // Beney
使用变量存储this
若不使用 ES6 则使用这种方式最为简单,先将调用本函数的对象存储到一个变量 _this
,然后函数中都使用这个 _this
,就保证了调用对象不会突然改变。
let a = {
name: "Beney",
fn1: function() {
console.log(this.name)
},
fn2: function() {
let _this = this // 使用变量存储this
setTimeout(function() {
_this.fn1()
}, 100)
}
}
a.fn2() // Beney
回调函数
声明了之后供别处调用的函数,常用作参数传递。
五、函数进阶🔰
5.1 原型
函数的 prototype 属性
函数定义时,默认添加
prototype
属性
每一个一个函数对象**都有一个** prototype(显式原型),默认指向一个空的Object对象(除了Object)。
这个 prototype 对象就是之后创建该函数实例对象的爸爸,prototype 中的所有方法都可以供实例对象使用。
函数对象和其原型对象为相互引用的关系:
- 函数对象的 prototype 指向原型对象
- 原型对象的 constructor 指向函数对象
function Fn () {
// 内部默认语句: Fn.prototype = {}
}
console.log(Fn.prototype.constructor === Fn) // true
实例的 proto
实例对象创建时,默认添加
__proto__
每一个实例对象(不仅函数实例)都有一个 __proto__
属性,隐式原型。
实例对象的隐式原型等于其构造函数的显式原型,即都指向一个统一的模板——原型对象
// 构造函数
function Fn () {
}
// 实例化一个对象
let fun = new Fn() // 内部默认语句:this.__proto__ = Fn.prototype
console.log(fun.__proto__ === Fn.prototype) // true
内存关系图
function Fn() {
}
let fn = new Fn()
Fn.prototype.test = () => {...}
总结:
- 函数的 prototype 属性,在定义的时候自动添加,默认值为一个空的Object对象。
- 函数的原型对象和函数相互引用。
- 实例对象的 proto 属性,在创建实例的时候自动添加,默认值为构造函数的原型对象。
- 所有原型对象,都可以看作是实例对象的一个父类,给实例对象提供了可以调用的方法。类似Java中的抽象类,用于同一份代码的复用。
function Fn() { // Fn.prototype = {}
}
let fn = new Fn() // fn.__proto__ = Fn.prototype
5.2 原型链
原型链本质是隐式原型链,其原理如下:
- 在访问一个对象的**属性**时
- 先在当前对象的属性中查找,找到则返回。
- 否则,一直沿着 proto 在其原型对象中查找,如此套娃,直至找到属性
- 若始终没有找到,返回undefined
原型链:查找对象属性
function Fn () {
}
let fn = new Fn()
fn.toString()
若仅关注对 toString 方法的查找:
若关注全局的关系:
function Fn()
本质为var Fn = new Function()
- 所有函数都是由 Function 创建,所以所有函数的 proto 都是Function Prototype
对象如Object也是如此,因为 Object 也是一个函数,用于创建对象
再次强调:所有函数的 proto 都指向 Function Prototyp - 特例:Function 函数本身也是由 Function 创建,即 Function 既是实例又是构造函数,所以 Function 的 proto 和 prototype 都指向 Function Prototype
原型继承
- 构造函数的实例对象自动拥有构造函数原型对象的所有属性(方法),实例就类似一个子类继承构造方法的原型。
- 本质利用的就是隐式原型链。
原型属性问题
- 读取对象的属性时,会自动到原型链中查找。
- 设置对象属性时,不会查找原型链。若当前对象没有此属性,则直接添加此属性并设置值。
- 方法一般定义在原型中方便复用;属性一般通过构造函数定义在实例本身上。
function Fn() {
this.test1 = () => {...} // 每一个实例独享一个test1
}
Fn.prototype.test2 = () => {...} // 所有实例共享一个test2
instanceof
表达式:A instanceof B
若 B Prototype在 A 实例的原型链上,则返回 true,否则为 false。
小测试
结合之前的全局关系图可以解决
function Fn() {}
Object.prototype.a = () => {
console.log('a()')
}
Function.prototype.b = () => {
console.log('b()')
}
var fn = new Fn()
fn.a() // a()
fn.b() // Uncaught TypeError,原型链上找不到b
Fn.a() // a()
Fn.b() // b()
// fn --> Fn.prototype --> Object.prototype
// Fn --> Function.prototype --> Object.prototype
细节总结
- 函数的显式原型都是指向一个空的 Object 实例对象,除了 Object,因为
Object.prototype.__proto__ === null
,故 Object Prototype 并不是一个 Object 空对象实例。 - 所有函数都是 Function 的实例,包括 Function 本身。
- Object Prototype 是原型链的尽头,再往上就是 null 了。
- 判断实例是否是某个函数的实例,只需要比较实例的隐式原型链,和函数的显式原型链是否相等。
5.3 执行上下文
执行上下文栈
全局代码执行前,JS 引擎就会创建一个栈来管理所有的上下文对象。一旦执行就会先创建 window 全局上下文。
- 创建准备执行的上下文
- 预处理准备:形参取值,提升变量,提升函数,this,arguments
- 执行函数体语句
window 的执行上下文就相当于 Java 中的 main 函数执行栈帧的概念。所以一开始运行 JS 代码,就有全局的window 上下文。
所谓的上下文可以看作是一个函数执行栈,一个函数执行产生一个栈帧。
变量声明提升
通过 var 定义的变量,其**声明语句会被自动提升到当前上下文域内的**最上方先执行。
function fn() {
// ...
var a = 3
}
function fn() {
var a // **undefined**
//...
a = 3
}
console.log(a) // a的作用域之外,报错
// Uncaught ReferenceError: a is not defined
函数声明提升
函数对象的声明也会自动提升到作用域的最上方执行。函数定义就是创建了一个函数对象。
所以函数定义之前,就可以调用该函数。
fn() // hello
function fn () {
console.log('hello')
}
fn() // 本质是变量提升,所以会报错!
var fn = function () {...}
note:变量提升先于函数提升。
细节
同名函数、变量同时提升
var c = 1
function c(c) {
console.log(c)
}
c(2) // Uncaught TypeError: c is not a function
以上写法经过提升后实际的执行顺序:
var c
function c(c) {
console.log(c)
}
c = 1
c(2) // 现在的报错原因就一目而了然
5.4 作用域
作用域分类:
- 全局作用域
- 函数作用域
- 块作用域,let,const
作用域指变量能够被访问到的一个范围,是一个静态的概念,函数定义好之后就一直存在,不会再改变。
作用域 | 上下文 |
---|---|
函数定义时就确定 | 一个函数执行创建一个上下文 |
静态概念 ,确定后不会改变 |
动态概念,函数调用时创建,函数结束时销毁 |
代码层面的概念 | 底层执行的概念 |
作用域链
作用域链的意思是:在当前作用域中查找不到某个变量的时候,会向外在更大的一层作用域中查找变量。
var a = 2
function fn1() {
var b = 3
function fn2() {
var c = 4
console.log(c)
console.log(b) // 向外一层作用域
console.log(a) // 向外两层作用域
}
fn2()
}
fn1()
案例理解
案例1
var x = 10
function fn() {
console.log(x)
}
function show(f) {
var x = 20
f()
}
show(fn)
案例2
var fn = function () {
console.log(fn)
}
fn()
案例3
var obj = {
fn2: function () {
console.log(fn2)
}
}
obj.fn2()
案例3修正
var obj = {
fn2: function () {
console.log(this.fn2) // 使用this
}
}
obj.fn2()
案例4,给按钮添加监听
let btns = document.getElementsByTagName('button')
for (let i = 0; i < btns.length; ++i) {
let btn = btns[i]
btn.onclick = () => {
alert(`第${i}个btn`)
}
} // let为块级作用域,能够正常显示
let btns = document.getElementsByTagName('button')
for (var i = 0, length = btns.length; i < length; ++i) {
let btn = btns[i]
btn.onclick = () => {
alert(`第${i}个btn`)
}
}
// var 为全局作用域,由于在点击按钮的时候循环已经执行完
// 故 i == length,所以无论点击哪一个按钮 i 都是 length 值
总结:let、const 是块级作用域,var 是全局作用域,作用域的不同使得变量提升的程度不同。
六、闭包
6.1 是啥
闭包就是变量的集合对象,内嵌子函数引用其父函数的变量时就可以产生闭包。
产生条件:
- 嵌套函数
- 内部函数引用了外部函数的数据
- 外部函数调用,内部函数定义执行后(内部函数调用之前)
- 闭包中包含的是变量
闭包用于在函数内部封装一些变量,并存储变量的状态,使得外部不可见。也可以看作是将一个操作的执行分成多个阶段,拆分成多个套娃函数一步步调用。
封装变量的本质是利用函数的作用域对于外部不可访问。
function a() {
const name = "Beney"
return function b() {
console.log(name) // 引用了外部函数的变量
}
}
a()() // Beney,内部的name外部无法访问
通过Chrome的开发者工具可以很方便观察到。
6.2 闭包作用
- 使得外部函数的局部变量在外部函数执行完后,仍然存活在内存中。延长了局部变量的生命周期。(外部函数每执行一次,产生一个闭包)
- 外部函数可以操作内部函数引用的数据。
局部变量能够保存的本质:内部函数对象一直存在,且函数对象内引用着外部函数的局部变量。 所以包含闭包的函数变成垃圾对象,闭包才死亡。
6.3 闭包缺点
- 函数执行完后,内部的局部变量没有释放,占用内存的时间较长。
- 若忘记释放对内部函数的引用,就会导致内存泄漏。
六、AJAX
Asynchronous Javascript and XML,是一种提高页面更新性能的 Web 技术,性能提高处主要在于对页面的部分更新,就是一种异步更新技术。
在此之前,页面只能全部更新,开销比较大。所以使用 AJAX 能够提高 B/S 的体验。但是现今 AJAX 由于其编写繁杂,可以使用浏览器的原生 api fetch
来替代
fetch(url, {
method: 'POST',
headers: {KV设置http头部的参数},
body: JSON.stringify({要传入http主体的数据}),
// ... 以上都是http报文的参数配置
}).then(async resp => {
const data = await resp.json();
console("receive data:", data);
})
/**
* post方式请求
* @param url {string} 请求地址
* @param payload {Object} 数据内容
* @return {Promise<Response>}
*/
export const doPost = async (url, payload) => {
return await fetch(url, {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json;charset=UTF-8",
},
body: JSON.stringify(payload),
});
};
七、常用类型API
7.1 Array
- push(…elem):尾部添加元素
- pop():删除尾部元素
- unshift(…elem):首部添加元素
- shift():删除首部元素
- splice(pos, cnt):指定位置删除指定个元素
- slice(i1, i2):返回[i1, i2)的浅拷贝,无参默认全部取出
- concat(Array):返回拼接后的新数组,原数组不受影响
- join(string):当前Array的每一个元素之间用指定的字符串相连,返回最终的结果字符串
- map(callback(elm, index, array)):数组的每一个元素重新映射,返回一个新的数组
- filter(callback(elm, index, array)):过滤数组元素,callback返回
true
则保留该元素,返回一个新的数组 - reduce(callback(acc, elm, index, array)):每一次计算的值累积到
acc
中,最后返回acc
。