调用
我,我就指向谁 (与函数的声明无关 取决于函数的调用)
- 全局函数 :函数名( ) ------------------>this指向window
- 对象方法 :对象名.方法名( ) -------> this指向对象
- 构造函数 :new 函数名( )------------>this指向new创建的空对象
分析this指向心得 : this指向取决于函数调用,而函数有三种调用方式。所以this指向也会有三种情况
(1)优先用排除法分析, 如果有new关键字,则指向new创建的对象。 否则不是window就是对象
(2)如何判断this是window还是对象,就看调用是: 函数名() 还是 对象名.方法名()
let obj = { name: 'ikun', sayHi: function () { function fn() { console.log(this); } fn()// 普通函数调用 :函数名() fn调用了this 所以this指向window } } obj.sayHi()//window
let obj1 = { name: 'ikun', sayHi: function () { console.log(this); } } obj1.sayHi() //对象方法调用 :对象名.方法名() 对象obj1调用了this 所以this指向obj1对象
function Fn() { console.log(this); } new Fn()//构造函数调用 :new 函数名() 根据new执行流程/创建一个空对象-this指向这个对象-对象赋值-返回对象 这里的this指向new创建的空对象
共同的特点: this的指向无法动态修改
call()
apply()
bind()
,它们定义在Function构造函数的原型中'123'
,1
,true
String('123')
,Number(1)
,Boolean(true)
可以动态修改函数中的this指向
- 相同之处:都可以修改函数中this指向
- 不同点:
1.传参方式不同 : call是一一传参,apply是数组/伪数组传参
2.执行机制不同 : call、apply会立即执行函数, bind不会立即执行函数
有3种写法,作用都是一样改this,应用场景不同
- call()语法:
函数名.call(this修改后的指向,参数1,参数2,...)
--------------适用于函数原本形参 <= 1- apply()语法:
函数名.apply(this修改之后的指向,伪数组或者数组)
-------------适用于函数原本形参 >= 2- bind()语法:
函数名.bind(this修改后的指向)
bind()语法并不会立即执行函数,而是返回一个修改指向后的新函数,常用于回调函数
如果bind的时候传参,则参数也会绑定,之后无法传递实参
<script> /* 1.函数三种执行方式 : 全局函数 : this指向window 对象方法: this指向对象 构造函数 : this指向new创建的对象 共同的特点: this的指向无法动态修改 2.函数上下文模式 : 2.1 作用: 可以动态修改函数中的this 2.2 语法: 有3种写法,作用都是一样改this,应用场景不同 a. 函数名.call(修改的this,arg1,arg2…………) * 适用于函数原本形参 <= 1 b. 函数名.apply(修改的this,[数组或伪数组]) * 适用于函数原本形参 >= 2 c. 函数名.bind(修改的this,arg1,arg2…………) * 特点:不会立即执行这个函数,而是返回修改this后的新函数 * 适用于不会立即执行的函数 : 事件处理函数,定时器函数 */ // function fn(){ // //三种执行模式this无法动态修改 // //this = {age:18}; // console.log(this); // }; // fn();//this:window /* 上下文模式 */ function fn(a, b) { console.log(this); console.log(a + b); }; //a. 函数名.call(修改的this,arg1,arg2…………) //应用场景: 适用于函数原本形参 <= 1 fn(10, 20);//this:window 30 fn.call({ age: 18 }, 100, 200); //age:18 300 //b. 函数名.apply(修改的this,[数组或伪数组]) //应用场景: 适用于函数原本形参 >=2 fn.apply({ age: 88 }, [20, 30]); //c. 函数名.bind(修改的this) //特点:这个函数不会立即执行,而是返回一个修改this之后的新函数 //应用场景 : 事件处理函数,定时器 let newFn = fn.bind({ name: '坤坤' }); console.log(newFn); newFn(50, 60); //4. 定时器中的this一定是指向window // 定时器:一段代码间隔事件执行 setTimeout(一段代码,间隔时间) //4.1 具名函数 let test = function () { console.log('我是具名函数'); console.log(this); }; let newTest = test.bind({ name: '张三' }) setTimeout(newTest, 3000); //4.2 匿名函数 setTimeout(function () { console.log('我是定时器中的函数'); console.log(this); }.bind({ name: '李四' }), 2000); </script>
/* 1. 函数上下问执行模式 : 动态修改this 注意点 : 修改的this只能是引用类型 2.如果写的是基本数据类型 string,number,boolean : 自定帮我们转成对应的基本装包类型 new String() Boolean() Number() undefined,null : */ function fn() { console.log(this); }; fn.call('str');//String fn.call(1);//Number fn.call(true);//Boolean //如果传的是undefined和null,或者不传。代码不会报错,也不会帮我们修改this,还是原来的window fn.call(undefined);//Window fn.call(null);//Window fn.call();//Window fn.call(window);//Window
1.Object.prototype.toString() 得到固定格式字符串
2.检测数据类型固定格式语法:Object.prototype.toString.call()
得到一个固定格式字符串: [object 数据类型]
/* call场景:数据类型检测 */ //1. typeof 数据 : 检测数据类型 /* typeof有两种数据类型无法检测: null,array 都会得到object */ console.log( typeof '123' )// 'string' console.log( typeof 123 )//'number' console.log( typeof true )//'boolean' console.log( typeof undefined )//'undefined' console.log( typeof null )//'object' console.log( typeof [10,20,30] )//'object' console.log( typeof function(){} )//'function' console.log( typeof {name:'123'} )//'object' //2. Object.prototype.toString() : 得到固定格式字符串 //"[object type]",其中 type 是对象的类型 //检测数据类型固定格式语法: Object.prototype.toString.call(数据) console.log( Object.prototype.toString.call('123') )//[object String] console.log( Object.prototype.toString.call(123) )//[object Number] console.log( Object.prototype.toString.call(true) )//[object Boolean] console.log( Object.prototype.toString.call(undefined) )//[object Undefined] console.log( Object.prototype.toString.call(null) )//[object Null] console.log( Object.prototype.toString.call([10,20,30]) )//[object Array] console.log( Object.prototype.toString.call(function(){}) )//[object Function] console.log( Object.prototype.toString.call({name:'123'}) )//[object Object]
function fn(a,b){ console.log(this) console.log(a+b) } fn(1,2)//普通函数 this->window //(1)函数名.call(修改的this,参数1,参数2,....) fn.call({name:'张三'},10,20) //(2)函数名.apply(修改的this, 数组/伪数组 ) //apply传参的时候会自动的遍历这个数组,然后按照循序逐一传参 a = [30,40][0] b=[30,40][1] fn.apply({name:'李四'},[30,40]) </script>
<script> /* 伪数组: 有数组三要素(下标、元素、长度),但是不能使用数组的方法 伪数组 本质是对象 伪数组不能使用数组方法: 伪数组的原型指向对象的原型,而不是Array的原型 */ let obj = { 0: 10, 1: 20, 2: 30, 3: 40, length: 4,//length 省略不了 } console.log(obj[0]); //将伪数组转成真数组 arr.push 往数组后面新增元素 返回的是数组的长度 let newArr = [] let a = newArr.push(obj[0], obj[1], obj[2], obj[3]) console.log(newArr)//[10, 20, 30, 40] console.log(a)//数组长度 4 // ES5:上下文 let newArr1 = [] newArr1.push.apply(newArr1, obj) //第一个参数:newArr1 本来this就是newArr 这里不需要修改this(相当于this不变) //第二个参数:obj 借助apply特点:自动遍历伪数组/数组,逐一传参(自己省去了for循环) console.log(newArr1); // ES6推荐: Arr.from(伪数组) 伪数组转成真数组 let arr = Array.from(obj) console.log(arr); </script>
<script> //求数组最大值 let arr = [10, 13, 12, 3, 14, 23, 32] // 1.arr.sort() 数组排序 参数:回调函数 固定语法死记硬背 // arr.sort(function (a, b) { // return a - b//从小到大排 // return b - a//从大到小排序 // }) arr.sort(function (a, b) { return b - a }) console.log(arr[0]); //2.擂台思想 let max = 0 for (let i = 0; i < arr.length; i++) { if (arr[i] > max) { max = arr[i] } } console.log(max) // ES5: Math.max.apply(Math, 数组名) //函数名.apply(修改的this,数组/伪数组) //第一个参数 Math: Math调用了方法 this指向的就是Math,这里就不需要修改this(传Math,相当于this不变) //第二个参数 arr :借助apply的特点.自动遍历数组/伪数组 逐一传参 let max3 = Math.max.apply(Math, arr) console.log(max3); //ES6(推荐): let max4 = Math.max(...arr) console.log(max4) </script>
function fn(a,b){ console.log(this) console.log(a+b) } fn(1,2)//普通函数 this->window //(1)函数名.call(修改的this,参数1,参数2,....) fn.call({name:'张三'},10,20) //(2)函数名.apply(修改的this, 数组/伪数组 ) //apply传参的时候会自动的遍历这个数组,然后按照循序逐一传参 a = [30,40][0] b=[30,40][1] fn.apply({name:'李四'},[30,40]) //(3)函数名.bind(修改this) //bind不会立即调用函数,而是得到一个修改this之后的新函数 let newFn = fn.bind({name:'王五'}) newFn(25,66)
bind场景主要是修改 '不需要立即执行的函数
//事件处理函数、定时器函数
//bind场景主要是修改 '不需要立即执行的函数' // 事件处理函数、定时器函数 //1.定时器中的this一定是window let fn = function(){ console.log( this ) } //使用bind修改this let newFn = fn.bind({name:'666'}) // fn() : 调用函数, 运行结果是函数返回值 // fn : 变量取值, 取出fn中存储的堆地址 setTimeout( newFn ,3000) //下面这个写法和上面写法是一样的 : 函数是数据类型,也可以像其他数据一样直接使用语法 setTimeout(function(){ console.log(this) }.bind({name:'干饭'}),3000)
/* 高阶函数: 自调用函数、回调函数、闭包、递归 */
什么是递归函数? : 函数内部调用自己
* 注意点: 需要满足条件才会递归,否则会导致死循环
* 递归函数和循环功能类似
//一个函数递归 // function fn(){ // console.log('哈哈'); // fn(); // }; // fn(); //两个函数递归 // function fn1(){ // console.log('哈哈'); // fn2(); // }; // function fn2(){ // console.log('呵呵'); // fn1(); // }; // fn2(); //需求:写一个函数,打印三次 班长爱坤哥 let i = 1; function fn(){ console.log('好好学习'); i++; if(i <= 3){ fn(); }; //循环实现 // for(let i = 1;i<=3;i++){ // console.log('班长爱坤哥'); // }; }; fn();
2.1 浅拷贝与深拷贝
//声明一个对象 //拷贝: 把对象中存储的数据 拷贝一份赋值给其他对象 let obj = { name:'ikun', age:30, hobby:['讲课','学生','学习'], friend:{ name:'朋友', sex:'男' } } //浅拷贝: 拷贝的是地址 let obj1 = obj //由于浅拷贝,拷贝的是地址。 所以修改拷贝后的数据,原来的数据也会变化 obj1.hobby = '美食' console.log(obj1,obj)
let obj = { name:'ikun', age:30, hobby:['讲课','学生','学习'], friend:{ name:'朋友', sex:'男' } } /* (1)遍历obj,把所有的属性添加给newObj (2)如果obj[key]是引用类型(数组、对象),则不能直接拷贝地址 (2.1)数组:给newObj声明一个空数组,然后遍历obj[key],把里面元素添加给newObj[key] (2.2)对象:给newObj声明一个空对象,然后遍历obj[key],把里面元素添加给newObj[key] (3)如果obj[key]不是引用类型,则直接赋值。结束递归 */ function kaobei(newObj,obj){ for(let key in obj){ //判断 obj[key] 是不是数组类型 if( obj[key] instanceof Array ){ //声明一个空数组,然后继续拷贝数组里面的数据 newObj[key] = [] kaobei(newObj[key],obj[key]) }else if(obj[key] instanceof Object){ //声明一个空对象,然后继续拷贝数组里面的数据 newObj[key] = {} kaobei(newObj[key],obj[key]) }else{ newObj[key] = obj[key] } } } //调用深拷贝函数 let newObj = {} kaobei(newObj,obj) //深拷贝:修改拷贝的数据,对原数据没有影响 newObj.hobby[0] = '111' console.log( newObj,obj)
/* 2.深拷贝有两种实现方式 2.1 推荐使用:json转换 2.2 递归 : */ let obj = { name:'ikun', age:30, hobby:['讲课','学生','学习'], friend:{ name:'朋友', sex:'男' } } //(1)先把js对象转成json格式字符串 : JSON.stringify(js对象) //json在把js转成json格式字符串的时候,底层会自动帮你深拷贝 // let json = JSON.stringify(obj) // console.log( json ) //(2)再把刚才的json字符串,转成js对象 : JSON.parse( json格式 ) // let js = JSON.parse( json ) // js.hobby = '学习' // console.log( js,obj ) //以上两个流程,可以简写成一行代码 let newObj = JSON.parse( JSON.stringify( obj ) ) newObj.friend = '小狗' console.log(newObj,obj)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Document</title> <style> * { padding: 0; margin: 0; } .menu p { width: 100px; border: 3px solid; margin: 5px; } .menu > div p { margin-left: 10px; border-color: red; } .menu > div > div p { margin-left: 20px; border-color: green; } .menu > div > div > div p { margin-left: 30px; border-color: yellow; } </style> </head> <body> <div class="menu"> <!-- <div> <p>第一级菜单</p> <div> <p>第二级菜单</p> <div> <p>第三级菜单</p> </div> </div> </div> --> </div> <script> //服务器返回一个不确定的数据结构,涉及到多重数组嵌套 let arr = [ { type: '电子产品', data: [ { type: '手机', data: ['iPhone手机', '小米手机', '华为手机'] }, { type: '平板', data: ['iPad', '平板小米', '平板华为'] }, { type: '智能手表', data: [] } ] }, { type: '生活家居', data: [ { type: '沙发', data: ['真皮沙发', '布沙发'] }, { type: '椅子', data: ['餐椅', '电脑椅', '办公椅', '休闲椅'] }, { type: '桌子', data: ['办公桌'] } ] }, { type: '零食', data: [ { type: '水果', data: [] }, { type: '咖啡', data: ['雀巢咖啡'] } ] } ] //arr:数据 father:父盒子 function addElement(arr,father){ //遍历数组,生成div>p添加到父元素中 for(let i = 0;i<arr.length;i++){ //(1)创建空标签 let div = document.createElement('div') //(2)设置内容 div.innerHTML = `<p>${arr[i].type || arr[i]}</p>` //(3)添加到父盒子 father.appendChild(div) //如果菜单还有data,说明还有子菜单,则需要继续遍历添加 if( arr[i].data ){ addElement(arr[i].data,div) } } } let menu = document.querySelector('.menu') //调用函数 addElement(arr,menu) </script> </body> </html>
function fn(){ let a = 1 //在fn1函数中, 访问了其他函数fn内部的变量。 fn1 + a 形成闭包 function fn1(){ console.log(a) } fn1() } fn() function test(){ let num = 1 setTimeout( function(){ console.log(num) },2000) } test()
闭包场景:
<script> /* 1.闭包(closure)是什么? 1.1 闭包是一个访问其他函数内部变量的函数 1.2 闭包 = 函数 + 上下文引用 的组合 总结:形成闭包要有两个条件,缺一不可。 (1)函数 (2)访问其他函数内部变量 2.闭包作用 : 变量污染 * 后期闭包出现较多的一般都会在回调函数里面 */ //获取元素 let input = document.querySelector('input') let button = document.querySelector('button') button.onclick = function(){ //有一个变量存储搜索结果 let str = '6666' //模拟网络请求:2秒钟之后获取搜索结果 setTimeout(function(){ alert(`搜索结束,本次搜索结果是${str}`) },2000) } </script>
- 全局函数 :函数名( ) ------------------>this指向window
- 对象方法 :对象名.方法名( ) -------> this指向对象
- 构造函数 :new 函数名( )------------>this指向new创建的空对象
- 相同点:都是修改函数this指向
- 不同点
- 传参方式不同: call用于单个参数,apply用于多个参数
- 执行机制不同: call与apply会立即执行, bind不会立即执行
- call、apply用一次修改一次
- bind;一次修改,终生有效
- 什么是闭包:以下两种回答都可以
- 闭包是一个访问其他函数内部变量的函数
- 闭包是 函数 + 上下文代码组合
- 闭包作用:解决变量污染
- 什么是递归:函数内部调用自己
- 递归场景
- 深拷贝
- 遍历dom树