面向对象编程介绍
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了
面向对象就是把事务分解成为一个个对象,然后由对象之间分工与合作。
面向对象是以对象功能来划分问题,而不是步骤
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性
- 封装性
- 继承性
- 多态性
面向对象与面向过程的比较
面向过程
- 优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编译‘
- 缺点:没有面向对象易维护、易复用、易扩展
面向对象
- 优点:易维护、易使用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于 维护
- 缺点:性能比面向过程低
类
类的本质
类的本事是一个函数 function 我们也可以简单地认为类就是构造函数地另一种写法
-
类有原型对象prototype
-
类也有一个原型对象prototype 里面有constructor 指向类本身
-
类可以通过原型对象添加方法
-
Star.prototype.sing = function(){};
ES6的本质是语法糖
类和面向对象
面向对象的思维特点
- 抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)
- 对类进行实例化获取类的对象
对象
对象是由属性和方法构成的
- 属性:事物的特征,在对象中用属性来表示 (常用名词)
- 方法:事物的行为,在对象中用方法来表示(常用动词)
类 class
类 抽象了对象的公共部分,它泛指某一大类
对象 特指某一个,通过类实例化一个具体的对象
在ES6 中创建类
语法
class name{
//class body
}
创建实例
let xx = new name();
注意:类必须使用new实例化对象
// 1、创建类 class 创建一个 明星类
class Star {
constructor(uname,age) {
this.uname = uname;
this.age = age;
}
}
// 2、利用类创建对象 new
let ldh = new Star("刘德华",20);
let zxy = new Star('张学友',60);
console.log(ldh.uname); //刘德华
console.log(zxy.uname); //张学友
类constructor 构造函数
constructor()方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过new命令生成对象实例时,自动调用该方法。如果没有显示定义,类内部会自动给我们创建一个constructor()
创建类的注意事项
- 通过class 关键字创建类,类名我们还是习惯性定义首字母大写
- 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
- constructor 函数 只要 new 生成实例时,就会自动调用这个函数,如果我们不写这个函数,类也会自动生成这个函数。
- 生成实例 new 不能忽略
- 最后注意语法规范,创建类 类名后面不要加小括号,生成实例 类型后面加大括号,构造函数不需要加function
类中添加共有的方法
- 我们类里面所有的函数不需要写function
- 多个函数方法之间不需要用逗号分隔
<script>
// 1、创建类 class 创建一个 明星类
class Star {
constructor(uname, age) {
this.uname = uname;
this.age = age;
}
sing(song) {
console.log(this.uname + "唱" + song);
}
}
// 2、利用类创建对象 new
let ldh = new Star("刘德华", 20);
let zxy = new Star("张学友", 60);
console.log(ldh.uname); //刘德华
console.log(zxy.uname); //张学友
ldh.sing("冰雨");
zxy.sing("李香兰");
</script>
类的继承
// 1、类的继承
class Father {
constructor() {}
money() {
console.log(100);
}
}
class Son extends Father {}
let son = new Son();
son.money();//100 son.money();//100
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
class Father{
constructor(x,y){
this.x = x;
this.y = y;
}
sum(){
console.log(this.x+this,y);
}
}
class Son{
constructor(x,y){
//this.x = x;
//this.y = y; 这样子使用是错误的
//调用了父类中的构造函数
super(x,y);
}
}
let son = new Son(100,30);
son.sum();//130
原因是son中的x,y与father是不一样的
super()
super()调用父类普通函数以及继承中属性方法查找原则 就近原则
// super
class Father{
say(){
console.log("我是你爹");
}
}
// class Son{
// say(){
// console.log('我是你儿');
// }
// }
// let son =new Son();
// son.say(); //我是你儿
class Son extends Father{
say(){
//console.log('我是你儿');
console.log(super.say()) //son.say() --->我是你爹
}
}
let son =new Son();
son.say(); //我是你儿
- 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
- 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
super必须放到子类this之前
下面的代码讲的是保证继承父类属性的同时追加子类自身属性。
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Son extends Father {
constructor(x, y) {
// 利用super调用父类中的构造函数
super(x, y);
this.x = x;
this.y = y;
//super(x, y); 在this之后报错,应该先调用父类中的构造函数再用this,
}
subtract() {
console.log(this.x - this.y);
}
}
let son = new Son(5, 3);
son.subtract(); //2
注意:子类构造函数使用super,必须放到this前面(必须先调用父类的构造方法,再使用子类构造方法)
类和对象
四个注意点:
- 在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象
- 类里面的共有属性和方法一定要加this使用
- 类里面的this指向问题
- constructor 里面的this指向实例对象,方法里面的this指向这个方法的调用者
constructor 里面的this 指向的是 创建的实例对象
this的指向问题
<button id="btn">按钮</button>
<script>
let that;
let _that;
class Star {
//constructor 里面的this 指向的是 创建的实例对象
constructor(uname, age) {
that = this;
console.log(this);
this.uname = uname;
this.age = age;
// this.sing() //undefined
this.btn = document.getElementById("btn");
this.btn.onclick = this.sing;
}
sing() {
// 这个sing方法里面的this 指向的是btn 这个按钮,因为这个按钮调用了这个函数
console.log(this);
console.log(this.uname); //undefined
console.log(that.uname) //刘德华
}
dance() {
// 这个dance里面的this 指向的是实例对象 ldh 因为ldh 调用了这个函数
_that = this;
console.log(this);
}
}
let ldh = new Star("刘德华");
ldh.dance();//bject { uname: "刘德华", age: undefined, btn: button#btn}
</script>
面向对象tab栏(案例)
思路分析以及布局
动态添加标签栏
功能需求:
- 点击tab栏,可以切换效果
- 点击+号,可以添加tab项和内容项
- 点击x号,可以删除当前的tab项,可以修改里面的文字内容
- 双击tab项文字或者内容项文字,可以修改里面的文字内容
切换功能:
css、内容与标题的相互
添加功能:
- 创建li元素和section元素
- 把这理工元素追加对对应的父元素里面
构造函数和原型
利用构造函数
构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与new一起使用。我们可以把对象中一些公共的事项和方法抽取出来,然后封装在这个函数里面
在js中,使用构造函数时要注意以下两点:
- 构造函数用于创建某一类对象,其首字母要大写。
- 构造函数要和new一起使用才有意义
new在指向时会做四件事情
- 在内存中创建一个新的空对象
- 让this指向这个新的对象
- 执行构造函数里面的代码,给这个新对象添加属性和方法。
- 返回这个新对象(所以构造函数里面不需要return)
<script>
let obj1 = new Object();
let obj2 = {};
// 利用构造函数创建对象
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log("我会唱歌");
};
}
let ldh = new Star("刘德华", 18);
console.log(ldh); //Object { uname: "刘德华", age: 18, sing: sing()}
ldh.sing();//我会唱歌
</script>
构造函数中的属性和方法我们称为成员,成员可以添加
实例成员就是构造函数内部通过this添加成员
实例成员只能通过实例化对象来访问
不可以通过构造函数来访问无奈的实例成员
静态成员 在构造函数本身上添加成员 sex就是静态成员
静态成员只能通过构造函数来访问,不能通过对象来访问
Star.sex="男";
console.log(Star.sex);//男
console.log(ldh.sex); //undefined
构造函数的问题
构造函数方法很好用,都是存在浪费内存的问题。
<script>
// 1、构造函数的函数问题
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log("001");
};
}
let ldh = new Star("刘德华", 18);
let zxy = new Star("张学友", 20);
console.log(ldh.sing === zxy.sing);//false
</script>
构造函数原型 prototype
构造函数通过原型分配的函数是所有对象所共享的
JavaScript规定 每一个构造函数都有一个prototype属性,指向另一个顶一下啊。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
<script>
// 1、构造函数的函数问题
function Star(uname, age) {
this.uname = uname;
this.age = age;
// this.sing = function() {
// console.log("001");
// };
}
Star.prototype.sing = function() {
console.log("001");
};
let ldh = new Star("刘德华", 18);
let zxy = new Star("张学友", 20);
console.log(ldh.sing === zxy.sing); //true
// 2、一般情况下,我们的公共属性定义到构造函数里面,公共的方法我们放在原型对象身上
</script>
constructor 构造函数
对象原型(proto_)和构造函数(prototype)原型对象里面都有一个属性constructor属性,constructor我们称为构造函数,因为它指回构造函数本身。
很多情况下,我们需要手动的利用constructor 这个属性指会 原来的构造函数
如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数。
<script>
// 1、构造函数的函数问题
function Star(uname, age) {
this.uname = uname;
this.age = age;
// this.sing = function() {
// console.log("001");
// };
}
// Star.prototype.sing = function() {
// console.log("001");
// };
// Star.prototype.movie = function(){
// console.log()
// }
Star.prototype = {
contructor: Star,
sing: function() {
console.log("我会唱歌");
},
movie: function() {
console.log("我会演电影!");
},
};
let ldh = new Star("刘德华", 18);
let zxy = new Star("张学友", 20);
// console.log(ldh.sing === zxy.sing); //true
console.log(Star.prototype.contructor);
console.log(ldh.__proto__.contructor);
// 2、一般情况下,我们的公共属性定义到构造函数里面,公共的方法我们放在原型对象身上
</script>
构造函数、实例、原型对象三者之间的关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-orqaCQTE-1642865777061)(C:\Users\26870\Desktop\002.png)]
JavaScript的成员查找机制
- 当访问一个对象的对象属性(包括方法)时,首先查找这个对象自身有没有该属性。
- 如果没有就查找它的原型(也就是_proto__)指向的prototype原型对象)
- 如果没有就查找对象的原型(Object的原型对象)
- 以此类推一直找到Object为止(null)
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log("我会唱歌");
};
Star.prototype.sex = "女";
let ldh = new Star("刘德华", 18);
ldh.sex = "男";
console.log(ldh.sex); //男 就近原则
console.log(Object.prototype); //Object { … }
console.log(ldh); //Object { uname: "刘德华", age: 18, sex: "男" }
console.log(Star.prototype);//Object { sing: sing()
原型对象中的this指向
- 在构造函数中,里面this指向的是对象实例
- 原型对象里面this 指向的是 实例对象
<script>
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
let that;
Star.prototype.sing = function() {
console.log("我会唱歌");
that = this;
};
let ldh = new Star("刘德华", 20);
// 1、在构造函数中,里面this指向的是对象实例 ldh
ldh.sing(); //我会唱歌
console.log(that); //Object { uname: "刘德华", age: 20 }
console.log(that === ldh); //true;
// 2、原型对象里面this 指向的是 实例对象 ldh
</script>
扩展内置对象
可以通过原型对象,对原来的内置对象就像扩展自定义。比如给数组增加自定义求偶数和的功能
// 原型对象的应用 扩展内置对象方法
console.log(Array.prototype);
Array.prototype.sum = function() {
let sum = 0;
for (let i = 0; i < this.this.length; i++) {
sum += this[i];
}
return sum;
};
不能写成下列代码,因为它把prototype全部覆盖掉了
Array.prototype={
sum:function(){
let sum = 0;
for (let i = 0; i < this.this.length; i++) {
sum += this[i];
}
return sum;
}
}
构造函数地特点
- 构造函数有原型对象prototype
- 构造函数原型对象prototype里面有constructor 指向构造函数本身
- 构造函数可以通过原型对象添加方法
- 构造函数创建地实例对象有_ _ proto_ __原型指向 构造函数地原型对象
继承
ES6之前并没有给我们提供extends继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承
call()
掉用这个函数,并且修改函数运行时的this指向
fun.call(this.Arg,arg1,arg2,.....)
- thisArg:当前调用含函数this的指向对象
- arg1、arg2:传递的其他参数
// call方法
function Fu(x, y) {
console.log("001");
console.log(this); //window
console.log(x + y); //
}
let o = {
name: "ludan",
};
// Fu(); //001 window NaN
// 1、call()可以调用函数
// Fu.call(); //001 window NaN
// 2、call()可以改变这个函数的this的指向 此时这个函数的this 就指向了o这个对象
Fu.call(o, 1, 2); //001 Object { name: "ludan" } 3
利用构造寒素继承父类属性
核心原理:就是通过call()把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性。
谁调this指向谁
// 借用父构造函数继承属性
// 1、父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 2、子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
let son = new Son("刘德华", 20, 100);
console.log(son); //Object { uname: "刘德华", age: 20 ,score:100}
利用原型对象继承方法
// 借用父构造函数继承属性
// 1、父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function() {
console.log("1000000");
};
// 2、子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用 constructor 指回原来的构造函数
Son.prototype.constructor = Son;
//子构造函数专门的方法
Son.prototype.exam = function() {
console.log("孩子不想学习");
};
//这样子覆盖了
// Son.prototype = Father.prototype; //如果修改了子原型对象,父原型对象也会发生改变
let son = new Son("刘德华", 20, 100);
console.log(son); //Object { uname: "刘德华", age: 20 ,score:100}
son.money(); //1000000
console.log(Son.prototype.constructor); //function Son(uname, age, score)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-17w8pi2s-1642865777062)(C:\Users\26870\Desktop\003.png)]
ES5新增的方法
数组方法
迭代(遍历)方法:forEach()、map()、filter()、some()、every();
forEach()
array.forEach(function(currentValue,index,arr))
参数:
- currentValue:数组当前项的值
- index:数组当前项的索引
- arr:数组对象本身
let arr = [1, 2, 3];
let sum = 0;
arr.forEach(function(value, index, array) {
console.log("每个数组元素" + value);
console.log("每个数组元素的索引号" + index);
console.log("数组本身" + array);
sum += value;
});
console.log(sum);
filter() 筛选数组
array.filter(function(currentValue,index,arr))
-
filter()方法创建一个新的数组,新数组中的呀u盛怒是通过检查指定数组中符合条件的所有元素,主要用于筛选数组
-
注意它直接返回一个新数组
参数:
- currentValue:数组当前项的值
- index:数组当前项的索引
- arr:数组对象本身
<script>
let arr = [12, 36, 4, 88];
var newArr = arr.filter(function(value, index) {
return value >= 20;
});
console.log(newArr); //Array [ 36, 88 ]
</script>
</body>
返回的是一个数组
some()
- some()方法用于检测数组中的元素是否满足指定条件,通俗点 查找数组中是否有满足条件的元素
- 注意它返回值是布尔值,如果查找到这个元素,就会返回true,如果查找不到就返回false
- 如果找到一个满足条件的元素,则终止循环,不再继续查找
- 参数:
- currentValue:数组当前项的值
- index:数组当前项的索引
- arr:数组对象本身
let arr = [11, 6, 89, 10];
let flag = arr.some(function(value) {
return value < 3;
});
console.log(flag); //false
let arr1 = ["red", "pink", "blue"];
let flag1 = arr1.some(function(value) {
return value == "pink";
});
console.log(flag1); //true
返回的是布尔值
利用数组新增的案例
<body>
<div class="find" align="center">
<div>
按照价格查询:<input class="start" />——<input type="text" class="end" />
<button id="btn01">搜索</button> 按照商品名称查询:<input type="text" class="name" />
<button id="btn02">查询</button>
</div>
<table align="center" border="1px" width="400px">
<thead>
<th>id</th>
<th>产品名称</th>
<th>价格</th>
</thead>
<tbody align="center"></tbody>
</table>
</div>
<script>
let data = [{
id: 1,
pname: "小米",
prices: 2999,
}, {
id: 2,
pname: "华为",
prices: 4999,
}, {
id: 3,
pname: "oppo",
prices: 3999,
}, {
id: 4,
pname: "小米",
prices: 1999,
}, ];
// 获取相应的元素
let tbody = document.querySelector("tbody");
let btn01 = document.querySelector("#btn01");
let start = document.querySelector(".start");
let end = document.querySelector(".end");
let btn02 = document.querySelector("#btn02");
let name = document.querySelector(".name");
// 把数据渲染到页面中
setData(data);
function setData(myData) {
// 先清空原来的渲染
tbody.innerHTML = "";
myData.forEach(function(value) {
let tr = document.createElement("tr");
tr.innerHTML =
"<td>" +
value.id +
"</td><td>" +
value.pname +
"</td><td>" +
value.prices +
"</td>";
value.prices + "</td>";
tbody.appendChild(tr);
});
}
// 3、根据按钮查询商品
// 当我们点击了按钮,就可以根据我们的商品价格去筛选数组里面的对象
btn01.addEventListener("click", function() {
let newData = data.filter(function(value) {
return value.prices >= start.value && value.prices <= end.value;
});
setData(newData);
});
// 把筛选完之后的对象渲染到页面中
// 4、根据商品名称
btn02.addEventListener("click", function() {
let newData02 = data.filter(function(value) {
return value.pname == name.value;
});
setData(newData02);
});
</script>
</body>
字符串方法
trim()
trim()方法会从一个字符串的两端删除空白字符串
str.trim();
trim()方法并不影响原自发形成本身,它返回的是一个新的字符串
charCodeAt()
该方法可以查看指定码元的字符编码,这个方法返回指定索引位置的码元值,索引以整数指定。
let message = 'acbddf';
console.log(message.charCodeAt(2)); //99
字符串操作方法
concat()
将一个或多个字符串拼接成一个新的字符串
concat()可以一次性接受任意多个参数,因此可以一次性拼接多个字符串
放回值:一个新的字符串
slice()
slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。
stringObject.slice(start,end)
返回值
一个新的字符串。包括字符串 stringObject 从 start 开始(包括 start)到 end 结束(不包括 end)为止的所有字符。
对象方法
Objct.defineProperty()定义对象中新属性或修改原有的属性。
Object.defineProperty(obj,prop,descriptor)
- obj:必需。目标对象
- prop:必需。需定义或修改的属性的名字
- descriptor:必须。目标属性所拥有的特性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PERfmKR0-1642865777063)(C:\Users\26870\Desktop\005.png)]
let obj = {
id: 1,
num: 1000,
price: 1909,
};
Object.defineProperty(obj, "num", {
value: 1000,
});
Object.defineProperty(obj, "id", {
// 如果为false 不允许修改这个属性值
writable: false,
});
删除delete
函数
函数的定义方式
- 函数声明方式 function(关键字)(命名函数)
- 函数的表达式(匿名函数)
- 利用new Function(‘参数1’,‘参数2’,‘函数体’); Function 里面必须是字符串的形式
- Function 执行效率低,也不方便书写,因此较少使用
- 所有函数都是Function的实例(对象)
- 函数也属于对象
// 1、自定义函数(命名函数)
function fu(){}
// 2、函数的表达式(匿名函数)
let fun = function(){}
// 3、利用new Function('参数1','参数2','函数体');
let f = new Function('a','b','console.log(a+b)');
f(1,2);
函数的调用
函数的调用方式
- 普通函数
- 对象的方法
- 构造函数
- 绑定事件函数
- 定时器函数‘
- 立即执行函数
// 1、普通函数 this指向window
function fu(){
console.log(123);
}
fu(); //也可以fu.call()
// 2、对象的方法 this指向o
let o ={
sayHi:function(){
console.log(123);
}
}
o.sayHi();
// 3、构造函数 this指向实例对象 原型对象的this指向的也是实例对象
function Star(){};
new Star();
// 4、绑定事件函数 this指向btn
btn.onclick = function(){}; //点击了按钮就可以调用
// 5、定时器函数 this指向window
setInterval(function(){},1000); //每隔1秒调用该函数
// 6、立即执行函数 this 指向window
(function(){
console.log(123);
})(); //立即执行函数是自动调用
函数中的this指向
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hSoAWC1E-1642865777063)(C:\Users\26870\Desktop\006.png)]
改变函数this的指向
JavaScript为我们专门提供了一些函数方法来帮助我们更优雅的处理函数内部this的指向问题。常用的有bind()、call()、apply()三种
call()
call()方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的this的指向
call主要作用可以实现继承
fun.call(thisArg,arg1,arg2);
let o = {
name: "ludan",
};
function fu(a, b) {
console.log(this);
console.log(a + b);
}
fu.call(o, 1, 2); //Object { name: "ludan" } 3
// call实现继承
function Father(uname, age, sex) {
this.uname = uname;
this.age = age;
this.sex = sex;
}
function Son(uname, age, sex) {
Father.call(Son, uname, age, sex);
}
let son = new Son("刘德华", 18, "男");
console.log(son); //Object { }
apply()
apply()方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的this的指向
fun.apply(thisArg,[argsArray]);
- thisArg:在fun函数运行时指定的this值
- argArray:传递的值,必须包含在数组里面
- 返回值就是函数的返回值,因为它就是调用函数
它的参数必须是数组(伪数组),它的主要作用是可以借助于数学内置对象求最大值
let a = {
name: "ludan",
};
function fu(arr) {
console.log(this);
console.log(arr);
}
fu.apply(o, ["pink"]); //Object { name: "ludan" } pink
// apply的主要作用
// Math.max();
let arr = [1, 66, 3, 99, 4];
let b = Math.max.apply(null, arr);
console.log(b); //99
bind()
bind()方法不会调用函数。但是能改变函数内部this的指向
fun.bind(thisArg,arg1,arg2)
- thisArg:在fun函数运行时指定的this值
- arg1,arg2:传递其他参数
- 返回由指定的this值和初始化改造的原始函数拷贝
返回的是原函数该变this之后产生的新函数
如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时就用bind
<button></button>
<script>
let c = {
name: "ludan",
};
function f(a, b) {
console.log(this);
console.log(a + b);
}
let d = f.bind(c, 1, 2);
d(); //Object { name: "ludan" } 3
// 如果我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮
let btn = document.querySelector("button");
btn.onclick = function() {
// 禁用按钮
this.disabled = true; //这个this 指向btn 这个按钮
setInterval(
function() {
this.disabled = false; //定时器里面的this指向的是window
}.bind(this),3000); //这个this 指向的是btn 这个对象
};
</script>
严格模式
严格模式定义
ES5的严格模式是采用具有局限性
严格模式在IE10以上版本的浏览器才会被支持,旧版本浏览器中会被忽略
严格模式对正常的JavaScript语义做了一些更改:
- 消除了JavaScript语法的一些不合理、不严谨之处,减少了一些怪异行为
- 消除了代码运行的一些不安全之处,保证代码运行的安全
- 提高编译器效率,增加运行速度
- 禁用了在ECMAScript的未来版本中可能回定义的一些语法,为为来新版本的JavaScript做好铺垫。比如保留一些关键字:class、enum、export、extends、import、super 不能做变量名
开启严格模式
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为脚本开启严格模式和为函数开启严格模式两种情况。
1、为脚本开启严格模式
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句“use strict”;(或‘use strict’)
<script>
'use strict';
// 下面的js代码 就会按照严格模式执行代码
</script>
<script>
(function(){
'use strict'
})();
</script>
2、为某个函数开启一个严格模式
<script>
// 此时只是给fn开启一个严格模式
function fn(){
'use strict';
// 下面代码按照严格模式
}
function fun(){
// 里面还是按照普通模式
}
</script>
严格模式中的变化
严格模式对JavaScript的语法和行为,都做了一些改变
1、变量规定
- 在正常模式中,如果严格变量没有声明赋值,默认是全局变量。严格模式禁止这种语法,变量都必须先声明,然后再使用
- 我们不能随意删除已经声明好的变量 如:delete x;是错误的
2、严格模式下this指向问题
- 以前全局作用域函数中的this指向window对象
- 严格模式下全局作用域中函数中的this是undefined
- 以前构造函数不加new也可以调用,当普通函数,this指向全局对象
- 严格模式下,如果 构造函数不加new调用,this会报错
- new 实例化的构造函数指向创建对象实例
- 定时器的this指向还是window
- 事件、对象还是指向调用者
<script>
"use strict";
// 1、变量必须先声明后使用
let num = 10;
console.log(num); //10
// 2、我们不能随意删除已经声明的变量
// delete num;
function fn() {
console.log(this); //undefined;
}
fn();
function Star() {
this.sex = "nan";
}
// Star();
// console.log(window.sex); //this is undefined
let a = new Star();
console.log(a.sex); //nan
</script>
3、函数变化
-
在函数中不能有重名的参数
-
函数必须声明在顶层。最新版的JavaScript 会引入“块级作用域”(ES6中已引入)。为了于新版本接轨,不允许在非函数的代码块内声明函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tqS5utVM-1642865777064)(C:\Users\26870\Desktop\007.png)]
高阶函数
**高阶函数**是对其他函数进行操作的函数,它接收函数作为参数和将函数作为返回值输出
<script>
function fn(callback){
callback&&callback();
}
fn(function(){alert('hi')});
</script>
<script>
function fn(){
return function(){}
}
fn();
</script>
此时fn是一个高阶函数
函数也是一种数据类型,同样可以作为参数,传递给另一个参数使用。最典型的就是作为回调函数
闭包
闭包(closure)指的是有权访问另一个函数作用域中的变量的函数。
简单理解就是,一个作用域可以访问另外一个函数内部的局部变量
<script>
// 闭包:我们fun 这个函数作用域 访问了另外一个函数 fn 里面的局部变量num
function fn() {
let num = 10;
function fun() {
console.log(num);
}
fun();
}
fn(); //10
</script>
变量作用域
变量根据作用域的不同分为两种:全局变量和局部变量
- 函数内部可以使用全局变量
- 函数外部不可以使用局部变量
- 当函数执行完毕,本作用域内局部变量会销毁
闭包的作用
function fn() {
let num = 10;
function fun() {
console.log(num);
}
return fun;
}
let f =fn();
f(); //10
// 类似于
// let f = function fun() {
// console.log(num);
// }
闭包的主要作用是延伸了变量的作用域
立即执行函数也称为小闭包,因为立即执行函数里面的任何一个函数都可以使用它的i这个变量
递归
定义
如果一个函数在内部可以调用其本身,那么就是这个函数就是递归函数
简单理解:函数内部自己调用自己,这个函数就是递归函数
递归函数的作用跟循环效果一样
由于递归任意发生“栈溢出” 错误(stack overflow),所以必须要加退出条件return
let num = 0;
function fn() {
console.log("我要打印这6句话");
if (num == 6) {
return; //递归函数必须加退出条件
}
num++;
fn();
}
fn();
利用递归求n的阶乘
// 利用递归函数求1~n的阶乘
function fn(n) {
if (n == 1) {
return 1;
}
return n * fn(n - 1);
}
console.log(fn(3)); //6
console.log(fn(4)); //24
利用递归函数求菲波那契数列(兔子序列)
<script>
// 利用递归函数求菲波那契数列(兔子序列)1、1、2、3、5、8、13、21...
// 用户输入一个数字n就可以求出 这个数字对应的兔子序列
// 我们只需要知道用户输入的n 的前面两项(n-1 n-2)就可以计算出n 对应的序列值
function fn(n) {
if (n == 1 || n == 2) {
return 1;
}
return fn(n - 1) + fn(n - 2);
}
console.log(fn(3)); //2
console.log(fn(8)); //21
</script>
利用递归遍历数组数据
<script>
let data = [{
id: 1,
name: "家电",
goods: [{
id: 11,
name: "冰箱",
}, {
id: 12,
name: "洗衣机",
}, {
id: 13,
name: "电视机",
}, ],
}, {
id: 2,
name: "服饰",
}, ];
// 我们想要做输入id号就可以返回的数据对象
// 1、利用forEach 去遍历里面的每一个对象
function getID(json, id) {
let o = {};
json.forEach(function(item) {
// console.log(item); //两个数组元素
//Object { id: 1, name: "家电", goods: (3) […] }
// Object { id: 2, name: "服饰" }
// 外层数据
if (item.id == id) {
// console.log(item.name);
o = item;
}
// 里层数据
else if (item.goods && item.goods.length > 0) {
o = getID(item.goods, id);
}
});
return o;
}
console.log(getID(data, 1));
console.log(getID(data, 11));
</script>
浅拷贝和深拷贝
- 浅拷贝只是拷贝一层,更深层次对象级别的只能拷贝引用
- 深拷贝拷贝多层,每一级别的数据都会拷贝
浅拷贝
<script>
// 浅拷贝只是拷贝一层 ,更深层次对象级别的只拷贝引用
// 深拷贝拷贝多层,每一级别的数据都会拷贝
let obj = {
id: 1,
name: "andy",
msg: {
age: 18,
},
};
let o = {};
// for (let k in obj) {
// // k是属性名,obj[k] 属性值
// o[k] = obj[k];
// }
// console.log(o);
// o.msg.age = 20;
// console.log(obj);
console.log("-----------");
Object.assign(o, obj);
console.log(o); //Object { id: 1, name: "andy", msg: {…} }
o.msg.age = 20;
console.log(obj);
</script>
深拷贝
<script>
// 深拷贝和浅拷贝,每一级别的数据就会被拷贝
let obj = {
id:1,
name:'andy',
msg:{
age:18
},
color:['pink','red']
};
let o = {};
// 封装函数
function deepCopy (newobj,lodobj){
for(let k in oldobj){
// 判断我们的属性值属于那种数据类型
// 1、获取我们的属性值
let item = oldobj[k];
// 2、判断这个值是否是数组
if(item instanceof Array){
newobj[k] = [];
deepCopy(newobj[k],item)
}else if(item instanceof Object){
// 3、判断这个值是否是对象
newobj[k]={}
deepCopy(newobj[k],item)
}else{
// 4、属于简单数据类型
newobj[k] = item;
}
}
}
deepCopy(o,obj);
console.log(o);
let arr = [];
console.log(arr instanceof Object); // true
</script>
正则表达式
详细笔记可见js入门