无锡住房和城乡建设局网站,黑马程序员大学叫什么,万维网网站301重定向怎么做,外贸网站框架# JavaScript 进阶 - 第3天笔记 了解构造函数原型对象的语法特征#xff0c;掌握 JavaScript 中面向对象编程的实现方式#xff0c;基于面向对象编程思想实现 DOM 操作的封装。
- 了解面向对象编程的一般特征
- 掌握基于构造函数原型对象的逻辑封装
- 掌握基于原型对…# JavaScript 进阶 - 第3天笔记 了解构造函数原型对象的语法特征掌握 JavaScript 中面向对象编程的实现方式基于面向对象编程思想实现 DOM 操作的封装。
- 了解面向对象编程的一般特征
- 掌握基于构造函数原型对象的逻辑封装
- 掌握基于原型对象实现的继承
- 理解什么原型链及其作用
- 能够处理程序异常提升程序执行的健壮性
## 编程思想 学习 JavaScript 中基于原型的面向对象编程序的语法实现理解面向对象编程的特征。
### 面向过程
面向过程就是分析出解决问题所需要的步骤然后用函数把这些步骤一步一步实现使用的时候再一个一个的依次调用就可以了。 举个栗子蛋炒饭!
### 面向对象
面向对象是把事务分解成为一个个对象然后由对象之间分工与合作。
在面向对象程序开发思想中每一个对象都是功能中心具有明确分工。面向对象编程具有灵活、代码可复用、容易维护和开发的优点更适合多人合作的大型软件项目。
面向对象的特性
- 封装性
- 继承性
- 多态性
## 构造函数
对比以下通过面向对象的构造函数实现的封装
htmlscript
function Person() {
this.name 佚名
// 设置名字
this.setName function (name) {
this.name name }
// 读取名字
this.getName () {
console.log(this.name) } }
// 实例对像获得了构造函数中封装的所有逻辑
let p1 new Person()
p1.setName(小明)
console.log(p1.name)
// 实例对象
let p2 new Person()
console.log(p2.name)
/script
封装是面向对象思想中比较重要的一部分js面向对象可以通过构造函数实现的封装。同样的将变量和函数组合到了一起并能通过 this 实现数据的共享所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的
总结
1. 构造函数体现了面向对象的封装特性
2. 构造函数实例创建的对象彼此独立、互不影响封装是面向对象思想中比较重要的一部分js面向对象可以通过构造函数实现的封装。
前面我们学过的构造函数方法很好用但是 存在浪费内存的问题
## 原型对象
构造函数通过原型分配的函数是所有对象所 共享的。
- JavaScript 规定每一个构造函数都有一个 prototype 属性指向另一个对象所以我们也称为原型对象
- 这个对象可以挂载函数对象实例化不会多次创建原型上函数节约内存
- 我们可以把那些不变的方法直接定义在 prototype 对象上这样所有对象的实例就可以共享这些方法。
- 构造函数和原型对象中的this 都指向 实例化的对象
htmlscript
function Person() { }
// 每个函数都有 prototype 属性
console.log(Person.prototype)
/script
了解了 JavaScript 中构造函数与原型对象的关系后再来看原型对象具体的作用如下代码所示
htmlscript
function Person() {
// 此处未定义任何方法 }
// 为构造函数的原型对象添加方法
Person.prototype.sayHi function () {
console.log(Hi~); }
// 实例化
let p1 new Person();
p1.sayHi();
// 输出结果为 Hi~
/script
构造函数 Person 中未定义任何方法这时实例对象调用了原型对象中的方法 sayHi接下来改动一下代码
htmlscript
function Person() {
// 此处定义同名方法 sayHi
this.sayHi function () { console.log(嗨!); } }
// 为构造函数的原型对象添加方法
Person.prototype.sayHi function () {
console.log(Hi~); }
let p1 new Person();
p1.sayHi();
// 输出结果为 嗨!
/script
构造函数 Person 中定义与原型对象中相同名称的方法这时实例对象调用则是构造函中的方法 sayHi。通过以上两个简单示例不难发现 JavaScript 中对象的工作机制**当访问对象的属性或方法时先在当前实例对象是查找然后再去原型对象查找并且原型对象被所有实例共享。**
htmlscript
function Person() {
// 此处定义同名方法 sayHi
this.sayHi function () { console.log(嗨! this.name) } }
// 为构造函数的原型对象添加方法
Person.prototype.sayHi function () { console.log(Hi~ this.name) }
// 在构造函数的原型对象上添加属性 Person.prototype.name 小明
let p1 new Person()
p1.sayHi();
// 输出结果为 嗨!
let p2 new Person()
p2.sayHi()
/script
总结
**结合构造函数原型的特征实际开发中往往会将封装的功能函数添加到原型对象中。**
### constructor 属性
在哪里 每个原型对象里面都有个constructor 属性constructor 构造函数
作用该属性指向该原型对象的构造函数 简单理解就是指向我的爸爸我是有爸爸的孩子
**使用场景**
如果有多个对象的方法我们可以给原型对象采取对象形式赋值.但是这样就会覆盖构造函数原型对象原来的内容这样修改后的原型对象 constructor 就不再指向当前构造函数了此时我们可以在修改后的原型对象中添加一个 constructor 指向原来的构造函数。
### 对象原型
对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法就是因为对象有 __proto__ 原型的存在。
注意- __proto__ 是JS非标准属性
- [[prototype]]和__proto__意义相同- 用来表明当前实例对象指向哪个原型对象prototype
- __proto__对象原型里面也有一个 constructor属性指向创建该实例对象的构造函数
### 原型继承
继承是面向对象编程的另一个特征通过继承进一步提升代码封装的程度JavaScript 中大多是借助原型对象实现继承的特性。龙生龙、凤生凤、老鼠的儿子会打洞描述的正是继承的含义。
htmlbody
script
// 继续抽取 公共的部分放到原型上
// const Person1 {
// eyes: 2,
// head: 1
// }
// const Person2 {
// eyes: 2,
// head: 1
// }
// 构造函数 new 出来的对象 结构一样但是对象不一样
function Person() {
this.eyes 2
this.head 1 }
// console.log(new Person)
// 女人 构造函数 继承 想要 继承 Person
function Woman() { }
// Woman 通过原型来继承 Person
// 父构造函数父类 子构造函数子类
// 子类的原型 new 父类
Woman.prototype new Person()
// {eyes: 2, head: 1}
// 指回原来的构造函数
Woman.prototype.constructor Woman
// 给女人添加一个方法 生孩子
Woman.prototype.baby function () {
console.log(宝贝) }
const red new Woman()
console.log(red)
// console.log(Woman.prototype)
// 男人 构造函数 继承 想要 继承 Person
function Man() { }
// 通过 原型继承 Person
Man.prototype new Person()
Man.prototype.constructor Man
const pink new Man()
console.log(pink)
/script
/body
① 当访问一个对象的属性包括方法时首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型也就是 __proto__指向的 prototype 原型对象
③ 如果还没有就查找原型对象的原型Object的原型对象
④ 依此类推一直找到 Object 为止null
⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向或者说一条路线
⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上 # JavaScript 进阶 - 第4天
## 深浅拷贝
### 浅拷贝
首先浅拷贝和深拷贝只针对引用类型
浅拷贝拷贝的是地址常见方法
1. 拷贝对象Object.assgin() / 展开运算符 {...obj} 拷贝对象
2. 拷贝数组Array.prototype.concat() 或者 [...arr]
如果是简单数据类型拷贝值引用数据类型拷贝的是地址 (简单理解 如果是单层对象没问题如果有多层就有问题)
### 深拷贝
首先浅拷贝和深拷贝只针对引用类型
深拷贝拷贝的是对象不是地址
常见方法
1. 通过递归实现深拷贝
2. lodash/cloneDeep
3. 通过JSON.stringify()实现
### 递归实现深拷贝
函数递归如果一个函数在内部可以调用其本身那么这个函数就是递归函数
- 简单理解:函数内部自己调用自己, 这个函数就是递归函数
- 递归函数的作用和循环效果类似
- 由于递归很容易发生“栈溢出”错误stack overflow所以必须要加退出条件 return
~~~htmlbody
script
const obj {
uname: pink,
age: 18,
hobby: [乒乓球, 足球],
family: { baby: 小pink }
}
const o {}
// 拷贝函数
function deepCopy(newObj, oldObj) {
debugger for (let k in oldObj) {
// 处理数组的问题 一定先写数组 在写 对象 不能颠倒
if (oldObj[k] instanceof Array) {
newObj[k] []
// newObj[k] 接收 [] hobby
// oldObj[k] [乒乓球, 足球]
deepCopy(newObj[k], oldObj[k]) }
else if (oldObj[k] instanceof Object) {
newObj[k] {}
deepCopy(newObj[k], oldObj[k])
} else {
// k 属性名 uname age oldObj[k] 属性值 18
// newObj[k] o.uname 给新对象添加属性
newObj[k] oldObj[k] } } }
deepCopy(o, obj)
// 函数调用 两个参数 o 新对象 obj 旧对象
console.log(o)
o.age 20
o.hobby[0] 篮球
o.family.baby 老pink
console.log(obj)
console.log([1, 23] instanceof Object)
// 复习
// const obj {
// uname: pink,
// age: 18,
// hobby: [乒乓球, 足球]
// }
// function deepCopy({ }, oldObj) {
// // k 属性名 oldObj[k] 属性值
// for (let k in oldObj) {
// // 处理数组的问题 k 变量
// newObj[k] oldObj[k]
// // o.uname pink
// // newObj.k pink
// }
// }
/script
/body~~~
#### js库lodash里面cloneDeep内部实现了深拷贝
~~~htmlbody !-- 先引用 --
script src./lodash.min.js/script
script
const obj {
uname: pink,
age: 18,
hobby: [乒乓球, 足球],
family: { baby: 小pink } }
const o _.cloneDeep(obj)
console.log(o)
o.family.baby 老pink
console.log(obj)
/script/body~~~
#### JSON序列化
~~~htmlbody
script
const obj {
uname: pink,
age: 18,
hobby: [乒乓球, 足球],
family: { baby: 小pink } }
// 把对象转换为 JSON 字符串
// console.log(JSON.stringify(obj))
const o JSON.parse(JSON.stringify(obj))
console.log(o)
o.family.baby 123
console.log(obj)
/script/body~~~
## 异常处理 了解 JavaScript 中程序异常处理的方法提升代码运行的健壮性。
### throw
异常处理是指预估代码执行过程中可能发生的错误然后最大程度的避免错误的发生导致整个程序无法继续运行
总结
1. throw 抛出异常信息程序也会终止执行
2. throw 后面跟的是错误提示信息
3. Error 对象配合 throw 使用能够设置更详细的错误信息
htmlscript
function counter(x, y) {
if(!x || !y) {
// throw 参数不能为空!;
throw new Error(参数不能为空!) }
return x y } counter()/script
### try ... catch
htmlscript
function foo() {
try {
// 查找 DOM 节点
const p document.querySelector(.p)
p.style.color red
}
catch (error) {
// try 代码段中执行有错误时会执行 catch 代码段
// 查看错误信息 console.log(error.message)
// 终止代码继续执行
return }
finally { alert(执行) }
console.log(如果出现错误我的语句不会执行) }
foo()
/script
总结
1. try...catch 用于捕获错误信息
2. 将预估可能发生错误的代码写在 try 代码段中
3. 如果 try 代码段中出现错误后会执行 catch 代码段并截获到错误信息
### debugger相当于断点调试
## 处理this 了解函数中 this 在不同场景下的默认值知道动态指定函数 this 值的方法。
this 是 JavaScript 最具“魅惑”的知识点不同的应用场合 this 的取值可能会有意想不到的结果在此我们对以往学习过的关于【 this 默认的取值】情况进行归纳和总结。
### 普通函数
**普通函数**的调用方式决定了 this 的值即【谁调用 this 的值指向谁】如下代码所示
htmlscript
// 普通函数
function sayHi() { console.log(this) }
// 函数表达式
const sayHello function () { console.log(this) }
// 函数的调用方式决定了 this 的值
sayHi() // window
window.sayHi()
// 普通对象
const user {
name: 小明,
walk: function () { console.log(this) } }
// 动态为 user 添加方法
user.sayHi sayHi
uesr.sayHello sayHello
// 函数调用方式决定了 this 的值
user.sayHi()
user.sayHello()
/script
注 普通函数没有明确调用者时 this 值为 window严格模式下没有调用者时 this 的值为 undefined。
### 箭头函数
**箭头函数**中的 this 与普通函数完全不同也不受调用方式的影响事实上箭头函数中并不存在 this 箭头函数中访问的 this 不过是箭头函数所在作用域的 this 变量。
htmlscript
console.log(this)
// 此处为 window
// 箭头函数
const sayHi function() {
console.log(this)
// 该箭头函数中的 this 为函数声明环境中 this 一致
}
// 普通对象
const user {
name: 小明,
// 该箭头函数中的 this 为函数声明环境中 this 一致
walk: () { console.log(this) },
sleep: function () {
let str hello
console.log(this)
let fn () { console.log(str) console.log(this)
// 该箭头函数中的 this 与 sleep 中的 this 一致 }
// 调用箭头函数 fn();
}
}
// 动态添加方法 user.sayHi sayHi
// 函数调用
user.sayHi()
user.sleep()
user.walk()
/script
在开发中【使用箭头函数前需要考虑函数中 this 的值】**事件回调函数**使用箭头函数时this 为全局的 window因此DOM事件回调函数不推荐使用箭头函数如下代码所示
htmlscript
// DOM 节点
const btn document.querySelector(.btn)
// 箭头函数 此时 this 指向了 window
btn.addEventListener(click, () { console.log(this) })
// 普通函数 此时 this 指向了 DOM 对象
btn.addEventListener(click, function () { console.log(this) })
/script
同样由于箭头函数 this 的原因**基于原型的面向对象也不推荐采用箭头函数**如下代码所示
htmlscript
function Person() { }
// 原型对像上添加了箭头函数
Person.prototype.walk () {
console.log(人都要走路...)
console.log(this); // window
}
const p1 new Person()
p1.walk()
/script
### 改变this指向
以上归纳了普通函数和箭头函数中关于 this 默认值的情形不仅如此 JavaScript 中还允许指定函数中 this 的指向有 3 个方法可以动态指定普通函数中 this 的指向
#### call
使用 call 方法调用函数同时指定函数中 this 的值使用方法如下代码所示
htmlscript
// 普通函数
function sayHi() { console.log(this); }
let user { name: 小明, age: 18 }
let student { name: 小红, age: 16 }
// 调用函数并指定 this 的值 sayHi.call(user);
// this 值为 user sayHi.call(student);
// this 值为 student
// 求和函数 function counter(x, y) { return x y; }
// 调用 counter 函数并传入参数
let result counter.call(null, 5, 10);
console.log(result);
/script
总结
1. call 方法能够在调用函数的同时指定 this 的值
2. 使用 call 方法调用函数时第1个参数为 this 指定的值
3. call 方法的其余参数会依次自动传入函数做为函数的参数
#### apply
使用 call 方法**调用函数**同时指定函数中 this 的值使用方法如下代码所示
htmlscript
// 普通函数
function sayHi() { console.log(this) }
let user { name: 小明, age: 18 }
let student { name: 小红, age: 16 }
// 调用函数并指定 this 的值
sayHi.apply(user) // this 值为 user
sayHi.apply(student) // this 值为 student
// 求和函数
function counter(x, y) { return x y }
// 调用 counter 函数并传入参数
let result counter.apply(null, [5, 10])
console.log(result)
/script
总结
1. apply 方法能够在调用函数的同时指定 this 的值
2. 使用 apply 方法调用函数时第1个参数为 this 指定的值
3. apply 方法第2个参数为数组数组的单元值依次自动传入函数做为函数的参数
#### bind
bind 方法并**不会调用函数**而是创建一个指定了 this 值的新函数使用方法如下代码所示
htmlscript
// 普通函数
function sayHi() { console.log(this) }
let user { name: 小明, age: 18 }
// 调用 bind 指定 this 的值
let sayHello sayHi.bind(user);
// 调用使用 bind 创建的新函数
sayHello()
/script
注bind 方法创建新的函数与原函数的唯一的变化是改变了 this 的值。
## 防抖节流
1. 防抖debounce所谓防抖就是指触发事件后在 n 秒内函数只能执行一次如果在 n 秒内又触发了事件则会重新计算函数执行时间
2. 节流throttle所谓节流就是指连续触发事件但是在 n 秒中只执行一次函数