JavaScript高级
JS高级
字符串的match方法
字符串.match(正则); //会根据正则提取数据 得到数组 let str="1 plus 2 equal 3"; let a = str.match(/\d+/g); console.log(a); //数组 [1,2,3]
闭包
内部函数访问, 外部函数导致其变量无法销毁
- 变量私有化
- 延长变量声明周期
function fn1 () { let num = 10 return function inner () { console.log(num) } } // res就是返回的那个闭包函数 let res = fn1() res() // 相当于间接的访问了num
函数参数
函数参数默认值
如果不给值,就是undefined
es6以后可以给函数的形参设默认值
function 函数名 (形参1=默认值, 形参2=默认值....) {}
// 函数参数默认值 function foo (a='abc', b=32) { console.log(a) console.log(b) } foo() foo('hello') // hello 32 foo('nb', 16) // nb 16
动态参数
- arguments
-
- 他只能在函数里
- 它是一个伪数组(有下标,有长度,没方法),他保存了所有传递过来的实参
function foot(){ console.log(arguments); } foot(); //伪数组,长度0 foot(10); //伪数组 [10] foot(10,'qwe'); //伪数组 [10,"qwe"]
剩余参数
函数必须传一个,剩余参数可以不传(返回空数组)
function 函数名 (参数1, ...剩余参数名) {}
- 必须把剩余参数写在最后
- 返回 (值 数组)
- 剩余参数是真数组
其他函数
箭头函数
箭头函数也是函数,但只能作为表达式(也就是说给变量赋值,作回调函数,作自执行函数)不能声明
//声明的 function name(){} //表达式 let name = function (){} //箭头函数 let name = (形参列表)=> {}
简写形式
//只有一个参数 不用写() 形参 => {} //参数大于1 必须加()
//函数只有一句话可以省略{} ()=> console.log('giao');
-
省略大括号 一句话就会当成返回值不用写return
-
有大括号 有返回值必须添加return
-
箭头函数没有 arguments(把参数变成伪数组)
-
箭头函数没有重新初始化this
-
就是不改变this指向当前this是什么箭头函数this就是什么
-
<button>hhh</button> <script> let btn = document.querySelector('button'); btn.addEventListener('click',function () { this.innerText = 'giao'; setInterval(function () { console.log(this); //指window 因为默认window.serInterval }, 1000); setInterval(() => { console.log(this); //指btn 因为箭头函数不会重新初始化this }, 1000); }) </script>
赋值结构
方便我们快速取出数组,对象里的值
数组结构
let [变量1,变量2] = 数组
- 变量之间用逗号隔开
//数组只有三个 let [变量*5] = 数组
- 后面两个值是 undefined
//数组五个 let [变量*2] = 数组
- 只取下标 0 下标 2
//数组五个 let [变量 ,...剩余参数] = 数组
- 取一个 后面的值放到 伪数组
//数组五个 let [,,,变量4,变量5] = 数组
- 只取后两个值
演示代码
let nums = [10, 20, 30, 40, 50] // 要把每个元素取出来存到变量,以前需要这样写 // let a = nums[0] // let b = nums[1] // let c = nums[2] // let d = nums[3] // let e = nums[4] // console.log(a, b, c, d, e) // 解构语法:能快速取出数组的值存到变量 // 语法: let [变量名1,变量名2, ....] = 数组 // let [a, b, c, d, e] = nums // 这一句话等于同上面那一段话 // console.log(a, b, c, d, e) // 只声明了两个变量,就只是取下标0和下标1的值 // 也就是说解构时,也不用全部写完,你想取几个就写几个变量 // let [a, b] = nums // console.log(a, b) // 那如果我写的变量数量必数组长度要大呢? // 我现在有声明7个变量,但我数组里一共只有5个元素 // let [a, b, c, d, e, f, g] = nums // // a,b,c,d,e 分别保存了数组下标0到下标4,f和g为undefined,因为取不到值所以是undefined // console.log(a, b, c, d, e, f, g) // 我想取出来2个,但是剩余的全部放到一个数组里 // 取出下标0和下标1,分别给a变量和b变量,剩余的全放到arr里面(所以arr会是一个数组) // let [a, b, ...arr] = nums // console.log(a, b, arr) // 最后一个细节:比如我想取2个,但我只想取下标2和下标3的两个数据 // 能不能用解构?也可以 let [, , num1, num2] = nums console.log(num1, num2) // 30和40
对象结构
语法
let { 属性名: '属性值', 方法名: function(){ console.log('gaio'); } } = 对象 console.log(属性名) console.log(方法名)
应用场景
function fn({name,age}){ console.log(name); console.log(age); } fn({ name: 'giao', age: 18 });
面向过程与面向对象
他们两是一种编程思想
- 面向过程
-
- 侧重于实现功能的每一步
- 面向对象:
-
- 侧重于把每一步封装好,放到一个对象里,以后找的对象,就能调用方法
创建对象与构造函数
//构造函数 一般首字母大写 且要用 new 调用 //构造函数就是一段能帮我们快速创建对象的函数 function Person (name, age, sex) { this.name = name this.age = age this.sex = sex } let p1 = new Person('jack', 16,'男') let p2 = new Person('rose', 15,'女')
访问对象
-
.属性
-
- 对象名.属性名
- 只可以静态访问属性
- 例如:.age 只找age属性
-
[] 传入属性名或字符串
-
-
对象名['属性名'] 对象名[key]
-
可以动态访问属性
-
例如:加引号就是找引号里的属性
不加引号就代表找变量 就看变量里是什么值
-
let p1 = { name:'jack', age: 16 } let attr = 'age' p1.attr // undefined,因为这只能静态访问属性,写什么就访问什么,所以这里访问p1的attr会得到undefined,因为p1里没有attr属性 p1['attr'] // 加了引号代表字符串,那就是找attr属性,所以也是得到undefined p1[attr] // 不加引号,代表动态访问,attr是什么,就访问什么,现在attr是age,所以访问age,age值是16,所以这里得到16
- . 不能传变量 [] 可以传变量
遍历对象
for(let key in 对象){ console.log(key); //遍历属性名 console.log(对象[key]); //遍历属性值 }
new关键字做的事
- 创建出一个空对象
- 把函数里的this指向到这个新对象里
- 在函数结束时返回这个新对象
构造函数做的事
- 构造函数只是封装了 对象属性,方法的代码
- 构造函数本质是普通函数,只不过用处不一样
内置的构造函数
- new Object
- new Array
- new Date
- ......
- 都是内置构造函数,但因为写起来麻烦,所以直接字面量创建即可
静态成员和实例成员
成员:指属性,方法
静态成员:由构造函数直接调用的属性方法
function Person(name, age) { this.name = name; this.age = age; } Person.say = function() { alert("这是静态方法"); }; Person.say //就是静态成员 Person.length //也是静态成员,因为length是函数中的 方法,是来直接获取函数中形参的个数。
实例成员:由构造函数创建出来的属性方法
function Person(name, age) { this.name = name; this.age = age; } var p = new Person("jack", 19); console.log(p.name p.age) //括号内三个就是实例成员了。
实例化:创建对象,也叫实例化对象
引用类型
- 基本数据类型
-
- string
- number
- boolean
- undefined
- null
- 复杂数据类型
-
- 函数
- 数字
- 对象
赋值
- 值类型赋值
-
- 基本数据类型传值都是类型赋值
- 直接给值 值存栈
- 引用类型赋值
-
- 复杂类型之间都是传递地址
- 值存 堆 地址存 栈
- 函数里的形参相当于 声明 let a;
Object.assign Object.keys Object.values
let obj = { name: 'giao', age: 18 } let obj2 = {} //obj赋值给obj2互不影响 //类似于 /* for(let key in obj){ obj2[key] = obj[key]; } */ Object.assign(obj,obj2); //返回所有属性名,返回一个数组 let a = Object.keys(obj); //返回所有属性值,返回一个数组 let b = Object.values(obj);
数组方法
concat 拼接数组
拼接两个数组 创建新数组
var a = ["S60", "S90"]; var b = ["XC40", "XC60", "XC90"]; var c = a.concat(b); console.log(c);
join 转换字符串
将数组转换为字符串
let a = ["Banana", "Orange", "Apple", "Mango"]; console.log(a.join()); //不填值默认用,拼接 console.log(a.join('-')); //可以换拼接值 console.log(a.join('')); //填空不显示拼接符
reverse 反转
反转数组中元素的顺序 ,会改变原数组
let a = ["Banana", "Orange", "Apple", "Mango"]; console.log(a.reverse());
indexof 查找
查找元素在数组里的下标 , 如果没找到 返 -1 ,对大小写敏感
let a = "BananaOrangeMango"; console.log(a.indexOf('查找字符',开始查找下标))
sort 排序
对数组排序
英文默认排序 , 顺序为按字母升序。
let a = ["Banana", "Orange", "Apple", "Mango"]; console.log(a.sort());
数字默认排序 , 按第一位 第一位完第二位 例如:1,11,21,233,44 ,
从小到大
数组.sort(function (a,b){ return a - b; })
从大到小
数组.sort(function (a,b){ return b - a; })
forEach 遍历数组
遍历数组 对于空数组不执行回调函数
数组.forEach(function (数组值,数组下标,当前数组对象){ console.log(数组值,数组下标,当前数组对象) });
- 会直接把数组里的数据修改
- forEach 不支持 continue与break
- 如果数组里是基本类型 值传递 数据修改不会改变
- 如果数组里是复杂类型 引用传递 数据修改会改变
map 遍历数组
- 和for差不多
- 对数组的每一项处理都会得到新数组
- 会返回一个新的数组来存储数据
//计算每项商品价格 let shop = [ {naem : 'name' , price : 32 , count : 4}, {name : 'giao' , price : 12 , count : 3} ]; let res = shop.map(v => v.price * v.count); console.log(res);
filter 筛选数组
返回数组符合条件的元素
let arr = [2, 5, 6, 11, 9, 22]; let temp = arr.filter( v => v >= 10) console.log(temp)
some与every
- every :一假则假
- some:一真则真
let list = [ {name:"aaa",age:3}, {name:"bbb",age:4}, {name:"ccc",age:5}, ]; var eve= list.every(function(item){ return item.age > 4 }) console.log(eve)//false; var some = list.some(function(item){ return item.age > 4 }) console.log(some) //true
find与findIndex
- find 查找数组元素 找不到返回undefined
- findIndex 查找数组下标 找不到返回 -1
//find let arr = [{name : 'giao', age : 18},{name : 'giao1', age : 19}] console.log(arr.find(value => value.name == 'giao')); //找到返回值 找不到返回undefined //findIndex let arr = [{name : 'giao', age : 18},{name : 'giao1', age : 19}] console.log(arr.findIndex(value => value.name === 'giao')); //找到返回下标 找不到返回-1
reduce 求和
把数组中每个元素的和累加起来
- 语法
array.reduce(function(total, currentValue, currentIndex, arr),initialValue)
- total 初始值,计算后返回的值
- currentValue 当前元素
- currentIndex 当前元素下标
- arr 数组对象
- initialValue 传递给函数的初始值
let nums = [10, 20, 30, 40, 50] let res = nums.reduce((total,value) => total = total + value ,0); console.log(res);
基本包装类型
-
只有复杂类型才有属性和方法
-
基本类型没有属性和方法
-
当你调用的时候系统会临时把这个数据自动转换成包装类型
-
包装类型可以用.valueOf() 得到基本类型
字符串的方法
trim 去除两端空格
字符串.trim();
substr 截取字符串
字符串.substr(开始截取的下标,截取个数);
- 截取下标:
-
- 不写直接截取到最后
substring 截取字符串
字符串.substring(开始截取的下标,截取的下标);
- 截取 开始截取的下标—截取的下标 不包括截取的下标
split 字符分割成数组
字符串.split(字符串,数组长度(选填))
"|a|b|c".split("|") //将返回["", "a", "b", "c"] "2:3:4:5".split(":") //将返回["2", "3", "4", "5"] "hello".split("") //可返回 ["h", "e", "l", "l", "o"] "hello".split("", 3) //可返回 ["h", "e", "l"]
toUpperCase 大写
字符串.toUpperCase();
toLowerCase 小写
字符串.toLowerCase();
indexOf 查找下标
字符串.indexOf('需要查找的字符')
- 返回下标
- 找不到返回 -1
练习
//获取问号后面的值转成对象 let str = 'http://www.zllhyy.cn/index.html?name=jack&age=16'; let a = str.substr(str.indexOf('?') + 1); let b = a.split('&'); let c = {} b.forEach(v => { let res = v.split('='); c[res[0]] = res[1] }) console.log(c);
空间内存原型
开辟空间的细节
- 只要出现new就会开辟新堆空间
- function 函数也会开辟新堆空间
- {} [] 也会开辟新对堆空间
- 复杂类型之间比较,比较地址
构造函数里内存浪费的问题
构造函数如果写了方法,那就意味着每次调用构造函数,都会出现新空间放这个方法,就会资源浪费
function Person (name, age) { this.name = name this.age = age //每次调用都会开新空间 this.sayHi = function () { console.log('大家好') } } let a = new Person('giao',18); a.sayHi();
- 解决方法
使用原型对象把方法放到原型对象上
function Person(name, age) { this.name = name this.age = age } // 写在原型对象上 Person.prototype.sayHi = function () { console.log('大家好') } let a = new Person('giao',18); a.sayHi();
原型对象
- 每当使用function声明函数,就会有一个原型对象
- 原型对象可以动态添加属性和方法
- 给原型对象加的,通过构造函数也可以访问
function Person() {} //找原型对象 console.log(Person.prototype); //找对应的函数 每一个原型对象都有这个默认属性 console.log(Person.prototype.constructor);
__proto__
- 每个对象都有
__proto__
属性,这个属性指向原型对象
__proto__与prototype
- __proto__是每个对象都有的属性
- prototype是函数才有属性方法
对象成员访问规则
- 对象自己有 访问自己的
- 对象没有,访问原型对象
面向对象的三大特性
- 封装
-
- 把代码封装到方法里
- 继承
-
- 一个对象拥有另一个对象的成员
- 多态
-
- 多种形态
- js中没什么体现
继承
- 子类的原型对象 = 父类的构造函数
- 再把子类的construction 指向 自己的构造函数
- 这样自己没有方法就可以去父类找
// 人类的构造函数 function Person (name, age) { this.name = name this.age = age } Person.prototype.eat = function () { console.log('吃啊吃') } function Chinese (name, age) { this.name = name this.age = age } // 让Chinese的prototype指向到person创建出来的对象 Chinese.prototype = new Person() // 因为这样会导致prototype对象里没有constructor // 所以需要重新指回 Chinese.prototype.constructor = Chinese let lj = new Chinese('李杰', 38) lj.eat() console.log(lj) console.log(Chinese.prototype)
原型链
- 每个对象都有__proto__属性,指向自己的原型对象
- 原型对象有__proto__属性,指向原型对象的原型对象
- 像这样的条链叫原型链
- 作用:实现继承
数组扩展方法
- 例如:数组里没有求最大值
- 给数组添加一个max方法
- 给原型对象添加所有数组都有方法
- 但数据不能写死,用this,谁调用就是谁
// 找出数组里的最大值,怎么找? // 应该谁调用,就取出谁的第一个元素,然后再跟后面的元素进行比较 // 找出最大值 Array.prototype.max = function () { // console.log('max方法', this) // 先取出调用这个方法的数组里的第一个元素 let max = this[0] for (let i = 1; i < this.length; i++) { if (this[i] > max) { max = this[i] } } // 把最大值返回 return max } let arr1 = [10, 20, 30] let arr2 = [100, 200, 300] let res1 = arr1.max() let res2 = arr2.max() console.log(res1) console.log(res2)
instanceof 判断是不是某种复杂类型
- 如果是 返回true
- 不是 false
- 为什么不用typeof
-
- typeof只对基本数据类型比较准确
- 原理:
-
- 判读对象在不在原型链上
- 基本数据类型不在原型链上
// // 基本数据类型连原型都没有,那么肯定不在原型链上 // // 用instanceof的时候是没有自动包装成对应的复杂类型 // console.log('abc' instanceof String) // false // console.log(true instanceof Boolean) // false // console.log(10 instanceof Number) // false // // 肯定有原型属性,所以在原型链上 // console.log( new String('abc') instanceof String ) // true // console.log( new Number(10) instanceof Number ) // true // console.log( new Boolean(true) instanceof Boolean ) // true // // 基本数据类型连原型都没有所以不在任何原型链上 // console.log('abc' instanceof Object) // false // console.log(true instanceof Object) // false // console.log(10 instanceof Object) // false // 因为调用valueOf以后得到的是他们对应的基本数据类型 // 基本数据类型连原型都没有,所以不在任何原型链上 console.log( new String('abc').valueOf() instanceof String ) // false console.log( new Number(10).valueOf() instanceof Number ) // false console.log( new Boolean(true).valueOf() instanceof Boolean ) // false
严格检查
-
在script开头 “use strict”
-
特点:
-
- 变量必须声明才使用
- this更加明确
-
"use strict" // 如果变量不声明直接赋值,它就是全局变量 // a = 10 // 报错 // console.log(a) function foo () { console.log('foo被调用了', this) } // 函数直接调用,里面的this是window foo() // undefined window.foo() // window
修改this指向 call
//函数.call(要修改的this指向, 实参列表) function getSum (n1, n2) { console.log(n1, n2, this) } getSum(10, 20) // 10 20 window // 既想修改this指向,又想传值 // call的参数1就是修改的this指向 // 参数2就是给第一个形参传递的实参 // 参数3就是给第二个形参传递的实参 // 以此类推 // 换句话说,使用call以后,以前该怎么传参还怎么传,只不过在最开始多了一个要修改的this指向 getSum.call(p1, 10, 20) // 10 20 p1对象 getSum.call(p1, 99) // 99 undefinned p1对象
- 如果不传 或者传递 null,undefined this就是window
- 如果传递的是基本类型,那么this就是这个基本类型的包装类型
修改this指向 apply
和call一样,只是参数不同
function fn(n1, n2) { console.log(this, n1, n2) } fn(10, 20) // window 10 20 let p1 = { name: 'jack', age: 16 } // 用数组或伪数组里的元素跟形参一一对应 // 第一个元素给第一个形参,第二个元素给第二个形参,以此类推 fn.apply(p1, [10,20]) // p1 10 20 fn.apply(p1, [10]) // p1 10 undefined
//函数.apply(要修改的this指向, 数组或伪数组) // 妙用 // 这个是可以求最大值 let arr = [10, 20, 102, 32, 54, 9] // 我怎么才能把把arr给一一铺开? // Math.max 这个max是由Math对象来调用的 // 所以里面的this默认就是Math // 而此时我们用apply的目的不是为了修改this指向 // 只是为了把数组一一铺开,所以修改的this指向还是传入Math,代表不修改 let res = Math.max.apply(Math, arr) console.log(res)
扩展运算符
...数据
可以将 对象,数组,字符串 成员都铺开
let arr = [10, 20, 30, 40] console.log(...arr) let str = 'hello' console.log(...str) let p1 = { name: 'jack', age: 16 } // 我希望有个对象叫p2,它也有name和age属性,值也是jack和16,而且要多一个height // 但又不希望p2改了会影响p1 let p2 = { height: 175, ...p1 } console.log(p2);
修改this指向 bind
-
跟call,apply的区别
- call,apply 会立即调用
- 而bind是产生一个修改this指向的新函数
-
let 新函数 = 函数.bind(this指向) -
function fn () { console.log('fn被调用',this) } let p1 = {name:'jack', age: 16} // fn.call(p1) // fn.apply(p1) // 把里面的this改成p1对象,返回一个修改了this指向后的新函数 let newFn = fn.bind(p1) newFn() // 它调用里面的this就是p1 fn() // window,bind不会影响原来函数的this -
如果bind时,没绑定实参,那么后面调用新函数还能继续传参
-
如果bind时,绑定了实参,那么后面新函数怎么调用它都是原来绑定的实参
-
function fn (a,b) { console.log('fn被调用',this,a,b) } let p1 = {name: 'jack'} // // // bind时如果仅仅只是修改this指向,没有传参 // let newFn = fn.bind(p1) // // // 那么新函数调用时就可以传参 // newFn(10,15) //p1 10 15 // newFn(10) // p1 10 undefined // newFn() // p1 undefined undefined // bind时如果既修改了this指向,又传递了实参 // 产生的新函数的this绑定到p1,且参数1绑定到15,参数2绑定到18 let newFn = fn.bind(p1,15,18) newFn() // p1 15 18 // 因为它之前绑定了参数,那么你后面调用新函数 // 不管怎么传,它都是之前绑定的函数 newFn(33,65) // p1 15 18 使用call和原型链实现继承
-
要创建中国人、美国人,他们都有共同的属性和方法
-
- 共同的属性:有名字、有年龄
- 共同的方法:有吃饭、跑步等行为
-
所以可以抽取一个父对象,包含名字、年龄、吃饭、跑步的行为,让他们继承即可
// 人类 function Person(name, age) { // 只有当name有值并且age也有值时才加 if (name && age) { this.name = name this.age = age } } Person.prototype.eat = function () { console.log('吃啊吃') } Person.prototype.run = function () { console.log('跑啊跑') } // 中国人 function Chinese(name, age) { // 光这么调用还不够 // 因为直接调用函数,函数里的this是window // 我需要把它里面的this,改成我当前的this Person.call(this, name, age) } // 为了继承,要让中国人的原型对象只想到Person的实例对象 Chinese.prototype = new Person() Chinese.prototype.constructor = Chinese // 一定要写在原型链的继承后面 Chinese.prototype.spring = function () { console.log('过春节') } // 美国人 function American(name, age) { // 属性继承 Person.call(this, name, age) } American.prototype = new Person() American.prototype.constructor = American // 创建一个中国人对象 let lilei = new Chinese('李雷', 16) console.log(lilei) lilei.eat() lilei.run() lilei.spring() // 创建一个美国人 let jim = new American('Jim Green', 16) console.log(jim) jim.eat() jim.run()
ES6-类与对象
-
什么是类?
-
- 好比:人类、动物类
- 就是一个群体的统称
-
- 类里描述这一类群体,有哪些特征和行为,所谓的特征对应到代码中就是属性,行为对应到代码中就是方法
- 类理解为是一套描述数据的模板,但是没有具体的数据
-
- 在ES6以前JS里是没有类专门的语法,都是通过
构造函数
起到类的作用
- 在ES6以前JS里是没有类专门的语法,都是通过
-
什么是对象?
-
- 某一个群体里的实际例子
- 对象可以理解为是根据模板创造出来的具体的数据
-
- 所以我们经常把
创建对象
叫做实例化对象
- 所以我们经常把
class 类名 { constructor() { // 给它描述有哪些属性 } // 方法列表 }
class Person { constructor(name, age) { // 给它描述有哪些属性:姓名、年龄 this.name = name, this.age = age } // 方法列表,在这里写方法不用加function // 语法:方法名 () { 方法体 } eat() { console.log('吃啊吃') } sleep () { console.log('睡啊睡') } } // 通过类来创建对象(实例化对象) let p1 = new Person('jack', 16) console.log(p1) p1.eat() p1.sleep()
ES6-静态成员
- es6以前:有构造函数直接调用的属性,方法
- es6以后:由类直接调用的属性,方法
- 在类的变量,函数 前加static关键字就是静态成员
ES6-类的继承
-
面向对象三大特征
-
- 封装,继承,多态
-
//语法 class 类名 extends 父类名 { constructor() { // 必须先调用 super() // 给自己独有属性赋值 } // 方法列表 } -
例如:
-
// 人类 // 如果没有写extends,那默认继承自Object // 相当于写了一个 extends Object class Person { constructor(name, age) { this.name = name this.age = age } eat() { console.log('吃啊吃') } } // 声明一个中国人类,并继承自人类 class Chinese extends Person { constructor(name, age, hukou) { // 是专门用来继承父类的属性的 // 必须先调用super然后才能给自己的属性赋值 super(name, age) this.hukou = hukou } spring() { console.log('过春节放鞭炮') } } let lilei = new Chinese('李雷', 16, '北京') console.log(lilei) lilei.spring() lilei.eat()
类的本质与类的继承本质
-
类的本质就是构造函数,所以类里的方法也都是放到构造函数.prototype里面的
-
这种语法我们称之为
语法糖
-
继承的本质:
-
- 还是通过
原型链
继承到方法 - 还是通过调用父类的构造函数并修改它里面的this指向来达到属性继承
- 还是通过
递归求1-5和
- 递归思路
let sum = 0 function getSum (i) { if (i > 1) { getSum(i - 1) } // console.log(i) sum += i } // 我传5,就要计算1到5的和 // 所以需要先把4找出来,找4之前先要找3,找3之前需要先找2,找2之前需要先找1 // 找到1不用往下找了,再把这些数字全部加起来,就是1到5的和 getSum(5) console.log(sum) // 15
递归遍历dom树
function getChild(ele) { for (let i = 0; i < ele.children.length; i++) { console.log(ele.children[i]) // 找当前这个元素的子元素 getChild(ele.children[i]) } } getChild(document.documentElement)
浅拷贝与深拷贝
-
这是指对象的拷贝操作
-
浅拷贝:代表只拷贝1层
-
深拷贝:代表不管有多少层都拷贝进来
-
- 深拷贝必须利用递归完成
-
代码如下
let p1 = { name: 'jack', age: 16, nums: [10, 20, 30], // 养宠物 pet: { nickname: '旺财' }, say: function () { console.log('你好') } } // 深拷贝:每一层都要拷贝 let p2 = {} function deepCopy(newObj, sourceObj) { for (let key in sourceObj) { // 如果是引用类型(对象)还得继续往下遍历 // 遍历到只有基本类型再赋值 // 如果是数组,就得开辟成数组的空间 if (sourceObj[key] instanceof Array) { // 首先要等于一个新对象,才会开辟新空间 newObj[key] = [] deepCopy(newObj[key], sourceObj[key]) } else if (sourceObj[key] instanceof Function) { newObj[key] = sourceObj[key] }else if (sourceObj[key] instanceof Object) { // 首先要等于一个新对象,才会开辟新空间 newObj[key] = {} deepCopy(newObj[key], sourceObj[key]) } else { newObj[key] = sourceObj[key] } } } deepCopy(p2, p1) // 把p1拷贝给p2 p2.pet.nickname = '汪汪' p2.nums[0] = 999 console.log(p1) console.log(p2)
==比较的细节
-
复杂类型之间比较:比较的是地址
-
基本数据类型之间比较:
-
- 如果是同类型,直接比较值
- 如果是不同类型,都是转成数值再比较
- 'abc' 转数字 nan true转数字 1 false 转数字 0
-
- 特殊情况:
-
-
- undefined == null 会得到true
- NaN参与 == 运算永远是false
- 除了 '',NaN,0,null,undefined 转换都是true
-
-
一个复杂类型和一个基本数据类型比较
-
- 会先把复杂类型调用valueOf或者toString转成基本类型后,再按基本数据类型的规则进行比较
- [].toString '' 0
- {}.toString [Object Object] NaN
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步