面向对象编程
类和对象
创建类
类的创建
//类
class Star {
constructor(name) {
this.name = name
}
sing(song) {
console.log(song + this.name);
}
}
const ldh = new Star('刘德华')
ldh.sing('我会唱歌')
//利用构造函数创建的对象
function Start(names, age) {
this.user = names
this.age = age
this.sing = function (song) {
console.log(this.user + '我会唱' + song + '我今年' + this.age);
}
}
var lzk = new Start('刘志坤', 18)
var zxy = new Start('张学友', 50)
lzk.sing('我是傻逼之歌')
zxy.sing('我张学友会唱歌')
类的继承
添加链接描述
entends可以访问父类的方法
//定义一个父类类
class Father {
constructor() {
}
sum() {
console.log(111);//返回111 this指向Son
}
}
//定义一个子类 继承父类
class Son extends Father {
}
//子类实例化
var sonClass = new Son(1, 2)
sonClass.sum()
surpe() 可以调用父类contructor和方法
在子类constructor中使用改方法就可以调用 父类的constructor 并且传参进去
class Father {
constructor(str) {//4
this.str = str//5
}
strClass() {
console.log(this.str);//6
}
}
class Son extends Father {
constructor(str) {//2
super(str)//3
}
}
var sonClass = new Son('我是子类,我需要把值传给父类的constructor')//执行步骤1
sonClass.strClass()//执行步骤1
在子类中调用父类的方法
//子类和父类有相同的方法 由于就近元素则 子类实例调用的话 输出的就是子类的方法
//可以在子类方法中使用super.方法 调用 父类的方法
//super还可以调用constructor 并且传递参数
class Father {
saHai() { //执行步骤3 返回值给super.saHai //调用父类中的普通函数
return '我是爸爸'
}
}
class Son extends Father {
saHai() {
console.log('我是儿子');
console.log(super.saHai())//执行步骤2 调用的是父类的方法
}
}
var sonClass = new Son()
sonClass.saHai()//执行步骤1
子类既可以调用自己的方法 也可以调用父类的方法
//子类和父类有相同的方法 由于就近元素则 子类实例调用的话 输出的就是子类的方法
//可以在子类方法中使用super.方法 调用 父类的方法
//super还可以调用construction 并且传递参数
class Father {
constructor(x, y) {
this.x = x
this.y = y
}
add() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
super(x, y)//super必须在this之前调用
this.x = x
this.y = y
}
sub() {
console.log(this.x - this.y);
}
}
var sonClass = new Son(1, 2)
sonClass.add()
在构造函数里面调用自己的方法
this的指向
构造函数原型
概述
在典型的OOP的语言中(如Java) , 都存在类的概念,类就是对象的模板,对象就是类的实例,但在ES6之前,
JS中并没用引入类的概念。
ES6 ,全称ECMAScript6.0 , 2015.06发版。但是目前浏览器的JavaScript是ES5版本,大多数高版本的浏览器也支持ES6 ,不过只实现了ES6的部分特性和功能。
在ES6之前, 对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定 义对象和它们的特征。
静态成员和实例成员
function Star(name, age) {
this.name = name
this.age = age
}
var ldh = new Star('刘德华', 50)
//实例成员 就是this添加的成员 只能通过实例化对象来访问
console.log(ldh.name);//刘德华
console.log(ldh.sex);//undefined 实例成员不可以访问静态成员添加的数据
//静态成员 只能通过构造函数来访问
Star.sex = '男' //添加静态成员
console.log(Star.sex);//男
console.log(Star.age);//undefined 静态成员不可以访问实例成员
get set 可以监听类的变化和修改
class Phone {
get price() {
console.log('get回调函数');
return '返回给这个p.price'//return是返回给这个方法的值
}
set price(newVal) { //必须要有参数
console.log('修改就调用set');
return '返回给p.price'
}
}
var p = new Phone()
// p.price//调用price方法
// console.log(p.price);//返回给这个price
p.price = 'set回调' //修改就调用了set
console.log(p.price);
构造函数浪费内存的问题
每次创建一个实例 就会开辟内存空间存放函数 如果创建的实例过多 就是造成内存问题
构造函数原型prototype
原型是一个对象 每一个构造函数都有一个原型
function Star(name, age) {
this.name = name
this.age = age
this.sing = function () {
console.log(sing);
}
}
var ldh = new Star('刘德华', 50)
var zxy = new Star('张学友', 50)
console.log(ldh.sing === zxy.sing);//false 说明不是同一个内存空间
明明是同一个方法 为什么 每次创建实例 就是新开辟一个内存空间呢?这么浪费内存
由于每一个构造函数都有原型对象 我们可以吧方法放到原型对象身上
//返回结果见下图
function Star(name, age) {
this.name = name
this.age = age
this.sing = function () {
console.log(sing);
}
}
var ldh = new Star('刘德华', 50)
console.dir(Star);
原型是什么 能做什么
把方法定义到构造函数的原型对象身上 所有的实例都可以使用这个方法 不会开辟新的内存空间
function Star(name, age) {
this.name = name
this.age = age
// this.sing = function () {
// console.log(sing);
// }
}
var ldh = new Star('刘德华', 50)
var zxy = new Star('张学友', 50)
//给构造函数添加原型对象 作用是共享方法 不会辟新的内存空间
Star.prototype.sing = function () {
console.log('我会唱歌');
}
console.log(ldh.sing() === zxy.sing());//true同一个地址
function Star(name, age) {
this.name = name
this.age = age
// this.sing = function () {
// console.log(sing);
// }
}
var ldh = new Star('刘德华', 50)
var zxy = new Star('张学友', 50)
//给构造函数添加原型对象 作用是共享方法 不会辟新的内存空间
Star.prototype.sing = function () {
console.log('我会唱歌');
}
console.log(ldh.__proto__); //指向的是构造函数的prototype
console.log(Star.prototype);//指向的是构造函数的prototype
console.log(ldh.__proto__ === Star.prototype);//true
总结 公共的属性放到构造函数里面 公共的方法放到原型对象身上 不会占用内存
原型对象
原型链 重点
原型对象的应用 扩展内置对象 数组求和
给数组对象添加一求和方法
Array.prototype = {
sum: function () {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum
}
}
this指向的是谁调用 就指向谁 如果arr.sum那就是指向arr
var arr = [1, 2, 3] //利用数组字面量创建一个数组实例
// var arr = new Array(1, 2, 3)
console.log(arr.sum());//调用原型对象上的方法
console.log(Array.prototype);
构造函数继承 ES6之前使用
ES6之前使用 call() 修改this指向方法 继承属性
继承父构造函数的两个属性 和类不同 没有extends
// call方法可以改变this的指向
function fn(x, y) {
console.log(this);//由于call修改了this指向 所以指向的是o
console.log(x + y);
}
var o = {
name: 'liu '
}
fn.call(o, 1, 2)//在调用函数的时候使用
//父构造函数
function Father(uname, age) {
this.uname = uname
this.age = age
}
//子构造函数
var that;
function Son(uname, age) {
that = this//Son {uname: "刘德华", age: 18}
Father.call(this, uname, age) //这里this指向的就是ldh这个Son实例对象
}
var ldh = new Son('刘德华', 18)
console.log(that);
console.log(ldh.uname);
继承父原型对象的方法
//继承方法
//父构造函数有一个money方法 子原型对象需要拿到这个方法
function Father(name, age) {
this.name = name
this.age = age
}
Father.prototype.money = function () {//方法
console.log('我是父原型对象身上的方法');
}
//第一种方法不推荐 通过把父亲的原型对象赋值给儿子的原型对象 这样虽然可以拿到money 但是父亲的原型对象里面的方法也会和儿子的同步
// Son.prototype = Father.prototype
//第二种方法 推荐 给父构造函数 创建实例化 实例化会new一个新的内存空间 里面的prototype指向的是父构造函数的实例对象
Son.prototype = new Father()
Son.prototype.constructor = Son//new Father()的构造函数会覆盖子构造函数 需要重新指回
//子构造函数
function Son(name, age) {
this.name = name
}
//子原型对象
Son.prototype.exam = function () {
console.log('我是子原型对象,我需要考试');
}
var ldh = new Son('刘德华', 18)
console.log(ldh);
console.log(Son.prototype.constructor);
console.log(Father.prototype.constructor);
面向对象编程
类的本质其实还是一个函数我们也可以简单的认为类就是构造函数的另外一种写法
函数
函数的原型
利用new Function创建的函数不存在闭包 只要创建就是一个全局函数
new Function创建的函数会生成一个实例对象 this指向的是实例对象
函数的调用方式
添加链接描述
函数this的指向
改变函数内部this的指向 bind() call() apply()
call()
var person = {
fullName: function() {
return this.firstName + " " + this.lastName;
}
}
var person1 = {
firstName: "Bill",
lastName: "Gates",
}
person.fullName.apply(person1); // 将返回 "Bill Gates"
call方法主要是实现继承父构造函数的方法 下面有两个构造函数
function Father(uname, age, sex) {
//与此同时 这里的this都是指向son实例对象
console.log(this);
this.uname = uname
this.age = age
this.sex = sex
}
function Son(uname, age, sex) {
//修改父构造函数的this指向 这里的this指向son实例对象
Father.call(this, uname, age, sex)//在子构造函数类调用父构造函数的方法
}
const son = new Son('刘德华', 18, '男')
aplly()第二个参数是伪数组
//1.也是调用函数第二个可以改变函数内部的this指向
//2.但是他的参数必须是数组(伪数组)
// 3. apply的主要应用比如说我们可以利用apply 借助于数学内置对象求最大值
// Math . max();
var arr = [1, 66, 3, 99, 4];
Math . max. apply(Math, arr);
bind()改变this指向 但是不立即调用函数 绑定捆绑
// 3. bind() 绑定捆绑的意思
var。={
name :andy '
};
function fn(a, b) {
console .log(this);//o
console.1og(a + b);//3
};
var f = fn.bind(o, 1, 2);
f();
//1.不会调用原来的函数
//可以改变原来函数内部的this指向
// 2.返回的是原函数改变this之后产生的新函数
//点击按钮关闭 三秒钟后显示
//这是是利用bind修改了计时器函数的this指向 不立即执行 计时器会自动3秒后执行
var btn = document.querySelector('button')
btn.addEventListener('click', function () {
this.disabled = true;
setTimeout(function () {//默认计时器this指向windows
console.log(this);
}.bind(this), 3000) //所以我们需要修改他的指向 bind的this是在定时器函数外面绑定的 这个this指向的是btn
})
总结
高阶函数
闭包
什么是闭包
闭包
闭包( closure )指有权访问另-一个函数作用域中变量的函数。 ----- JavaScript高级程序设计
简单理解就是, -个作用域可以访问另外-个函数内部的局部变量。
闭包被访问的变量所在的函数 就是闭包函数
function fn() {
var sum = 10;
function fun() {
console.log(sum);//fun函数可以访问到sum sum所在的函数就是闭包函数
}
fun()
}
fn()
闭包的主要作用是延伸了作用范围 等到f()调用完成这个内存就会被销毁
闭包:能够读取其他函数内部变量的函数。(应用场景:要获取某函数内部的局部变量)
闭包的优点:1.能够读取函数内部的变量 2.让这些变量一直存在于内存中,不会在调用结束后,被垃圾回收机制回收
闭包的缺点:正所谓物极必反,由于闭包会使函数中的变量保存在内存中,内存消耗很大,所以不能滥用闭包,解决办法是,退出函数之前,将不使用的局部变量删除
闭包案列 必考
//三公里内 起步价13; 之后每1公里+5块钱; 拥堵多加10块钱
var car = (function () {
var total = 0;//总价
var start = 13;//起步价
return {
price: function (n) {
if (n <= 3) {
total = start
} else {
total = start + (n - 3) * 5
}
return total
},
yd: function (flag) {
return flag ? total + 10 : total
},
}
})()
console.log(car.price(5));
console.log(car.yd(true));
在get函数里面谁调用就指向谁
递归
下面是常规的递归代码
function fn() {
console.log('递归');
fn()
}
fn()
必须加退出条件
var sum = 1
function fn() {
console.log('打印5次')
if (sum === 5) {
return
}
sum++
fn()
}
fn()
递归的执行顺序
function fn(n) {
if (n == 1) {
return 1
}
return n * fn(n - 1)
}
console.log(fn(5));
//执行顺序
5 * fn(4)
5 * (4 * fn(3))
5 * (4 * (3 * fn(2))
5 * (4 * (3 * (2 * fn(1)))
5 * (4 * (3 * (2 * (1))//120
斐波那契数列
递归对数据处理
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱'
}, {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰',
}];
//输入id号就可以返回一个数据对象
//利用foreach遍历外面的数组 利用递归传递里面的数组
function getId(data, idc) {
data.forEach(function (item) {
if (item.id == idc) {
console.log(item);
}
//判断有这个数组吗?并且数组里面的长度大于0吗?
else if (item.goods && item.goods.length !== 0) {
getId(item.goods, idc)//递归
}
else {
return
}
})
}
getId(data, 12)
浅拷贝
浅拷贝遇到更深层次的 只是拷贝地址
var obj = {
name: '的话',
age: 18,
mas: {
sex: '男人'
}
}
var o = {}
for (let k in obj) {
o[k] = obj[k]
}
o.mas.sex = '女人'//修改拷贝后的对象
console.log(obj);//原对象跟着改变了
console.log(o);//对象改变
assign方法浅拷贝
var obj = {
name: '的话',
age: 18,
mas: {
sex: '男人'
}
}
var o = {}
Object.assign(o, obj)
o.mas.sex = '女人'
console.log(obj);
深拷贝
var oldObj = {
name: '的话',
age: 18,
mas: {
sex: '男人'
},
arr: ['翔安', '江西']
}
//定义一个空的对象 用来接收拷贝的数据
var newObj = {}
//深拷贝
function deepCope(newObj, oldObj) {
for (var k in oldObj) {
// 先把属性值给保存出来 以便于判断
var item = oldObj[k]
// console.log(item);
//判断是不是数组 必须吧数组返到最前面
if (item instanceof Array) {
newObj[k] = []//属性名是一空数组
console.log(item);//这里面拿到的是查找到的数组
deepCope(newObj[k], item)
}
else if (item instanceof Object) {
newObj[k] = {}
deepCope(newObj[k], item)
}
else {
newObj[k] = item;
}
}
}
deepCope(newObj, oldObj)
console.log(newObj);
ES6
let变量 作用域
不影响作用域链接
可以输出
let的块级作用域在循环
const常量
解构赋值
var num = {
p: '100'
}
//解构赋值 p是从对象解构出来的值 data是给他命名
var { p: data } = num
console.log(data);
ES6强化写法
箭头函数
**箭头函数中this是静态的,始终指向的是所在作用域下的值 用call也不行 **
如图所示getName2是箭头函数 他所在作用域就是指向Windows无法通过call等改变this指向的方法
缺点
不用为构造函数实例化对象
不能使用arguments
箭头函数的this指向
rest参数替代arguments
可以使用一些数组的方法比如push put ; arguments则不可以
扩展运算符
ES5 concat合并数组
**//「…」扩展运算符能将「数组」转换为逗号分隔的「参数序列」
//1.数组的合并
const kuaizi = ['王太利', '肖央'];
const fenghuang = ['曾毅', '玲花'];
const zuixuanxiaopingguo = kuaizi.concat(fenghuang);
console.log(zuixuanxiaopingguo);//["王太利", "肖央", "曾毅", "玲花"]
const ES6 = [...kuaizi, ...fenghuang]
console.log(ES6);//["王太利", "肖央", "曾毅", "玲花"]
利用…运算符对对象合并
flat() flatMap把多维数组转化为一维数组[1,2,3,[5,6]]
伪数组转换真数组
symbol的唯一性 没认真学
js数据类型口诀 usonb
Es6迭代器 自定义遍历
迭代器的运行原理
- 声明一个数组
- 他有symobl.interator这个属性
- 每次interator.next会返回一个包含value和done的对象
promise重点
Promise是ES6引入的异步编区的新解决方案。语法上Promise是一个构造函数,
用来封装异步操作并可以获取其成功或失败的结果。
1) Promise 构造函数: Promise (excutor) {}
2) Promise.prototype.then 方法
3) Promise.prototype.catch 方法
// Promise是一个构造函数
// resolve 表示成功 可以通过.then来调用
// reject 表示失败 可以通过.catch来调用
const p = new Promise(function (resolve, reject) {
setTimeout(function () {
var err = '数据读取失败'
var success = '读取成功'
resolve(success)
reject(err)
}, 1000)
})
//调用构造函数
p.then((res) => { console.log(res) }).catch((rej) => { console.log(rej); })
解决回调地狱
读取文件 使用fs读取
使用Promise读取
var p = new Promise((resolve, reject) => {
fs.readFile('./劝学.md', (err, data) => {
resolve(data)
})
})
// 调用
p.then((res) => {//此时的res是劝学
return new Promise((resolve, reject) => {
fs.readFile('./劝学2.md', (err, data) => {
resolve([res, data])//把成功获取的数值传递给下一个then
})
})
}).then((res) => { //此时的res是一个数组 劝学 和劝学2
return new Promise((resolve, reject) => {
fs.readFile('./劝学3.md', (err, data) => {
res.push(data)
resolve(res)//把成功获取的数值传递给下一个then
})
})
}).then((res) => {//此时的res是所有数据的集合
console.log(res.join('\n'));
})
async await语法糖读取异步任务
先封装promise函数
再封装一个async函数 await后面放promise对象
async await
promiseAll 批量异步任务场景
promiseAll全部都成功 才返回成功的promise 和promiseallsettlde不同始终都能返回成功 始终都能得到每一个成功和失败的状态结果
//await只能在async函数中 后面是promise对象
Ajax请求数据 这个很常用
Ajax封装 这个很常用
封装一个Ajax请求 返回的数据是prmise对象
老方法 调用封装的函数 因为是promise对象 有them方法 输出结果
新方法 优雅 在后面也async函数里边 await后面放promise对象 声明一个值接收
对数组处理的新方法
set 升级版数组 自动去重
是一个对象
集合
let arr = [1, 2, 3, 4, 5, 3, 2, 1]
let arr2 = [4, 5, 6, 5, 6]
//求交集 他两都有
// let result = [...new Set(arr)].filter(item => {
// if (new Set(arr2).has(item)) {
// return true
// }
// else {
// return false
// }
// })
let result = [...new Set(arr)].filter(item => new Set(arr2).has(item))
console.log(result);//[4,5]
//求并集
let all = [...new Set([...arr, ...arr2])]
console.log(all);//[1, 2, 3, 4, 5, 6]
//求差集
let result2 = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)))
console.log(result2);
map 升级版对象
解决浮点数精度问题
java的浮点数是不对等的
对象的合并覆盖 assign()
Es7 includes方法 检测数组时候包含某一个元素 类似indexOf
var arr = [1, 5, 8, 79, 878, 5, 8, 6]
console.log(arr.includes(5));