this函数三种指向
环境对象 this : 谁 调用我 我就指向谁
普通函数:函数名() this 指向window
对象方法 对象名.方法名() this指向对象
构造函数 new函数名() this指向new创建实列对象
小技巧:没电没new是window,不是new是实列,有点是点左边的对象
let fn=function(){
console.log(this)
}
//1普通函数
fn()
//构造函数
new fn()
//对象方法
let obj={
name:'张三',
eat:fn
}
obj.eat()
测试
let obj = {
name: "张三",
eat: function() {
//1级链
console.log(this) //1.obj
function fn() {
//2级链
console.log(this) //2.window
}
fn()
}
}
let eat = obj.eat
obj.eat()
函数上下文
上下人调用:修改函数内部的this
2.1 函数名.call(修改后的this,参数1,参数2.....)
2.2函数名 .applay()
2.3函数名.bind()
call()调用函数
let fn=function(a,b){
console.log(this)
console.log(a+b)
}
// 函数名.call(修改的this,参数,参数2...)
fn.call({name:'张三'},10,20)
call() 伪数组转真数组
/* 伪数组 : 有数组三要素(下标,元素,长度),但是没有数组的方法
伪数组本质是对象
*/
let weiArr={
0:88,
1:20,
2:50,
3:60,
length:4
}
console.log(weiArr)
//如果希望伪数组也可以调用数组的方法(排序、拼接),就需要把伪数组转成真数组
/*
1. slice可以查询数组,默认情况下不传参这个方法会得到数组本身
2. 但是伪数组由于原型不是Array,所以无法调用slice
3. slice方法存储在哪里? : Array.prototype
*/
Array.prototype.slice.call(weiArr)
console.log(weiArr)
//ES6新增语法用于伪数组转真数组 : Array.from(伪数组)
let arr=Array.from(weiArr)
console.log(arr)
call() 万能数据类型检测
1. typeof 数据 : 有两种数据类型无法检测
null和数组无法检测,结果都是 'object'
2. 解决方案:万能数据类型检测
Object.prototype.toString.call(数据)
*/
//值类型
let str = 'abc'
let num = 123
let bol = true
let und = undefined
let nul = null
//引用类型
let arr = [10,20,30]
let fn = function(){}
let obj = {name:'张三'}
console.log( typeof str )//'string'
console.log( typeof num )//'number'
console.log( typeof bol )//'boolean'
console.log( typeof und )//'undefined'
console.log( typeof nul )//'object'
console.log( typeof arr )//'object'
console.log( typeof fn )//'function'
console.log( typeof obj )//'object'
/* Object.prototype.toString() 返回固定格式字符串 '[object 数据类型]' */
console.log( Object.prototype.toString.call( str ) )//[object String]
console.log( Object.prototype.toString.call( num ) )//[object Number]
console.log( Object.prototype.toString.call( bol ) )//[object Boolean]
console.log( Object.prototype.toString.call( und ) )//[object Undefined]
console.log( Object.prototype.toString.call( nul ) )//[object Null]
console.log( Object.prototype.toString.call( arr ) )//[object Array]
console.log( Object.prototype.toString.call( fn ) )//[object Function]
console.log( Object.prototype.toString.call( obj ) )//[object Object]
applay()调用函数
function ab(a,b){
console.log(a+b)
console.log(this)
}
ab.apply({name:'李四'},[30,40])
applay 伪数组转数组
let obj={
0:50,
0:30,
0:20,
0:10,
0:20,
length:4
}
console.log(obj)
// 需求 伪数组转真数组
let arr=[]
// 思路一把伪数组每一个元素push到数组中
// arr.push(obj[0],obj[1],obj[2],obj[3])
// for(let i = 0;i<obj.length;i++){
// arr.push(obj[i])
// }
// 思路二 使用 apply arr.push.apply(arr,伪数组)
arr.push.apply(arr,obj)
console.log(arr)
//Es6中提供一个更加简洁的静态方法,把伪数组转成真数组
let arr1=Array.from(obj)
console.log(arr)
applay 求数组最大值
//1js基础 :擂台思想
let arr=[20,30,40,50]
let max=arr[0]
for(let i = 1;i<arr.length;i++){
if(arr[i]>max){
max=arr[i]
}
}
console.log(max)
//2Math.max()
let max1=Math.max(arr[0],arr[1],arr[2],arr[3])
console.log(max1)
//3Math.max.apply(Math,数组/伪数组)
let max2=Math.max.apply(Math,arr)
console.log(max2)
// es6中更加简单的方式可以求数组最大值
// 展开运算符 自动遍历数组传参 (类似apply传参特点)
let max3=Math.max(...arr)
console.log(max3)
bind()调用函数
function fn(a,b){
console.log( a + b )
console.log( this )
}
// 函数名.call(修改后的this,形参1,形参2…………)
fn.call({name:'张三'},10,20)
// 函数名.apply(修改后的this, 数组或伪数组 )
// apply会自动帮你遍历数组,然后按照顺序逐一传参
fn.apply({name:'李四'}, [30,40] )
//函数名.bind(修改后的this)
// bind不会立即执行函数,而是返回一个修改this之后的新函数
let newFn = fn.bind({name:'王五'})
newFn(100,200)
newFn(10,20)
bind() 修改定时器
/*
定时器中的this : 默认指向window ,要想修改继续用Bing
+
*/
// (1)声明函数
let fn=function(){
console.log(this)
}
//(2)修改fnthis指向
let newfn=fn.bind({name:'张三'})
// (3)定时器
setTimeout(newfn,2000)
//这种写法和上面三个步骤等价
// setTimeout(function(){
// console.log(this)
// }.bind({name:'小齐'}),2000)
call,apply,bind区别
/*
1.环境对象 this : 谁'调用'我,我就指向谁
普通函数; 函数名() this指向window
对象方法: 对象名.方法名() this指向对象
构造函数; new 函数名() this指向new创建实例对象
2.上下文调用 : 修改函数内部的this
2.1 函数名.call(修改后的this,形参1,形参2…………)
2.2 函数名.apply(修改后的this, 数组或伪数组 )
2.3 函数名.bind(修改后的this)
* 不会立即执行函数,而是得到一个修改this之后的新函数。
* bind一般用于修改: 定时器函数、事件处理函数
3. call 和 apply 和 bind 三者区别
相同点 : 作用一致,修改函数this指向
不同点 :
传参方式不同 : call是按照顺序传参, apply是数组/伪数组传参
执行机制不同 : call和apply会立即执行函数,而bind不会立即执行而是得到修改this的新函数
*/
闭包
/*
1.闭包closure是什么 :
a. 闭包 是一个 访问其他函数内部变量 的 函数
b. 闭包 = 函数 + 上下文引用
2.闭包作用 : 解决变量污染
3.在浏览器中调试闭包 :
*/
//局部作用域 : 在函数内部声明
function fn() {
let num = 10
// fn1 + 访问num 组合才叫闭包
function fn1() {
console.log(num)
}
fn1()
}
fn()
闭包案列
document.querySelector('.btn').onclick=function(){
//1获取用户搜索框的内容
let txt =document.querySelector('input').value
// 2网路请求:不是立即能出结果,网路请求需要时间
//使用定时器来模式请求
setTimeout(function(){
alert(`${txt}的搜索结果如下:123条`)
},1000)
}
递归
1递归函数 :一个函数 在内部调用自己
递归函数 功能和循环类似(优先用循环,递归少用)
function fn(){
console.log('今天学得很开心')
fn()
}
// fn()
//双函数递归 : 两个函数互相调用
function fn1(){
console.log('哈哈')
fn2()
}
function fn2(){
console.log('呵呵')
fn1()
}
// fn1()
2浅拷贝与深拷贝json实现
1.1浅拷贝 拷贝的是地址 修改的拷贝的数据对原来的数据有影响
1.2深拷贝 :拷贝的数据,修改数据对原来没有影响
let obj = {
name:'张三',
age:20,
sex:'男',
hobby:['吃饭','睡觉','学习']
}
// 浅拷贝: 拷贝地址
let newObj = obj
// 修改拷贝后的数据,原数据也会修改
newObj.name = '李四'
console.log( obj,newObj)
let obj1={
name:'张三',
age:20,
hobby:['吃饭','睡觉','打豆豆']
}
// js 对象转json
let jsonStr=JSON.stringify(obj1)
// json转js
let newObj=JSON.parse(jsonStr)
newObj.name='小齐'
newObj.hobby[0]='游戏'
console.log(obj1,newObj)
浅拷贝与深拷贝 递归实现
2.递归应用:
浅拷贝与深拷贝 :
方式一(推荐) : JSON方式实现
* let newObj = JSON.parse( JSON.stringify( obj ) )
方式二(递归) : 了解
遍历dom树
*/
let obj={
name:'张三',
age:20,
sex:'男',
hobby:['吃饭','睡觉','学习'],
student:{
name:'班长',
score:90
}
}
// 使用递归函数
function kaobei(obj,newObj){
for(let key in obj){
if(obj[key] instanceof Array){
// 声明一个空数组,然后拷贝数据里面的数据
newObj[key]=[]
//递归继续调用拷贝数据
kaobei(obj[key],newObj[key])
}else if(obj[key] instanceof Object){
newObj[key]={}
//递归继续调用拷贝数据
kaobei(obj[key],newObj[key])
} else{
newObj[key]=obj[key]//obj地址拷贝给newobj
}
}
}
// 创建一个空对象,然后深拷贝
let newObj={}
kaobei(obj,newObj)
newObj.name='李四'
newObj.hobby[0]='唱歌'
newObj.student.name='小齐齐'
console.log(obj,newObj)
递归遍历dom书
<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: ["雀巢咖啡"]
}
]
}
]
//封装一个遍历don树函数
function addElement(arr,father){//arr 遍历那个数组 father你要加到那个dom树里面去
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)
}
//arr[i].data 是二级菜单
}
}
addElement(arr,document.querySelector('.menu'))