面向对象编程
面向过程编程(POP)
面向过程即分析好步骤,按照步骤编程。
面向对象编程(OOP)
找出对象,以对象功能来划分问题。
面向对象的特点:封装性 继承性 多态性
易维护、易复用、易扩展,性能比面向过程稍微低一点。
ES6中的类和对象
类class
类是泛指的,抽象了对象的公共部分;
对象是特指某一个,通过实例化具体的对象。
<script>
//1.创建类class constructor为构造函数,new的时候会自动调用这个构造函数;
class Star {
constructor(uname, age) {
this.name = uname;
this.age = age;
}
//类里面的函数无需class 多个函数方法之间无需加逗号分隔
sing(song) {
console.log('我唱歌');
console.log(this.name + song);
}
}
//2.利用类创建对象new
var ldh = new Star('刘德华', 20);
console.log(ldh.name);
console.log(ldh.age);
ldh.sing('冰雨');
</script>
类的继承
包括extends spuer()
<script>
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
say() {
return '我是你爹';
}
}
//extends 子类继承父类的属性和方法
class Son extends Father {
constructor(x, y) {
//下面这种不对,因为father里的sum,this.x this.y指向的是father的构造函数
// this.x = x;
// this.y = y;
super(x, y);//调用父类的(构造)函数
//子类扩展自己新的方法,但super必须在子类this之前调用
this.x = x;
this.y = y;
}
say() {
// console.log('我是你儿');
console.log(super.say() + '的儿子');//子类通过super.函数名()调用父类的普通函数。
}
minus() {
console.log(this.x - this.y);//指向的是子类的构造函数的this.x this.y
}
}
var son = new Son(1, 2);
son.sum();
son.say();//就近原则,先在子类里查找有没有该方法
var son2 = new Son(6, 1)
son2.minus();
son2.sum();
类的注意事项01
类的注意事项02(this指向问题)
<button>点击</button>
<script>
var _that = null;
var that = null;//全局变量,在任何一个地方都可以使用
//类没有提升,要写在前面
class Star {
constructor(uname) {
that = this;
//constructor里面的this指向的是创建的实例对象,此处是ldh
this.uname = uname;
//this.song() 可以调用song函数
this.btn = document.querySelector('button');
this.btn.addEventListener('click', this.song);//都要加this 这里函数后面不要加小括号,不然马上就调用了,谁调用方法this就指向谁
}
song() {
console.log(this);//this指向的是btn,所以下面是undefined
console.log(this.uname);
console.log(that.uname);//that指向的是constructor里面的this
}
dance() {
_that = this;
console.log(this);
}
}
var ldh = new Star('刘德华');
ldh.dance();//lhd调用了dance(),则dance里的this指向的是ldh
构造函数和原型
静态成员和实例成员
构造函数的问题
注意简单数据类型、复杂数据类型
构造函数存在内存浪费的问题,我们的方法function(){}是复杂数据类型,有多少个对象就会重复开辟多少个内存来存放方法。
构造函数原型 prototype
每个构造函数都有prototype属性,指向另一个对象叫做原型对象。
对象原型_proto_
为什么对象可以访问构造函数的原型对象的方法:对象身上系统自己添加一个_proto_指向我们的构造函数的原型对象prototype
constructor构造函数
原型对象prototype和对象原型_proto_都有constructor属性,他会指回原来的构造函数,有时候需要手动指回原来的构造函数:
当我们是赋值操作的时候,原型对象被直接修改,就需要手动添加constructor属性指回原来的构造函数。
构造函数、实例、对象原型之间的关系
原型链
只要是对象就有原型 _ proto_
原型一定指向一个原型对象 prototype
原型链成员查找规则
就近查找。
Object.prototype有toString()方法,其他的没有,但我们可以输出对象实例的toSrting()。
原型对象this指向
原型对象函数里面的this指向的仍然是实例对象。下面输出true
扩展内置对象
注意要使用“.”的形式来添加新的方法,不要赋值!
继承
ES6之前:构造函数+原型对象 组合继承
call()
作用:
1.调用函数;
2.改变this指向;注意第一个参数是改变this指向,后面的参数当函数的普通实参来看。
借用父构造函数继承属性
也可以添加子类自己的属性:
ES5中的新增方法
数组方法
forEach() 和filter()里面的return不会终止迭代
var arr = [12, 66, 4, 88, 9, 7];
var newArr = arr.filter(function (value, index) {
return value % 2 == 0;
});
console.log(arr);
console.log(newArr);
//some 查找是否有满足条件的元素
//some找到第一个符合条件的元素就停止循环了
var flag = arr.some(function (value) {
return value > 2;
})
console.log(flag);
在some里遇到return true就终止遍历。
字符串方法
去除字符串两边的空格:
str.trim();
可以用它来解决表单的bug:
对象方法
Object.keys(obj);
返回所有属性名组成的数组
Object.defineProperty()定义新的属性或修改属性。
这样修改是失效的。
enumerable默认是false,无法遍历,Object.keys(obj)的时候就没有它。
当configurable为false则不允许被删除,且不允许再修改第三个参数的特性。
函数进阶
函数的定义和调用
所有的函数都是Function的实例(对象),即函数也是对象,函数也有原型。
// 函数的调用方式
// 1. 普通函数
function fn() {
console.log('人生的巅峰');
}
// fn(); fn.call()
// 2. 对象的方法
var o = {
sayHi: function() {
console.log('人生的巅峰');
}
}
o.sayHi();
// 3. 构造函数
function Star() {};
new Star();
// 4. 绑定事件函数
// btn.onclick = function() {}; // 点击了按钮就可以调用这个函数
// 5. 定时器函数
// setInterval(function() {}, 1000); 这个函数是定时器自动1秒钟调用一次
// 6. 立即执行函数
(function() {
console.log('人生的巅峰');
})();
// 立即执行函数是自动调用
this
// 函数的不同调用方式决定了this 的指向不同
// 1. 普通函数 this 指向window
function fn() {
console.log('普通函数的this' + this);
}
window.fn();
// 2. 对象的方法 this指向的是对象 o
var o = {
sayHi: function() {
console.log('对象方法的this:' + this);
}
}
o.sayHi();
// 3. 构造函数 this 指向 ldh 这个实例对象 原型对象里面的this 指向的也是 ldh这个实例对象
function Star() {};
Star.prototype.sing = function() {
}
var ldh = new Star();
// 4. 绑定事件函数 this 指向的是函数的调用者 btn这个按钮对象
var btn = document.querySelector('button');
btn.onclick = function() {
console.log('绑定时间函数的this:' + this);
};
// 5. 定时器函数 this 指向的也是window
window.setTimeout(function() {
console.log('定时器的this:' + this);
}, 1000);
// 6. 立即执行函数 this还是指向window
(function() {
console.log('立即执行函数的this' + this);
})();
改变函数的this指向
bind() call() apply()
call():
apply()
打印的就是字符串,不是数组了
注意数组里面加引号就是字符串,不然就是数字型
bind()
bind会绑定函数,但不会调用,也可以改变this指向。它相当于把函数进行了一个拷贝,返回一个新函数。
运用:(重要)
<body>
<!-- 有一个按钮,点击之后禁用,3s后开启 -->
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
<script>
var btns = document.querySelectorAll('button')
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
this.disabled = true;
setTimeout(function () {
this.disabled = false;
}.bind(this), 3000)//这个this在函数外面,指向的是btn
}
}
</script>
</body>
总结
严格模式
IE10以上才能执行
开启严格模式
为整个脚本开启严格模式:
或在写立即执行函数里
为某个函数开启严格模式:
严格模式的变化
变量:1.先声明再复制 2.不能delete
this:全局作用域下的this是undefined
下面这样就是把Star()当普通函数调用了,在普通模式下this指向window,相当于window.sex=‘男’,在严格模式下this指向undefined,this.sex执行会报错。
函数的变化:1.不能有重名的参数
函数必须是声明在顶层,且:
高阶函数
函数可以作为参数传递–如callback()
或将函数作为返回值输出–看闭包。
闭包(重要)
闭包的定义
闭包是有权访问另一个函数作用域中变量的函数。
被访问的变量所在的函数叫做闭包。
闭包的主要作用是延伸了变量的作用范围
局部变量通常是函数调用完毕就被销毁,但闭包使它不会立刻被销毁。
下面的例子:如何在全局作用域中访问局部作用域的变量。
<body>
<ul class="nav">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
<script>
//闭包应用-利用闭包输出当前li的索引号
var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
(function (i) {
lis[i].onclick = function () {
console.log(i);//此处有闭包产生(立即执行函数),i是立即执行函数里面的变量,在这里被调用了
}
})(i);//把实参i传给立即执行函数的形参i
}
</script>
</body>
(注意为什么不能之间打印i 因为回调函数(绑定了事件的 计时器)这种都属于异步任务,而for循环本来是个同步任务,已经执行到i++直到i=4了.
递归
如果一个函数在内部可以调用其本身,这个函数即递归函数。
死递归会导致栈溢出,必须要加退出条件return。
实例:求斐波那契数列
//1、1、2、3、5、8...
function fb(n) {
if (n == 1 || n == 2) {
return 1;
}
return fb(n - 1) + fb(n - 2)
}
console.log(fb(6));
浅拷贝和深拷贝
浅拷贝是把更深层次对象的地址拷贝,修改浅拷贝后的数据会把原数据也修改了。
下面这样obj的msg.age也会改变
另一种浅拷贝写法:
深拷贝拷贝深层次的对象会开辟新空间。
深拷贝方法:我们给新拷贝好的对象添加属性后原来的对象是不会改变的。
<script>
//深拷贝
var obj = {
id: 1,
name: 'hu',
goods: {
color: 'pink',
price: 10000
},
arr: [1, 2, 3]
}
var o = {};
function getCopy(newObj, oldObj) {
for (var k in oldObj) {
var item = oldObj[k];
if (item instanceof Array) {//先判断数组,因为数组也属于对象,需要先筛出去
newObj[k] = [];
getCopy(newObj[k], item)
} else if (item instanceof Object) {
newObj[k] = {};
getCopy(newObj[k], item)
} else {
newObj[k] = item;
}
}
}
getCopy(o, obj)
正则表达式
检测是否符合正则表达式要求规范
// 1.利用RegExp创建
// var regexp=new RegExp();
// 2.利用字面量创建
var rg = /123/;
console.log(rg.test(123));//true
console.log(rg.test('abc'));//false
/^abc$/
表示只能是abc
/^ [abc] $/ 表示三选一 a或者b或者c才可(aa 这种不可)
任意一个小写字母都是true:
/ ^ [a-z] $ /
下面的意思是小写大写字母数字下划线短横线多选一
如果中括号里面有^表示取反,就是不能包含的意思
量词:{x,y}大于等于3小于等于y
量词中间不要有空格
括号总结
下面只有最后一个true
让abc重复三次
预定义类(元字符)
正则表达式中的替换
全局匹配就可以匹配所有的符合的表达式,不然只能匹配第一个
ES6新增语法
let
let声明的变量只在所处的块级有效。
防止内层变量覆盖外层变量。
Var不具有这个属性:下面num为undefined abc=200
let可以防止循环变量变成全局变量,这样在for循环外面就不能访问到。
let关键字声明的变量没有变量提升
使用let关键字声明的变量有暂时性死区特性
let/const 命令会使区块形成封闭的作用域。若在声明之前使用变量,就会报错。
总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。
这在语法上,称为 “暂时性死区”
经典面试题
const关键字声明常量
1.具有块级作用域2.必须赋值
3.常量值声明后不可更改:
直接赋值的操作就改了内存地址,是不行的。
但对于复杂数据类型可以这样改:
但不能
解释:常量的含义是指向的对象不能修改,但可以修改对象的属性
直接修改obj={}的话相当于修改了obj存储的地址,但修改属性相当于obj存放的地址没有改变,只是改变里原地址里指向的属性的值。
参考:https://www.bilibili.com/video/BV15741177Eh?p=28
增强字面量写法
const name=‘hpy’
const age=16
const obj={
name,age
}
就直接以name为属性hpy为值了,age为属性16为值了。
同时function可以直接写,函数名(),如
run(){
}
解构赋值
注意下面真正的变量是myName,就是把’zhangsan’赋给myName…
箭头函数
箭头函数是用来简化函数定义语法的
// 在箭头函数中如果函数体只有一句代码且执行结果就是函数返回值,此时可以省略{}和return
const sum = (num1, num2) => num1 + num2
console.log(sum(1, 2));
// 如果形参只有一个,形参外侧的小括号也可以省略
const v = a => {
alert(a)
}
v(23)
箭头函数的this指向
箭头函数内部不绑定this,它里面的this和上下文的this指向一致
function fn() {
console.log(this);
return () => {
console.log(this);
}
}
const obj = { name: 'zhangsan' }
const v = fn.call(obj)
v();
注意对象是不能产生作用域的,下面这样写this指向window 弹出100
注意:下面这样写的话this指向的是obj
var obj = {
age: 10,
say: function () {
alert(this.age)
}
}
obj.say()
剩余参数
…args是一个数组,可以存放剩下的实参
const sum = (...args) => {
let total = 0;
args.forEach(item => total += item);
return total;
}
console.log(sum(10, 20, 30));
console.log(sum(10, 20));
ES6的内置对象扩展
扩展运算符
逗号只是参数分隔符不会被输出
用于数组合并
将伪数组转为真正的数组
这样就可以调用数组的方法,如push…1
复制对象
展开运算符是不能用于对象的,但可以在构造字面量对象时使用:可以将已有对象的可枚举属性拷贝到新构造的对象中,用于浅拷贝和对象合并。
var obj1={foo:'bar',x:1};
var obj1={foo:'baz',y:2};
var obj={...obj1};
//克隆{foo:'bar',x:1}
var mergeobj={...obj1,...obj2};
//合并{foo:'bar',x:1,y:2};
Array的扩展方法
Array.from()
可以把伪数组(对象…)转为真正的数组
find()方法
find返回的是item
findIndex()
includes()
模板字符串
使用``声明的字符串叫做模板字符串
let name = `zhangsan`
console.log(name);
//模板字符串拼接字符串:
let name = `zhangsan`
let sayHello = `我的名字叫${name}`
模板字符串可以换行,就是换行会显示出来;
调用函数:会显示函数的返回值
String的扩展方法
是否以某个字符串开头或结尾,返回布尔值。
Set数据结构
const s1 = new Set(['a', 'b']);
console.log(s1.size);//2
const s2 = new Set(['a', 'b', 'a', 'b']);
console.log(s2.size);//2
const arr = [...s2]
console.log(arr);//['a','b']
柯里化
// 要执行add(1)(2)(3)=6或add(1)(2,3,4)(5)=15
function add() {
let args = [].slice.call(arguments)//把arguments类数组转为数组
const inner = function () {
args.push(...arguments)//把新的arguments push进刚刚的数组,这里还有闭包概念
return inner//递归执行
}
inner.toString = function () {//inner有个隐式的toString的func会执行,返回return的东西,reduce会遍历数组前后两个数
return args.reduce(function (a, b) {//
return a + b
})
}
return inner//在add里会调用inner
}
console.log(add(1)(2)(3));
console.log(add(1, 2)(2, 3, 4)(5));
asnyc和defer的区别
1、defer 和 async 在网络读取(脚本下载)这块儿是一样的,都是异步的(相较于 HTML 解析)
2、两者的差别:在于脚本下载完之后何时执行,显然 defer 是最接近我们对于应用脚本加载和执行的要求的。defer是立即下载但延迟执行,加载后续文档元素的过程将和脚本的加载并行进行(异步),但是脚本的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。async是立即下载并执行,加载和渲染后续文档元素的过程将和js脚本的加载与执行并行进行(异步)。
3、关于 defer,我们还要记住的是它是按照加载顺序执行脚本的
4、标记为async的脚本并不保证按照指定它们的先后顺序执行。对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行。
5、async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的。