回顾
* 原型:
* 每个实例对象中都有一个属性__proto__,是原型,浏览器使用的,不标准的属性
* 每个构造函数中都有一个属性prototype,是原型,程序员使用的,
* 面向对象和面向过程都是编程思想
* 面向对象注重的是结果,面向过程注重的是过程
* 面向对象的特性:封装,继承,多态
* 继承:
* 1.通过原型实现继承,改变原型的指向,属性在初始化的时候就已经固定了,如果是多个对象实例化,
那么每个实例对象的属性的值在初始化的时候都是一样的
* 2.借用构造函数继承,不能继承方法
* 3.组合继承,可以解决属性和方法的继承的问题
* 4.拷贝继承,就是把一个对象中的原型中的所有的属性和方法复制一份给另一个对象
* 创建对象的三种方式
* 1. 字面量的方式
* 2. 调用系统的构造函数
* 3. 自定义构造函数
* 原型链:实例对象和原型对象之间的关系,主要是通过__proto__和prototype来联系
* 原型的指向是可以改变的,所以,js中是通过改变原型来实现继承
* 原型的作用:
* 实现数据共享,继承, 都是为了节省内存空间
* 如果属性和方法都需要共享,那么就把属性和方法添加到原型中
* 函数中的this的指向:
* 普通的函数中this是window
* 构造函数中的this,构造函数一般都是创建实例对象使用的,是通过new关键字,构造函数也是函数
* 构造函数中的this是实例对象
* 方法中的this是实例对象
* 原型中的方法中的this是实例对象
* 定时器中的this是window
* 函数是对象,构造函数也是函数,也是对象
* 对象是不是函数呢?不一定
* 对象中有__proto__
* 函数中有prototype
* Math是对象,但不是函数
apply、call方法
apply和call
两方法也是函数调用的方式
作用:可以改变this的指向
apply和call方法中如果没有传入参数,或者是传入的是null,那么调用该方法的函数对象中的this就是默认的window
apply和call都可以让函数或者方法来调用,传入参数和函数自己调用的写法不一样,但是效果是一样的
f1(10,20);//函数的调用
f1.apply(null,[100,200]);
f1.call(null,100,200);
-----------------------------三个表达式效果相同
var result1=f1.apply(null,[10,20]);
var result2=f1.call(null,10,20);
------------------------------------直接用变量接收返回值
===================================================================
//apply和call可以改变this的指向
function Person(age,sex) {
this.age=age;
this.sex=sex;
}
//通过原型添加方法
Person.prototype.sayHi=function (x,y) {
console.log("您好啊:"+this.sex);
return 1000;
};
var per=new Person(10,"男");
per.sayHi();
console.log("==============");
function Student(name,sex) {
this.name=name;
this.sex=sex;
}
var stu=new Student("小明","人妖");
var r1=per.sayHi.apply(stu,[10,20]);
var r2=per.sayHi.call(stu,10,20);
console.log(r1);
console.log(r2);
=================================================================
总结:
//apply和call的使用方法
/*
* apply的使用语法
* 函数名字.apply(对象,[参数1,参数2,...]);
* 方法名字.apply(对象,[参数1,参数2,...]);
* call的使用语法
* 函数名字.call(对象,参数1,参数2,...);
* 方法名字.call(对象,参数1,参数2,...);
*
* 作用:改变this的指向
* 不同的地方:参数传递的方式是不一样的
*
* 只要是想使用别的对象的方法,并且希望这个方法是当前对象的,那么就可以使用apply或者是call的方法改变this的指向
* */
所有的函数都是Function的实例对象--所以函数可以调用apply和call函数
apply和call方法实际上并不在函数的实例对象中,而是在Function的prototype中
实例对象调用方法,方法要么在实例对象中存在,要么在原型对象中存在
bind方法
//bind方法是复制的意思,参数可以在复制的时候传进去,也可以在复制之后调用的时候传入进去
//apply和call是调用的时候改变this指向
//bind方法,是复制一份的时候,改变了this的指向
====================================================
function Person(age) {
this.age=age;
}
Person.prototype.play=function () {
console.log(this+"====>"+this.age);
};
function Student(age) {
this.age=age;
}
var per=new Person(10);
var stu=new Student(20);
//复制了一份
var ff=per.play.bind(stu); //复制时改变this指向
ff(); ------------------->[object Object]====>20
//bind是用来复制一份
//使用的语法:
函数名字.bind(对象,参数1,参数2,...);---->返回值是复制之后的这个函数
方法名字.bind(对象,参数1,参数2,...);---->返回值是复制之后的这个方法
函数中的成员
//函数中有一个name属性----->函数的名字,name属性是只读的,不能修改
//函数中有一个arguments属性--->实参的个数
//函数中有一个length属性---->函数定义的时候形参的个数
//函数中有一个caller属性---->调用(f1函数在f2函数中调用的,所以,此时调用者就是f2)
function f2() {
console.log("f2函数的代码");
f1(1,2);
}
高阶函数
1.函数作为参数
函数作为参数的时候,如果是命名函数,那么只传入命名函数的名字,没有括号
//排序,每个文件都有名字,大小,时间,都可以按照某个属性的值进行排序
//三部电影,电影有名字,大小,上映时间
function File(name, size, time) {
this.name = name;//电影名字
this.size = size;//电影大小
this.time = time;//电影的上映时间
}
var f1 = new File("jack.avi", "400M", "1997-12-12");
var f2 = new File("tom.avi", "200M", "2017-12-12");
var f3 = new File("xiaosu.avi", "800M", "2010-12-12");
var arr = [f1, f2, f3];
function fn(attr) {
//函数作为返回值
return function getSort(obj1, obj2) {
if (obj1[attr] > obj2[attr]) {
return 1;
} else if (obj1[attr] == obj2[attr]) {
return 0;
} else {
return -1;
}
}
}
var ff = fn("name");
//函数作为参数
arr.sort(ff);
for (var i = 0; i < arr.length; i++) {
console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
}
2.定时器中传入函数、
function f1(fn) {
setInterval(function () {
console.log("定时器开始");
fn();
console.log("定时器结束");
},1000);
}
f1(function () {
console.log("好困啊,好累啊,就是想睡觉");
});
3.函数作为返回值
function f1() {
console.log("f1函数开始");
return function () {
console.log("我是函数,但是此时是作为返回值使用的");
}
}
var ff=f1();
ff(); //此时ff是一个函数
闭包
概念:函数A中,有一个函数B,函数B中可以访问函数A中定义的变量或者是数据,此时形成了闭包(这句话暂时不严谨)
作用:缓存数据,延长作用域链
优缺点:缓存数据
模式:函数模式的闭包,对象模式的闭包
应用:
函数模式的闭包:在一个函数中有一个函数
function f1() {
var num=10;
//函数的声明
function f2() {
console.log(num);
}
//函数调用
f2();
}
f1();
对象模式的闭包:函数中有一个对象
function f3() {
var num=10;
var obj={
age:num
};
console.log(obj.age);//10
}
f3();
案例-------------------------------------------------------------
//普通的函数
function f1() {
var num = 10;
num++;
return num;
}
console.log(f1()); //11
console.log(f1()); //11
console.log(f1()); //11
-------------------------------缓存数据
//函数模式的闭包
function f2() {
var num = 10;
return function () {
num++;
return num;
}
}
var ff = f2();
console.log(ff());//11
console.log(ff());//12
console.log(ff());//13
=================================================================
//总结:如果想要缓存数据,就把这个数据放在外层的函数和里层的函数的中间位置
//闭包的作用:缓存数据.优点也是缺陷,没有及时的释放
//局部变量是在函数中,函数使用结束后,局部变量就会被自动的释放
//闭包后,里面的局部变量的使用作用域链就会被延长
案例–闭包实现点赞
<ul>
<li><img src="images/ly.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
<li><img src="images/lyml.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
<li><img src="images/fj.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
<li><img src="images/bd.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
</ul>
<script>
//获取所有的按钮
//根据标签名字获取元素
function my$(tagName) {
return document.getElementsByTagName(tagName);
}
//闭包缓存数据
function getValue() {
var value=2;
return function () {
//每一次点击的时候,都应该改变当前点击按钮的value值
this.value="赞("+(value++)+")";
}
}
//获取所有的按钮
var btnObjs=my$("input");
//循环遍历每个按钮,注册点击事件
for(var i=0;i<btnObjs.length;i++){
//注册事件
btnObjs[i].onclick=getValue();
}
沙箱–环境–黑盒—自调用函数
在一个虚拟的环境中模拟真实世界,做实验,实验结果和真实世界的结果是一样,但是不会影响真实世界
//沙箱---小环境
(function () {
var num=10;
console.log(num);
})();
-----------------------------------------------------------
//沙箱---小环境
(function () {
var num=20;
console.log(num+10);
}());
-----------------------------------------------------------
var num=100;
(function () {
var num=10;
console.log(num); //10
}());
console.log(num); //100
案例-------------------------------------
1.
<div>这是div</div>
<div>这是div</div>
<div>这是div</div>
<p>这是p</p>
<p>这是p</p>
<p>这是p</p>
<script>
var getTag = 10;
var dvObjs = 20;
var pObjs = 30;
//------------------------沙箱,变量名和函数名相同冲突解除
(function () {
//根据标签名字获取元素
function getTag(tagName) {
return document.getElementsByTagName(tagName)
}
//获取所有的div
var dvObjs = getTag("div");
for (var i = 0; i < dvObjs.length; i++) {
dvObjs[i].style.border = "2px solid pink";
}
//获取所有的p
var pObjs = getTag("p");
for (var i = 0; i < pObjs.length; i++) {
pObjs[i].style.border = "2px solid pink";
}
}());
console.log(getTag); //10
console.log(dvObjs); //20
console.log(pObjs); //30
递归
函数中调用函数自己,此时就是递归,递归一定要有结束的条件!!!
var i = 0;
function f1() {
i++;
if (i < 5) {
f1();
}
console.log("从前有个山,山里有个庙,庙里有个和尚给小和尚讲故事:");
}
案例---------------------------------------------------------------
1. 递归实现:求n个数字的和 n=5---> 5+4+3+2+1
函数的声明
function getSum(x) {
if(x==1){
return 1;
}
return x+getSum(x-1);
}
//函数的调用
console.log(getSum(5));
==============================
* 执行过程:
* 代码执行getSum(5)--->进入函数,此时的x是5,执行的是5+getSum(4),此时代码等待
* 此时5+getSum(4),代码先不进行计算,先执行getSum(4),进入函数,执行的是4+getSum(3),等待, 先执行的是getSum(3),进入函数,执行3+getSum(2),等待,先执行getSum(2),进入函数,执行 2+getSum(1);等待, 先执行getSum(1),执行的是x==1的判断,return 1,所以,
* 此时getSum(1)的结果是1,开始向外走出去
* 2+getSum(1) 此时的结果是:2+1
* 执行:
* getSum(2)---->2+1
* 3+getSum(2) 此时的结果是3+2+1
* 4+getSum(3) 此时的结果是4+3+2+1
* 5+getSum(4) 此时的结果是5+4+3+2+1
*
* 结果:15
2.//递归案例:求一个数字各个位数上的数字的和: 123 --->6 ---1+2+3
//523
function getEverySum(x) {
if(x<10){
return x;
}
//获取的是这个数字的个位数
return x%10+getEverySum(parseInt(x/10));
}
console.log(getEverySum(1364));//5
3. //递归案例:求斐波那契数列
function getFib(x) {
if(x==1||x==2){
return 1
}
return getFib(x-1)+getFib(x-2);
}
console.log(getFib(12));