文章目录
作用域
GC
Js Gc 算法
- 引用计数(已淘汰)
- 标记清除
closure
- 一句话:内层函数 + 外层函数的变量 = 闭包
- 作用:私有化数据,or 私有化状态
变量和函数提升
- Js 祖传
var
变量,目前不会再用 - 变量的提升:只提升声明,不提升赋值
- 除了变量提升,函数也可以提升,但是函数赋值并不能提升(!!!)
fun()
// 这种方式能正常输出到console 【函数声明提升】
// function fun(){
// console.log('done')
// }
// 这种方式能正常 不能 输出到console 【函数赋值不能提升】
fun = function fun(){
console.log('done')
}
函数参数
- 形参、实参
- 默认参数
arguments
//arguments 是特殊的参数,本质是个【伪数组】,它只存在于函数
function sum() {
let sum = 0;
for (let index = 0; index < arguments.length; index++) {
const element = arguments[index];
sum += element;
}
return sum;
}
console.log(sum(1, 2, 3))
- 剩余参数
// 剩余参数,有点像java中的可变参数 【开发过程中还是多使用剩余参数,而不是 arguments】
function config(baseURL, ...other) {
console.log(baseURL)
console.log(other) //Array
}
config('www.a.com', 'get', 'post')
- 展开运算符
// 展开运算符,可以展开数组;注意【展开数组,不会修改原数组】
const arr1 = [1, 2, 3]
// 比如数组最大值
console.log(Math.max(...arr1)) // (method) Math.max(...values: number[]): number
console.log(Math.min(...arr1))
// 合并数组
const arr2 = [4, 5, 6]
console.log(...arr1, ...arr2)
箭头函数
跟java的lamda类似,不过js箭头函数还可以直接生成对象
//【箭头函数生成对象时,注意外面包裹着括号; 同时记得 js对象的属性是不需要引号的】
const f = (name) => ({ name: name })
console.log(typeof (f)) //function
console.log(f('ht')) // {name: 'ht'} ,注意name没有引号,这是个对象,不是个json
箭头函数的this
const f1 = () => {
//【虽然this指window,但window是从上下文,也就是<script> 沿用到的。箭头函数自身是没有this的】
console.log(this) //window
}
f1()
const obj = {
name: 'ht',
sayHi: function () {
console.log(this) //this 指代当前对象obj
}
}
obj.sayHi()
const obj2 = {
name: 'ht2',
sayHi: function () {
console.log('------', this) //this 指代当前对象obj
const hello = () => {
console.log('++++++', this) // this 也 指代当前对象obj
}
hello()
const hay = function () {
// this 指代 window,hay()的执行本质是 window.hay(),注意这里不是箭头函数,而是function
console.log('______', this)
}
hay()
}
}
obj2.sayHi()
DOM中的箭头函数
<script>
const butt = document.querySelector('button')
// 箭头函数指向 window
butt.addEventListener('click', () => {
console.log('-------', this)
});
// 箭头函数指代DOM,也就是 butt 对象
butt.addEventListener('click', function () {
console.log('+++++++', this)
})
</script>
#
解构赋值
数组解构赋值
数组解构必须加分号
const [a, b, c] = [1, 2, 3]
console.log(a) // 能正常得到a 的赋值 ,所以这种语法就是个 语法糖
let x = 1
let y = 2; // 【这里必须要加分号,否则js会把下一行的 [x,y]当做是这一句的一部分,这可能也是Js是个蛋疼语言的原因】
[x, y] = [y, x] // 使用解构语法糖,将 x,y 两个变量进行了值的互换
console.log(x) //2
解构语法中,参数和参数值不匹配的特殊情况
const [a, b] = [1]
console.log(a)
console.log(b) //undefined
const [c, d] = [1, 2, 3]
console.log(c)//1
// 解构支持可变参数
const [e, f, ...g] = [1, 2, 3, 4]
console.log(typeof (g)) //object
console.log(g) //[3,4] ,伪数组
//解构语法也支持多维数组
const [h, [i, j]] = [5, [6, 7]]
console.log(i) //6
对象解构赋值
//解构对象 【其实也就是语法糖】
const person = {
name: 'John',
family: {
father: 'Leo',
sister: 'lily'
},
age: 10
}
// const { name, family: { father, sister } } = person;
// console.log(father)
const { name, family: parent } = person;
console.log(parent) // 【相当于获取了一个 family 变量的别名】
深入对象
// 创建对象的三种姿势
// 其一:对象字面量
const obj = { name: 'ht' }
// 其二:new Object()
const obj2 = new Object()
obj2.name = 'ht'
// 其三:构造函数,这在需要多次创建对象时十分有效
// 一般使用大写开头
function Human(name, age) {
this.name = name // 实例成员
this.age = age// 实例成员
}
const p1 = new Human('ht', 10)
const p2 = new Human('ht2', 20)
Human.eyes = 2 // 静态属性 【因为是操作了 Human这个构造函数】
Human.hi = function () { // 静态方法
return 'everyone says hello'
}
console.log(Human.hi())
console.log(p1)
js built-in functions
js内置构造函数 【注意叫做内置的 构造函数】。这和java的class
类似了。
- Object
- Array
- String
- Number
Object
//--------------Object ----------------
// 构造函数Object内置静态方法
const obj = { name: 'ht', age: 10 }
// 获取所有键 或者 值的集合
console.log(Object.keys(obj))
console.log(Object.values(obj))
//遍历
for (const key in obj) {
console.log(obj[key])
}
// 拷贝并增加属性
const copy = {}
Object.assign(copy, obj)
console.log(copy)
Object.assign(copy, obj, { gender: "male" })
console.log(copy)
Array
foreach
map
reduce
join
find
every
some
concat
sort
splice
reverse
findIndex
Array.from()
【静态方法】
const arr = Array.from([-2, -1, 1, 2])
console.log(typeof (arr)) //object
console.log(arr instanceof Array) //true
console.log(arr)
console.log(arr.join(';'))
console.log(arr.concat([3, 4]))
console.log(arr.reverse())
console.log(arr.splice(1, 1))
console.log(arr.some(function (value, index, array) {
return value === 2
}))
console.log(arr.sort(function (a, b) {
return 1
}))
string
length
split()
substring
startsWith
includes
toUpperCase
toLowerCase
indexOf
replace
match
endsWith
-…
js oop
构造函数
js 原型
- 原型
constructor
- 原型继承
- 原型链
原型
原型继承
// 在原型上增加Array的方法
Array.prototype.sum = function () {
return this.reduce(function (acc, val) { return acc + val });
}
const arr = [1, 2, 3, 4, 5]
console.log(arr.sum())
Array.prototype.max = function () {
return Math.max(...this) // Sprede syntax,扩展参数
}
console.log(arr.max())
constructor
p.s. 窃以为这是js的设计缺陷:给构造函数protoype赋值,反而把构造函数的信息丢掉了
function Star() {
}
// 这种写法很繁琐,所以干脆像后面的写法,将 prototype 赋值为一个对象
// Star.prototype.sing=function(){}
// Star.prototype.dance=function(){}
Star.prototype = { //原型对象
//如果么有这个 constructor 属性,sing 和dance 没法找回定义它们的构造函数
// 窃以为这是Js设计的缺陷.
// constructor 就是为了重新指回这个原型对象的构造函数
constructor: Star,
sing: function () { },
dance: function () { }
}
console.log(Star.prototype)
对象原型
总结就是一句话:实例对象的对象原型(__proto__
) === 构造函数的原型对象(prototype
)。
- 对象原型 -----> 实例对象的原型,对象原型是对象的
- 原型对象 --> 构造函数的原型,原型对象是构造函数
- 无论对象原型(
__proto__
) 还是 构造函数的原型对象(prototype
),都有constructor
属性,指向实例对应的构造函数
function Star(){}
const a = new Star();
const b = new Star();
//不同实例的对象原型是相同的
console.log(a.__proto__ === b.__proto__); //true
//不同实例的对象原型不仅是相同的,而且等于构造函数的原型对象
console.log(a.__proto__ === Star.prototype);//true
// 实例的对象原型和构造函数的原型对象都有 指针指向构造函数
console.log(a.__proto__.constructor === Star.prototype.constructor)//true
console.log(a.__proto__) // {constructor.f}
原型继承
先看一个有缺陷的做法:
const Person = {
eyes: 2
}
function Man() {
}
// Man 构造函数的原型对象是 Person ,【注意 Person不是构造函数,而是一个对象】
Man.prototype = Person;
const man = new Man()
//对象man继承到了 eyes 属性
console.log(man['eyes'])
console.log(Man.prototype) // 少了 constructor 属性
Man.prototype.constructor = Man;
console.log(Man.prototype) // 加上constructor属性
function Woman(){}
Woman.prototype = Person;
Woman.prototype.haveBaby = function(){
console.log('havebaby')
}
// 【虽然只是给Woman.prototype加了新方法,但是man对象也受到了影响,】
// 本质是因为: prototype 是个引用,真正的原型对象是 Person
man.haveBaby() // havebaby
再看一个改善的做法:
function Person() {
this.eyes = 2
}
function Man() {}
function Woman() {}
// Man 构造函数的原型对象是 Person实例
Man.prototype = new Person();
Man.prototype.constructor = Man;
Woman.prototype = new Person()
Woman.prototype.constructor = Woman
Woman.prototype.haveBaby = function(){
console.log("havebaby")
}
const man = new Man()
// caught TypeError: man.haveBaby is not a function
console.log(man.haveBaby())
原型链
总结下来有二句重点:
- 只要是对象,就有__proto__
- 只要是原型对象,就有constructor, 指向构造函数
原型链的本质是个查找规则。
function Person() { }
const person = new Person()
// 下面几个引用构成了 原型链
console.log(person.__proto__) // 【对象 person 的 __proto__ 】
console.log(Person.prototype.__proto__); // 【person 对象原型的 __proto__ 】
console.log(Person.prototype.__proto__ === Object.prototype); //true
console.log(Object.prototype.__proto__ === null); // true 【object的__proto__】
console.log(Person.prototype.constructor); //ƒ Person(){}
console.log(Object.prototype.constructor); //ƒ Object() { [native code] }
原型小案例-- Modal
深浅拷贝
const obj = {
uname: 'ht',
family: {
father: 'old ht'
},
greet: function () {
console.log('hello');
}
}
obj.greet(); //hello
const copy = JSON.parse(JSON.stringify(obj))
// caught TypeError: copy.greet is not a function
// 【说明: JSON 这种深拷贝的方式,只能copy 属性,不能copy方法】
copy.greet()
console.log(copy)
异常处理
this
普通函数this
// 普通函数的this 指向依照原则: 谁调用我,我就指向谁
function hi(){
console.log(this)
}
const hello = function(){
console.log(this)
}
hi();//this 指向 window
hello() //this 指向 window
const ht = {
name: 'ht',
walk:function(){
console.log('walk');
}
}
ht.hi = hi
ht.hello = hello
ht.hi() // this 指代 ht 对象
ht.hello() // this 指代 ht 对象
箭头函数的this
改变this指向
call (有点类似java的反射)
apply
bind
小结
//1、 call() 方法。有点类似java的反射
const ht = {
name: 'ht',
hi: function (x, y) {
console.log(x + y);
console.log(this)
}
}
ht.hi.call(ht, 1, 2)
// 2. apply() ,注意传参有一个数组,这点和 call() 不一样
ht.hi.apply(ht, [1,3])
// apply()另有个典型作用
console.log(Math.max.apply(null, [1,2,3]))
const arr = [1,2,3];
console.log(Math.max(...arr))
console.log(Math.max([1,2,3]))
// 3.bind()
// - bind() “蓄而不发”,也就是 绑定了函数但是不执行
// - bind() 返回值是个函数
// - bind() 能改变this的指向【主要用途】
const func = ht.hi.bind(ht,1,2)
func()
使用案例:
const butt = document.querySelector('button')
butt.addEventListener('click', function (e) {
console.log(this)
this.disabled = true;
/**
setTimeout(function () {
butt.disabled = false;// 不能使用 this,而要使用butt; 因为 this 指代的是 window
}, 2000)
*/
// 使用bind 强制将this指向 button,这样能起到相同的作用
setTimeout(function () {
this.disabled = false;
}.bind(butt), 2000)
});
性能优化
防抖 debounce
<script src="./lodash-4.17.21/package/lodash.min.js">
</script>
<script>
/* 为啥这里的代码写在上面的 script 标签里就会不起作用? */
const box = document.querySelector('.box')
let count = 0;
const func = function (e) {
box.innerHTML = count++;
console.log(count);
}
// box.addEventListener('mousemove', func)
// 1)使用 lodash 的工具来实现
// box.addEventListener('mousemove', _.debounce(func, 500))
// 2) 自己实现
let prev;
box.addEventListener('mousemove', function (e) {
if (prev) {
clearTimeout(prev);
}
prev = setTimeout(func, 500)
})
</script>
节流 throttle
lodash
API 实现
box.addEventListener('mousemove', _.throttle(func, 500))
- 自己实现
function throttle(fun, t) {
let prevTime = Date.now()
// this is a closure
const f = function () {
if (Date.now() - prevTime > t) {
setTimeout(function () {
fun()
}, t)
prevTime = Date.now()
}
}
return f
}
box.addEventListener('mousemove', throttle(func, 500))
清除定时器的trap
<script>
let timer = null;
timer = setTimeout(function () {
// 在setTimeout 中无法删除定时器,因为 定时器【还在运作】,所以使用timer=null
// clearTimeout(timer);
timer = null;
console.log(timer)
}, 1000)
</script>
小结
综合案例:如何保存视频的上一次观看的进度
- 使用
localStorage
存取进度时间 video
对象具有ontimeupdate
和onloadedata
方法- 使用
lodash库的throttle()
实现“节流”(虽一直播放,但每秒才保存一次进度)