JHHK

欢迎来到我的个人网站
行者常至 为者常成

JavaScript概要

目录

语言

一、常量变量

// 常量:不允许修改
const pi = 3.14 

// 变量:可以修改
let num = 99 
num= 100 

二、基本数据类型

// boolean
let isClick = false

// number(NaN也是number类型 浮点也是number类型)
let a = 0

// string
let str = 'hello world'

// undefine是一种类型
undefine

三、对象类型

let array = [1, 2, 3]

let dic = {key1:'value1', key2:'value2'}

let person = new Person('xiaoming', 18)

// 匿名函数
let blk = (a, str)=>{ return 10 }
let blk = function(a, str){ return 10}

// 函数
function sum (numA, numB) {}

// null 也是object类型
let test = null

四、一门语言有哪些基本要素

1、分支结构、循环结构

2、面向对象、面向过程
封装、继承、多肽
属性
方法:构造函数、析构函数、普通函数

3、多线程、同步异步

4、其它
运算:四则/逻辑/位运算
事件:捕获与冒泡
定时器:间隔执行、到点执行

变量/函数提升

一、变量提升

当你使用 var 关键字声明变量时,这个变量的声明会被提升到当前函数或全局作用域的顶部

// 输出: undefined
console.log(x); 
var x = 10;

// 相当于
var x;
console.log(x); 
x = 10;

理解:函数作用域 和 块级作用域{}

// 使用 let 声明的变量,具有块级作用域
if (true) {
    // var blockScopedVar = 'I am block scoped';
    let blockScopedVar = 'I am block scoped';
}
console.log(blockScopedVar); // 报错:blockScopedVar is not defined


for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i); // 输出: 5,5,5,5,5(因为 i 具有函数作用域,共享相同的 i 变量)
    }, 1000);
}

for (let j = 0; j < 5; j++) {
    setTimeout(function () {
        console.log(j); // 输出: 0,1,2,3,4(因为 j 具有块级作用域,每个循环迭代都有自己的 j 变量)
    }, 1000);
}

二、函数提升

声明一个函数时,整个函数体(包括函数名称和函数体内的内容)会被提升到当前函数或全局作用域的顶部

greet(); // 输出: Hello!
function greet() {
    console.log('Hello!');
}


// 实际上,上述代码在执行时会被解释为以下形式:
function greet() {
    console.log('Hello!');
}
greet();

构造函数

一、构造函数

1、创建实例对象(属性 + 方法)
2、方法内this指向实例对象
3、person1.sing 和 person2.sing并不是同一个方法

/*
命名以大写字母开头
只能由 "new" 操作符来执行 - 实例化
构造函数返回实例化对象,return 返回的值无效,所以不要写return
构造函数内部的this指向实例化对象
*/
function Person(name = '', age = 1) {
    this.name = name
    this.age = age
    this.sing = () => {
        console.log('我会唱歌')
    }
    this.dance = function () {
        console.log('我会跳舞')
    }
}

// 创建实例对象:因为参数有默认值,可以不传参
let person1 = new Person()
let person2 = new Person('qiaozhi', 6)

//获取属性、调用方法
console.log(`person = ${person.name}-${person.age}`)
person.sing()
person.dance()

//false
console.log(person1.sing == person2.sing);

二、静态属性/方法

/*
构造函数的属性和方法被称为静态成员
静态属性,静态方法
静态成员方法中的 this 指向构造函数本身
*/
Person.eyes = 2
Person.walk = function () {
    console.log('人多会走路', this.eyes)
}
console.log(Person.eyes, Person.walk());

原型对象

原型对象的本质,它也是一个对象
作用:共享方法、共享属性、可以节省内存

一、原型对象

原型对象共享方法

// 在Person构造函数的原型对象上添加一个方法
Person.prototype.sing = function () {
    //构造函数和原型对象中的this 都指向 实例化的对象
    console.log(`${this.name}正在唱歌...`);
}

// 创建两个Person对象
let ldh = new Person('刘德华', 50)
let zxy = new Person('张学友', 55)

// 这两个对象共享相同的原型对象上的sing方法
ldh.sing()// 输出:刘德华正在唱歌
zxy.sing()// 输出:张学友正在唱歌

// 这是同一个方法
console.log(ldh.sing === zxy.sing); // 输出:true

