上一篇JavaScript高级学习(三)主要学习了原型链和继承,除此以外还夹杂了一些其他知识,例如this指向问题,这一篇我们主要学习apply、call、bind方法,以及闭包等知识。
apply()、call()、bind()
1.apply和call
function f1(x,y){
console.log((x+y)+"====>"+this);
}
f1(10,20);
//此时的f1实际上被当成对象来使用
f1.apply();
f1.call();
//apply和call没有传参数
call和apply方法也是函数调用的方式。
apply和call方法中如果没有传入参数,或者传入的是null,那么调用该方法的函数对象中的this就是函数本身指向的this。
apply(thisArg,[array]);
call(thisArg,args);
apply和call方法都可以让函数或者方法来调用,传入参数和函数自己调用的写法不一样,但是效果是一样的。
例子:
function f1(x,y){
console.log((x+y)+"====>"+this);
}
f1(10,20);
var obj={
age:10,
sex:"男"
};
f1.apply(obj,[20,5]);
f1.call(obj,20,6);
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都可以改变this的指向
- 作用 :函数调用、改变this指向
- 使用方法
方法/函数名字.apply(对象,[参数1,参数2...]);
方法/函数名字.call(对象,参数1,参数2...);
- 异同:
同:都可以改变this指向 ,调用函数
异:参数传递方式不一样 - 只要是想使用别的对象的方法,并且希望这个方法是当前对象的,那么就可以使用apply和call方法改变this的指向
- apply和call方法实际上并不在函数这个实例对象中,而是在Function的prototype中
function f1() {
console.log(this+":====>调用了");
}
console.dir(f1);
//对象调用方法,说明,该对象中有这个方法
f1.apply();
f1.call();
console.log(f1.__proto__==Function.prototype);
//所有的函数都是Function的实例对象
console.dir(Function);
2.bind
bind(thisArg,args);//返回值为函数
function f1(x, y) {
console.log((x + y) + ":=====>" + this);
}
var ff=f1.bind(null);
ff(10,20);//30:=====>[object Window]
bind复制,复制一份的时候,把参数传入到了f1函数中,x===>10,y===>20,null就是this,默认就是window
bind方法是复制的意思,参数可以在复制的时候传进去,也可以在复制之后调用的时候传入进去
apply和call是调用的时候改变this指向
bind方法,是复制一份的时候,改变了this的指向
方法/函数名字.bind(对象,参数1,参数2,...);---->返回值是复制之后的这个函数
函数中的几个成员
函数中有的属性:
- name:函数的名字,name是只读的,不能修改
- arguments:实参的个数
- length:函数定义的形参个数
- caller:调用
function f1(x,y){
console.log(f1.name);
console.log(f1.arguments.length);
console.log(f1.length);
console.log(f1.caller);//调用者
}
f1(10,20,10,6);//f1 4 2 null
function f2() {
f1(1,2);
}
f2();//f1 2 2 f2
函数作为参数、函数作为返回值
1.函数作为参数
function f1(fn){
fn();
}
//fn是参数,作为函数使用了,函数可以为参数
- 传入匿名函数
function f1(fn){
fn();
}
f1(function () {
console.log("我是匿名函数");
});
- 传入命名函数
函数作为参数,如果是命名函数,那么只传入命名函数的名字,不加括号
function f1(fn){
fn();
}
function f2() {
console.log("f2的函数");
}
f1(f2);//f2的函数
- 定时器中传入函数
function f1(fn) {
setInterval(function () {
console.log("定时器开始");
fn();
console.log("定时器结束");
},1000);
}
f1(function () {
console.log("好困啊,好累啊,就是想睡觉");
});
2.函数作为返回值
function f1() {
console.log("f1函数开始");
return function () {
console.log("我是函数,但是此时是作为返回值使用的");
}
}
var ff=f1();
ff();
例子:
判断某个对象类型是不是传入的类型
补充知识点:
var num=10;
console.log(typeof num);//获取num这个变量的数据类型
var obj={};
console,log(obj instanceof Object);//true
//输出Object的数据类型
console.log(Object.prototype.toString());//[object Object]
//输出数组的数据类型
console.log(Object.prototype.toString.call([]));//[object Array]
想要获取某个对象的数据类型
Object.prototype.toString.call(对象);
function getFunc(type){
return function (obj){
return Object.prototype.String.call(obj)===type;
}
}
var ff=getFunc("[object Array]");
var result=ff([10,20,30]);
console.log(result);///true
3.排序
排序—函数作为参数使用,匿名函数作为sort方法的参数使用
var arr = [1, 100, 20, 200, 40, 50, 120, 10];
//排序
arr.sort();
console.log(arr);//[1,10,100,120,20,200,40,50]
sort()不是稳定的排序方法
var arr = [1, 100, 20, 200, 40, 50, 120, 10];
arr.sort(function (obj1,obj2) {
if(obj1>obj2){
return -1;
}else if(obj1==obj2){
return 0;
}else{
return 1;
}
});
console.log(arr);//[200,120,100,50,40,20,10,1]
var arr1=["acdef","abcd","bcedf","bced"];
arr1.sort(function (a,b) {
if(a>b){
return 1;
}else if(a==b){
return 0;
}else{
return -1;
}
});
console.log(arr1);//["abcd","acdef","bced","bcedf"]
闭包
1.作用域,作用域链
变量:
- 局部变量
- 全局变量
作用域:变量的使用范围
- 局部作用域
- 全局作用域
JS中没有块级作用域-------->块级作用域: 任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的
//全局变量
while(true){
var num=10;
break;
}
console.log(num);//10
{
var num2=100;
}
console.log(num2);//100
if(true){
var num3=1000;
}
console.log(num3);//1000
//局部变量
function f1() {
var num=10;
}
console.log(num);//error
作用域链:变量的使用,从里到外,层层搜索,搜索到了就可以直接使用
var num=10; //作用域链 级别:0
function f1() {
var num=20;//作用域链 级别:1
function f2() {
var num3=30;//作用域链 级别:2
console.log(num);//30
}
f2();
}
f1();
层层搜索,搜索到0级作用域的时候,如果还是没有找到这个变量,结果就是报错
2.预解析
就是在浏览器解析代码之前,把变量和函数的声明提前(提升)到该作用于的最上面
//变量的提升
//var num;
console.log(num);//undefined
var num=100;
//函数的声明被提前了
f1();//这个函数,执行了
function f1() {
console.log("这个函数,执行了");
}
//命名函数
//var f2;声明提前,f2相当于undefined,undefined+()不正确
f2();
f2=function () {
console.log("小杨好帅哦");
};//error
闭包
- 概念:函数A中,有一个函数B,函数B中可以访问函数A中定义的变量或者是数据,此时形成闭包。(不严谨)
- 模式:函数模式的闭包、对象模式的闭包
- 作用:缓存数据,延长作用域链
- 优缺点:缓存数据
1.模式
函数模式的闭包:在一个函数中有另一个函数
//例1
function f1(){
var num=10;
function f2(){
console.log(num);
}
f2();
}
f1();//10
//例2
function f1(){
var num=10;
return function(){
console.log(num);
}
}
var ff=f1();
ff();//10
对象模式的闭包:函数中有一个对象,对象访问外层函数定义的变量或数据
//例1
function f1(){
var num=10;
var obj={
age:num,
sex:"男"
};
console.log(obj.num);
}
f1();//10
//例2
function f1(){
var num=10;
return {
age:num,
sex:"男"
}
}
var ff=f1();
console.log(ff.age);//10
2.作用
闭包的作用是:缓存数据,延长作用域链
以下例子显示必闭包的作用
//普通函数
function f1(){
var num=10;
num++;
console.log(num);
}
f1();//11
f1();//11
f1();//11
//闭包函数
function f1(){
var num=10;
return function(){
num++;
return num;
}
}
var ff=f1();
console.log(ff());//11
console.log(ff());//12
console.log(ff());//13
以上例子显示了闭包的作用——缓存数据
普通函数多次调用时,num总重新赋值(var num=10)
闭包函数中,ff函数中没有重新赋值,因为数据缓存,在之前的num基础上+1
3.案例
闭包:产生多个相同的随机数
function f1(){
var num=parseInt(Math.random()*10+1;//1-10随机数
return function(){
console.log(num);
}
}
var ff=f1();
ff();
ff();
ff();
//3个结果相同
4.总结
如果想要缓存数据,就把这个数据放在外层的函数和里层的函数的中间位置。
闭包作用:缓存数据,优点也是缺点,不能及时释放
局部变量是在函数中,函数使用结束后,局部变量就会自动释放
闭包中,里面的局部变量的使用作用域链会延长
沙箱
沙箱:环境,黑盒,虚拟的环境中,在一个虚拟的环境中模拟真实世界,做实验,实验结果和真实世界结果是一样的,但是不会影响真实世界。
var num=10;
console.log(num+10);//20
//沙箱---小环境
(function () {
var num=10;
console.log(num);//10
})();
//沙箱---小环境
(function () {
var num=20;
console.log(num+10);//30
}());
var num=100;
(function () {
var num=10;
console.log(num);//10
}());
console.log(num);//100
递归
函数中调用函数自己,就是递归。递归函数中,一定要有结束条件
案例
求n个数字的累加和
//函数的声明
function getSum(x) {
if(x==1){
return 1;
}
return x+getSum(x-1);
}
//函数的调用
console.log(getSum(5));//15
求一个数字各个位数上的数字之和
function getEverySum(x) {
if(x<10){
return x;
}
//获取的是这个数字的个位数
return x%10+getEverySum(parseInt(x/10));
}
console.log(getEverySum(1364));//14
浅拷贝和深拷贝
在之前学习继承的时候,大概的看了以下浅拷贝,现在继续学习浅拷贝和深拷贝。
1.浅拷贝
拷贝就是复制,相当于 把一个对象中所有的内容,都复制一份给另一个对象,直接复制,或者说,就是把一个对象的地址给了另一个对象,他们的指向相同,两个对象之间有共同的属性或者方法都可以使用。
var obj1={
age:10,
sex:"男"
}
var obj2={};
function extend(a,b){
for(var key in a){
b[key]=a[key];
}
}
extend(obj1,obj2);
2.深拷贝
把一个对象中所有的属性或者方法,一个一个的找到,并且在另一个对象中开辟相应的空间,一个一个的存储到另一个对象中
var obj1={
age:10,
sex:"女",
car:["奔驰","宝马","特斯拉","奥拓"],
dog:{
name:"大黄",
age:5,
color:"黑白色"
}
};
var obj2={};
function extend(a,b){
for(var key in a){
var item=a[key];
if(item instanceof object){
b[key]={};
extend(item,b[key]);
}else if(item instanceof Array){
b[key]=[];
extend(item,b[key]);
}else{
b[key]=item;
}
}
}
extend(obj1,obj2);
总结
- apply、call使用和区别
都可以改变this指向(使用其他函数/对象的方法)
方法/函数名字.apply(对象,[属性1,属性2…]);
方法/函数名字.call(对象,属性1,属性2…);
其他对象.方法名.apply(对象,[属性1,属性2…]);
其他对象.方法名.call(对象,属性1,属性2…); - bind方法的使用和区别
复制一个方法或函数,在复制的同时改变了this指向
方法/函数名字.bind(对象,属性1,属性2…);
返回值是复制之后的这个函数 - 高阶函数
函数的使用方式:函数作为参数、函数作为返回值
函数为参的时候:可以直接使用匿名函数,也可以是命名函数 - 作用域和作用域链及预解析
- 闭包
函数中有另一个函数,或者是一个函数中有另一个对象,里面的函数或对象都可以使用外面函数中定义的变量或数据,此时形成闭包。
闭包模式:函数模式的闭包、对象模式的闭包
闭包作用:缓存数据,延长作用域链,同时也是缺点,函数中的变量不能及时的释放。 - 沙箱
就是一个环境,也叫黑盒。在这个环境中模拟外面真实的开发环境,完成需求,效果和真实的开发环境效果相同
优点:避免命名冲突 - 递归
函数中掉调用函数自己,递归一定要有结束条件,否则就是死循环
一般用在遍历上
缺点:效率低