--------------记录自JS高级视频中的学习笔记包括了函数进阶,apply,call,bind方法的使用,闭包,递归,浅拷贝与深拷贝,沙箱.
函数进阶
- 创建函数的两种方式:
- 函数声明:
function f1(){ console.log("函数声明");}f1();复制代码
- 函数声明如果放在if-else语句中,在IE8中会出错(函数声明会被提升)
- 函数表达式:
var f2 = function(){ console.log("函数表达式");};f2(); 复制代码
- 使用函数表达式更好
- 函数声明:
- 函数中的this指向
- 普通模式
- 普通函数中的this---window
- 定时器中的this---window
- 构造函数中的this---实例对象
- 对象.方法中的this---当前的实例对象
- 原型方法中的this---实例对象
- 严格模式: "use strict"
- 普通函数中的this得在window.函数();调用的时候才是window,若函数();则为undefined
- 普通模式
- 函数的不同的调用方式
- 普通函数:
function f1(){}f1();复制代码
- 构造函数---通过new来调用,创建对象
function F1(){}var f = new F1();复制代码
- 对象的方法
function F1(){ this.f1 = function(){};}var f = new F1();f.f1(); 复制代码
- 普通函数:
- 函数也是对象,对象不一定是函数
- 对象中有__proto__原型,是对象
- 函数中有prototype原型,是对象
- 若一个东西里有prototype,又有__proto__,说明是函数也是对象;(Math对象中有__proto__但没有prototype)
- 所有的函数实际上都是Function的构造函数创建出来的实例对象
- 函数中的几个成员介绍
- name属性: 函数的名字,name的属性是只读的,不能修改
- arguments属性: 实参的个数
- length属性: 函数定义的时候形参的个数
- caller属性: 调用者(例如f1函数在f2函数中调用的,此时的调用者就是f2)
- 函数可以作为参数使用
- 函数作为参数的时候,如果是命名函数,那么只传入命名函数的名字,没有括号
- 练习:
var arr = [1, 100, 20, 200, 40, 50, 120, 10]; //排序---函数作为参数使用,匿名函数作为sort方法的参数使用,那么此时的匿名函数中有两个参数, arr.sort(function (obj1,obj2) { if(obj1>obj2){ return -1; }else if(obj1==obj2){ return 0; }else{ return 1; } });//从小到大排序复制代码
- 判断数据类型
- typeof: 返回一个字符串,表示未经计算的操作数的类型
- 对象 instanceof Object: 返回布尔类型,判断对象是否为某个类型
- Object.prototype.toString(): 返回Object的数据类型([object Object])
- Object.prototype.toString().call(对象): 返回的是该对象的数据类型
- 获取某个对象的类型是不是传入的类型
//获取某个对象的类型是不是你传入的类型 //[10,20,30] 是不是"[object Array]" //type---是变量----是参数----"[object Array]" //obj---是变量-----是参数----[10,20,30]; //判断这个对象和传入的类型是不是同一个类型 function getFunc(type) { return function (obj) { return Object.prototype.toString.call(obj) === type; } } var ff = getFunc("[object Array]"); var result = ff([10, 20, 30]); console.log(result);//true var ff1 = getFunc("[object Object]"); var dt = new Date(); var result1 = ff1(dt); console.log(result1);//false复制代码
apply,call与bind的使用
apply,call
- 作用: 都可以改变this的指向
function f1(x,y){ console.log((x + y) + ":===>" + this); } //apply 和 call调用 f1.apply(null,[1,2]);//3:===>[object Window] f1.call(null,1,2);//3:===>[object Window] //改变this的指向 var obj = { }; f1.apply(obj,[1,2]);//3:===>[object Object] f1.call(obj,1,2);//3:===>[object Object]复制代码
- apply的使用语法
- 函数名字.apply(对象,[参数1,参数2,...]);
- 方法名字.apply(对象,[参数1,参数2,...]);
- call的使用语法
- 函数名字.call(对象,参数1,参数2,...);
- 方法名字.call(对象,参数1,参数2,...);
- 不同的地方 : 参数传递的方式不一样
- 只要是想使用别的对象的方法,并且希望这个方法是当前对象的,那么就可以使用apply或者是call的方法改变this的指向
- apply和call方法实际上并不在函数这个实例对象中,而是在Function的prototype中
bind方法
- bing方法是复制的意思,参数可以在复制的时候传进去,也可以在复制之后调用的时候传入进去
function A(age){ this.age = age; } A.prototype.a = function(){ console.log(this + "===>" + this.age); } function B(age){ this.age = age; } var aObj = new A(10); var bObj = new B(20); //复制了一份 var c = aObj.a.bind(bObj); c();//[object Object]===>20复制代码
- 使用语法:
- 函数名字.bind(对象,参数1,参数2,...)--->返回值是复制之后的这个函数
- 方法名字.bind(对象,参数1,参数2,...)--->返回值是复制之后的这个方法
- 应用实例
function ShowRandom(){ //1-10的随机数 this.number = parseInt(Math.random()*10 + 1); } //添加原型方法 ShowRandom.prototype.show1 = function(){ //改变了定时器中的this的指向了,本来应该是window,现在为实例对象 window.setInterval(this.show2.bind(this),1000); }; //添加原型方法 ShowRandom.prototype.show2 = function(){ //显示随机数-- console.log(this.number); }; //实例对象 var sr = new ShowRandom(); //调用方法,输出随机数字 //调用这个方法一次,可以不停的产生随机数字 sr.show1();复制代码
- 使用语法:
作用域和作用域链及预解析
- 变量--->局部变量和全局变量
- 局部变量是在函数中,函数使用结束后,局部变量就会被自动的释放,闭包后,里面的局部变量的使用作用域链被自动的延长
- 作用域:就是变量的作用范围--->局部作用域和全局作用域
- js中没有块级作用域--一对括号中定义的变量,这个变量可以在大括号外面使用,函数中定义的变量是局部变量
- 作用域链:变量的使用,从里向外,层层的搜索,搜索到了就可以直接使用了
- 层层搜索,搜索到0级作用域的时候,如果还是没有找到这个变量,结果就是报错
- 预解析:就是在浏览器解析代码之前,把变量的声明和函数的声明提前(提升)到该作用域的顶部(函数声明优先于变量声明)
闭包
- 闭包的概念:函数A中,有一个函数B,函数B中可以访问函数A中定
- 模式:函数模式的闭包,对象模式的闭包
- 作用:缓存数据,延长作用域链
- 优点和缺点:缓存数据(没有及时的释放 )
- 应用:
//函数模式的闭包 function f1(){ var num = 10; //函数的声明 function f2(){ console.log(num); } //函数调用 f2(); } f1();//10 //对象模式的闭包:函数中有一个对象 function f3(){ var num = 10; var obj = { age : num } console.log(obj.age);//10 }复制代码
- 案例:产生三个相同的随机数
function f1(){ var num = parseInt(Math.random()*10 + 1); return function (){ console.log(num); } } var ff = f1(); ff();//3(随机的) ff();//3 ff();//3复制代码
- 总结:如果想要缓存数据,就把这个数据放在外层的函数和里层的函数的中间位
沙箱
- 沙箱: 环境,黑盒,在一个虚拟的环境中模拟真实世界,做实验,实验结果和真实世界的结果是一样的,但是不会影响真实世界
- 沙箱--小环境
(function (){ var num = 10; console.log(num + 10); }());复制代码
- 小案例
(function (){ var str = "小白喜欢小黑"; str = str.substr(2); console.log(str);//喜欢小黑 }()); (function (){ var str = "小明喜欢小红"; str = str.substr(2); console.log(str);//喜欢小红 }());复制代码
- 总结:放在沙箱内(自调用函数)可以避免全局变量冲突,不会污染外部环境
递归
- 递归: 函数中调用函数自己,此时就是递归,递归一定要有结束的条件,一般应用在遍历上,递归轻易不要用,效率很低
- 案例: 求n个数字的累加和
//递归实现:求n个数字的和 //函数的声明 function getSum(x){ if(x==1){ return 1; } return x + getSum(x - 1); } //函数的调用 console.log(getSum(5));//15 /* * * 执行过程: * 代码执行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 * * * * */复制代码
- 案例: 求一个数字各个位数上的数字的和: 123 --->6
function getEverySum(x){ if(x<10){ return x; } //获取的是这个数的个位数 return x%10 + getEverySum(parseInt(x/10)); } console.log(getEverySum(1364));//14复制代码
- 案例: 求斐波那契数列(第三个数等于前两个数之和 第一第二个数为1)
function getFib(x){ if(x==1 || x==2){ return 1; } return getFib(x-1) + getFib(x-2); } console.log(getFib(12));//144复制代码
浅拷贝和深拷贝
- 浅拷贝: 拷贝结束复制,就相当于把一个对象中的所有的内容,复制一份给另一个对象,直接复制,或者说,就是把一个对象的地址给了另一个对象,他们指向相同,两个对象之间有共同的属性或者方法,都可以使用
//作用:把一个对象的属性复制到另一个对象中,浅拷贝 //把a对象中的所有的属性复制到对象b中 function extend(a,b){ for(var key in a){ b[key] = a[key]; } }复制代码
- 深拷贝: 拷贝函数复制,深: 把一个对象中的所有属性或者方法,一个一个的找到,并且在另一个对象中开辟相应的空间,一个个的存储到另一个对象中
//通过函数实现,把对象a中所有的数据深拷贝到对象b中 function extend(a,b){ for(var key in a){ //先获取a对象中每个属性的值 var item = a[key]; //判断这个值是不是数组 if(item instanceof Array){ //如果是数组,那么在b对象中添加一个新的属性,并且这个属性值也是数组 b[key] = []; //调用这个方法,把a对象中的这个数组的属性值一个个的复制到b对象的这个数组属性中 extend(item,b[key]); }else if(item instanceof Object){//判断在这个值是不是对象类型的 //如果是对象类型的,那么在b对象中添加一个属性,是一个空对象 b[key] = {}; //再次调用这个函数,把a对象中的属性对象的值一个一个复制到b对象的这个属性对象中 extend(item,b[key]); }else{ //如果值是普通的数据,直接复制到b对象整过属性中 b[key] = item; } } }复制代码