原型对象共享属性

// 定义一个共享的age属性并初始化为0
Person.prototype.age = 0; 
const person1 = new Person('Alice');
const person2 = new Person('Bob');
console.log(person1.age); // 输出: 0
console.log(person2.age); // 输出: 0


// 修改了person1的age属性.
//实际上创建了一个名为age的属性并将其分配给person1对象,而不会修改原型对象上的age属性
person1.age = 25;
console.log(person1.age); // 输出: 25
console.log(person2.age); // 输出: 0(person2的age属性仍然是0,因为它没有被修改)

二、原型链

基类

// 基类构造函数
function Animal(name) {
    this.name = name;
}
// 基类原型对象上的方法
Animal.prototype.sayHello = function () {
    console.log(`Hello, I'm ${this.name}`);
};

子类

// 派生类构造函数
function Dog(name, breed) {
    // 使用call方法调用基类构造函数,以继承基类属性
    Animal.call(this, name);
    this.breed = breed;
}


// 建立原型链,使Dog继承Animal
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复构造函数指向


// 派生类上的方法
Dog.prototype.bark = function () {
    console.log(`${this.name} is barking!`);
};

使用

// 创建Animal实例
const animal = new Animal('Generic Animal');
animal.sayHello(); // 输出: Hello, I'm Generic Animal

// 创建Dog实例
const dog = new Dog('Buddy', 'Golden Retriever');
dog.sayHello(); // 输出: Hello, I'm Buddy
dog.bark(); // 输出: Buddy is barking!

// 是否是同一个方法
console.log(dog.sayHello === animal.sayHello); // ture

继承链关系图
三个关键:构造函数、原型对象、实例对象

instanceof的用法

// 是否在继承链上
console.log(arr instanceof Object); // true

this指针

一、this讲解

只有普通函数内才有this:
普通函数this指向调用者:Obj.testFunc(),指向Obj
当函数直接调用时,this指向全局对象(在浏览器中是window,在Node.js中是global)。在严格模式下,this会是undefined。
testFunc(),指向全局对象。

function generalFn() {
    // 普通函数的this是 [object Window]
    console.log(`普通函数的this是 ${this}`);
}
generalFn()

箭头函数:
箭头函数中并不存在 this
箭头函数会默认帮我们绑定外层 this 的值
箭头函数的this是在定义时确定,捕获的定义时上下文中的this
适用:需要使用上层this的地方
不适用:
构造函数/原型函数/字面量对象中函数/dom事件函数

let person = {
    name: "qiaozhi",
    age: 5,
    walk: function () {
        // 对象内普通函数的this是 [object Object]
        console.log(`对象内普通函数的this是 ${this}`);
    },
    eat: () => {
        // 对象内箭头函数的this是 [object Window]
        console.log(`对象内箭头函数的this是 ${this}`);
    }
}
person.walk()
person.eat()
class Timer {
  constructor() {
    this.seconds = 0;
  }

  start() {
    setInterval(() => {
      this.seconds++;
      console.log(this.seconds);
    }, 1000);
  }
}

const timer = new Timer();
timer.start();

在这个例子中,setInterval的回调函数是一个箭头函数,因此this指向Timer实例,而不是setInterval的上下文(不是setInterval内的调用者)。

箭头函数的this指向不变,避免了传统函数中this指向不确定的问题

二、修改this指向

三个函数:call、apply、bind(对箭头函数无效)

let person = { name: '乔治', age: 18 }
function sayHi(x, y) {
    console.log(`this 指向 ${JSON.stringify(this)}`, `参数1:${x}`, `参数2:${y}`);
}

// this 指向 {"name":"乔治","age":18} 参数1:call1 参数2:call2
sayHi.call(person, 'call1', 'call2')


//this 指向 {"name":"乔治","age":18} 参数1:apply1 参数2:apply2
sayHi.apply(person, ['apply1', 'apply2'])
// apply 主要跟数组有关系,把参数变成了数组形式传入,比如使用 Math.max() 求数组的最大值
console.log(Math.max.apply(null, [1, 2, 3]))
console.log(Math.max(...[1, 2, 3]))


// this 指向 {"name":"乔治","age":18} 参数1:bind1 参数2:bind2
let sayHello = sayHi.bind(person)
//bind 不会调用函数, 可以改变函数内部this指向.
sayHello('bind1', 'bind2')

