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是函数才有属性方法
  • hhh

对象成员访问规则

  • 对象自己有 访问自己的
  • 对象没有,访问原型对象

面向对象的三大特性

  • 封装
    • 把代码封装到方法里
  • 继承
    • 一个对象拥有另一个对象的成员
  • 多态
    • 多种形态
    • 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里是没有类专门的语法,都是通过 构造函数 起到类的作用
  • 什么是对象?

    • 某一个群体里的实际例子
    • 对象可以理解为是根据模板创造出来的具体的数据
    • 所以我们经常把 创建对象 叫做 实例化对象
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和

  • 递归思路
  • h
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
posted @   rain_sparse  阅读(23)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示