闭包

什么是闭包?
外层函数 + 内层函数 + 内层函数访问外层函数的变量

逃逸闭包?
在调用函数内部被执行为非逃逸闭包。
函数调用结束了,仍然可以被调用是逃逸闭包。

可能引起的问题:
内存泄露

1、访问局部变量:计数器

function counter() {
    let count = 0;
    return function () {
        return count++;
    };
}
const increment = counter();
console.log(increment()); // 0
console.log(increment()); // 1

2、模块化:计算器

// 闭包可以用于创建模块化的代码结构,将相关的功能函数(内部函数)封装在一个函数(外部函数)内部,并通过返回内部函数来暴露一些接口。
function createCalculator() {
    let result = 0;
    return {
        add: function (num) {
            result += num;
        },
        subtract: function (num) {
            result -= num;
        },
        getResult: function () {
            return result;
        }
    };
}
const calculator = createCalculator();
calculator.add(5);
calculator.subtract(2);
console.log(calculator.getResult()); // 3

3、异步执行:网络请求

function fetchData(url, callback) {
    // 外部函数:fetchData; 内部函数: setTimeout; 局部变量:callback;
    // 模拟异步请求
    setTimeout(function () {
        const data = 'Some data from the server';
        callback(data);
    }, 1000);
}
fetchData('https://example.com/api/data', function (result) {
    console.log('Data received:', result);
});

异步

一、Promise

Promise的两个关键:resolve 和 reject
resolve触发.then内的回调,reject触发.catch内的回调

const p1 = new Promise((resolve, reject) => {
    console.log('这个代码块是立即执行的1')
    setTimeout(() => {
        resolve('第一次回调')
    }, 2000);
})

// promise实例对象的then方法
const p2 = p1.then(result => {
    console.log(result)
    return new Promise((resolve, reject) => {
        console.log('这个代码块是立即执行的2')
        setTimeout(() => {
            resolve('第二次回调')
        }, 2000);
    })
})

p2.then((result) => {
    console.log(result)
})

以上代码可以使用链式编程写成下面这样

console.log(1)
new Promise((resolve, reject) => {
    console.log('这个代码块是立即执行的1')
    setTimeout(() => {
        resolve('第一次回调')
        // reject('第一次回调:失败')
    }, 2000);
}).then((result) => {
    console.log(result)
    return new Promise((resolve, reject) => {
        console.log('这个代码块是立即执行的2')
        setTimeout(() => {
            resolve('第二次回调')
            // reject('第二次回调:失败')
        }, 2000);
    })
}).then((result) => {
    console.log(result)
}).catch((error) => {
    console.log(`error:${error}`);
});
console.log(2)

/**
1
这个代码块是立即执行的1
2
第一次回调
这个代码块是立即执行的2
第二次回调
*/


//then可以传递两个回调函数,一个成功回调,一个失败回调
//.then((success)=>{},(error)=>{});

二、async 和 await的使用

function asyncAndAwaitTest() {

    console.log('执行顺序0')
    testFunc()
    console.log('执行顺序1')

    async function testFunc() {
        //async函数和await_捕获错误 使用try...catch
        try {

            console.log('执行顺序2');

            //重要:在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值
            let p = new Promise((resolve, reject) => {
                console.log('执行顺序3')
                setTimeout(() => {
                    resolve('这是resolve的结果')
                    // reject('这是reject的结果')
                }, 2000);
                console.log('执行顺序4')
            })

            //await 后边跟的是一个Promise 的实例对象p
            // p 的异步执行结果能拿到的两种方式就是 p.then((result)=>{})) 或 let result = await p
            const result = await p

            console.log('执行顺序5')
            console.log(result)
            console.log('执行顺序6')

        } catch (error) {
            console.log('error = ', error);
        }

        /*
            执行顺序0
            执行顺序2
            执行顺序3
            执行顺序4
            执行顺序1
            ... 2s后执行下面
            执行顺序5
            这是resolve的结果
            执行顺序6
        */
    }
}
function promiseAllTest() {
    const bjPromise = axios({ url: 'http://hmajax.itheima.net/api/weatherc', params: { city: '110100' } })
    const shPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '310100' } })
    Promise.all(
        [bjPromise, shPromise]
    ).then((result) => {
        // 注意:结果数组顺序和合并时顺序是一致
        console.log(result)
    }).catch((error) => {
        console.log('error = ', error)
    })
}

事件循环

定时器

1、定时器

setTimeout(() => {
    console.log('这段代码会在2秒后执行');
}, 2000);


const timer = setTimeout(() => {
    console.log('这段代码不会执行');
}, 2000);

//清除定时器
clearTimeout(timer)

2、间歇函数

setInterval(() => {
    console.log('这段代码会间隔1秒执行');
}, 1000);

const interval = setInterval(() => {
    console.log('这段代码不会执行');
}, 1000);

// 清除间歇函数
clearInterval(interval)

捕获与冒泡

lxy:捕获与冒泡是一个完整的事件链路,stopPropagation会终止整个链路的后续事件
lxy:链路:父元素捕获回调 - 子元素捕获回调 - 子元素冒泡回调 - 父元素冒泡回调

// 捕获事件回调
container.addEventListener('click', function (e) {
    // console.log(e) // e 是事件对象

    //this 是环境对象
    console.log(`捕获:${this.className}监听回调,被点击对象是${e.target.className}`)
    // e.stopPropagation()
}, true)//默认是false:冒泡事件回调。true:捕获事件回调


// 冒泡事件回调  
container.addEventListener('click', function (e) {
    console.log(`冒泡:${this.className}监听回调,被点击对象是${e.target.className}`)
    // e.stopPropagation()
}, false)

Dom

1、对一个节点,添加/删除/转换 一个类选择器

let container = document.querySelector('.container')
container.classList.add('border')
container.classList.remove('container')
container.classList.toggle('container')

2、克隆/追加/插入/删除一个节点

//true 克隆所有子节点 false为只克隆自己
const cloneSub1 = sub1.cloneNode(true)
father.appendChild(cloneSub1)// 追加到后边
father.insertBefore(sub3, sub1)// 插入到sub1的前边
father.removeChild(sub3)

3、拿到一个节点(父容器),修改它的innerHTML

let sub = document.querySelector('.container .sub')
sub.style.backgroundColor = 'blue'
sub.innerHTML = `<h1>哈哈</h1>`

let divs = document.querySelectorAll('div')
console.dir(divs)

4、节点的查询

father.children
sub1.parentNode
sub1.nextElementSibling

5、偏移计算

//html 被卷去的头部
let scrollTop = document.documentElement.scrollTop
console.log(scrollTop)

//offsetTop以谁为准:带有定位的父级,如果都没有则以 文档左上角 为准
let offsetTop = document.querySelector('.sub').offsetTop
console.log(offsetH)

其它

一、节流与抖动

1、节流
对于连续触发的事件,在 n 秒中只执行一次函数。如果不加节流在n秒内可能调用上百次

let div = document.querySelector('.throttle')

let callFn = function () {
    div.innerHTML = i++
}

i = 0
// div.addEventListener('mousemove', callFn)

//节流:对于连续触发的事件,在 n 秒中只执行一次函数。如果不加节流在n秒内可能调用上百次
div.addEventListener('mousemove', throttle(callFn, 500))


//节流函数
function throttle(fn, msecond = 500) {
    let startTime = new Date()
    return function () {
        let now = new Date()
        if (now - startTime >= msecond) {
            fn()
            startTime = now
        }
    }
}

2、抖动
在指定时间内,如果触发了第二次操作,就将第一次取消,然后重新计时。
比如:输入框事件

let div = document.querySelector('.debounce')

let callFn = function () {
    div.innerHTML = i++
}

i = 0
// div.addEventListener('mousemove', callFn)

div.addEventListener('mousemove', debounce(callFn, 500))


//
function debounce(fn, msecond = 500) {
    let timeId
    return function () {
        if (timeId) clearTimeout(timeId)
        timeId = setTimeout(function () {
            fn()
        }, msecond);
    }
}

二、JS的GC机制

JS同样也不需要我们去手动管理内存。JS的内存管理使用的是GC机制(Tracing Garbage Collection)。
不同于OC的引用计数,Tracing Garbage Collection是由GCRoot(Context)开始维护的一条引用链,一旦引用链无法触达某对象节点,这个对象就会被回收掉。如下图所示:


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.