美文网首页
前端基础之JS(三)

前端基础之JS(三)

作者: 若雨千寻 | 来源:发表于2023-12-07 09:13 被阅读0次

1.31 箭头函数能否当构造函数

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的 this , arguments , super 或new.target 。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数

1.32 继承,优缺点 及方法有哪些?

继承的好处

a:提高了代码的复用性

b:提高了代码的维护性

c:让类与类之间产生了关系,是多态的前提

继承的弊端

类的耦合性增强了,但是开发的原则:高内聚,低耦合

1.32.1原型链继承

实现方式:将子类的原型链指向父类的对象实例

function Parent(){
    this.name = "parent";
    this.list = ['坤'];
}
Parent.prototype.sayHi = function(){
    console.log('hello');
}
function Child(){

}
Child.prototype = new Parent();
let child = new Child();
console.log(child.name);    
child.sayHi();

原理:子类实例child的__proto__ 指向Child的原型链prototype,而Child.prototype指向Parent类的对象实例,该父类对象实例的 __proto__ 指向Parent.prototype,所以Child可继承Parent的构造函数属性、方法和原型链属性、方法

优点:可继承构造函数的属性,父类构造函数的属性,父类原型的属性

缺点:

  1. 无法向父类构造函数传参;
  2. 共享父类实例的属性(若父类共有属性为引用类型,一个子类实例更改父类构造函数共有属性时会导致继承的共有属性发生变化)
var a = new Child();
var b = new Child();
a.list.push('rap');
console.log(b.list); // ['坤','rap']

1.32.2 构造函数继承

实现方式:在子类构造函数中使用call或者apply劫持父类构造函数方法,并传入参数

function Parent(name, id){
    this.id = id
    this.name = name
    this.printName = function(){
        console.log(this.name)
    }
}
Parent.prototype.sayName = function(){
    console.log(this.name)
};
function Child(name, id){
    Parent.call(this, name, id) // Parent.apply(this, arguments);
}
var child = new Child("坤", "1")
child.printName()   // 坤
child.sayName()     // Error

原理:使用call或者apply改变子类函数的作用域,使this执行父类构造函数,子类因此可以继承父类共有属性

优点:可解决原型链继承 共享 的问题

缺点: 不可继承父类的原型方法,构造函数不可以被复用

1.32.3 组合继承

原理:综合使用构造函数继承和原型链继承

function Parent(name, id){
    this.id = id;
    this.name = name;
    this.list = ['rap'];
    this.printName = function(){
        console.log(this.name);
    }
}
Parent.prototype.sayName = function(){
    console.log(this.name);
};
function Child(name, id){
    Parent.call(this, name, id);    // Parent.apply(this, arguments);
}
Child.prototype = new Parent();
var child = new Child("坤坤", "1");
child.printName();  // 坤坤
child.sayName()     // 坤坤
var a = new Child();
var b = new Child();
a.list.push('篮球');
console.log(b.list); // ['rap']    

优点:可继承父类原型上的属性,且可传参;每个新实例引入的构造函数是私有的

缺点:会执行两次父类的构造函数,消耗较大内存,子类的构造函数会代替原型上的那个父类构造函数

1.32.4 原型式继承

原理:类似Object.create,用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象,结果是将子对象的 __proto__ 指向父对象

let parent = {
    name: ['坤坤']
}
function copy(object) {
    function fn() {}
    fn.prototype = object
    return new F()
}
var child = copy(parent)

缺点: 共享引用数据类型

1.32.5 寄生式继承

原理:扩展原型式继承

function copy(object) {
    function fn() {}
    fn.prototype = object
    return new F()
}
function createObject(obj) {
    let obj = copy(obj);
    obj.getNames = function() {
        console.log(this.names)
        return this.names
    }
    return obj
}

优点:可添加新的属性和方法

1.32.6 寄生组合式继承

原理:改进组合继承,利用寄生式继承的思想继承原型

function inheritPrototype(subClass, superClass) {
    // 复制一份父类的原型
    let p = copy(superClass.prototype);
    // 修正构造函数
    p.constructor = subClass;
    // 设置子类原型
    subClass.prototype = p;
}
function Parent(name, id){
    this.id = id;
    this.name = name;
    this.list = ['a'];
    this.printName = function(){
        console.log(this.name);
    }
}
Parent.prototype.sayName = function(){
    console.log(this.name);
};
function Child(name, id){
    Parent.call(this, name, id);
// Parent.apply(this, arguments);
}
inheritPrototype(Child, Parent);

1.32.7 ES6 class extends

class A {
    constructor() {
        this.a = 'hello';
    }
}
class B extends A {
    constructor() {
        super();
        this.b = 'world';
    }
}
let b = new B();

1.33 扩展运算符 ...

1.33.1哪些类型能被扩展操作符?

类型:数组、对象、字符串

  • 复杂数据类型都可以,当要转化为可迭代数据结构时可设置对象的迭代器对扩展运算符扩展出来的值进行操作。

  • 基础数据只有string可以使用扩展运算符,number,boolean,null,undefined无效

1.33.2 场景

// 1、函数调用
function add(x, y) {
    return x + y
}
add(...[4, 38])

function f(a, b, c, d, e) { }
f(1, ...[1, 2], 2, ...[3])

//2.往数组里push多个元素
var arr1 = ['子异', '坤坤'];
var arr2 = ['说', '唱', '跳'];
arr1.push(...arr2);
console.log(arr1); //['子异', '坤坤','说', '唱', '跳']

//3.替代函数的apply方法
function f(a, b, c) { }
var args = [0, 1, 2];
f.apply(null, args);    //ES5 的写法
f(...args);             //ES6的写法

//4.求一个数组的最大数简化
Math.max.apply(null, [14, 3, 77]) //ES5 的写法
Math.max(...[14, 3, 77]) //ES6 的写法,等同于Math.max(14, 3, 77)

//5.扩展运算符后面可以放表达式
const arr = [...(5 > 0 ? ['a'] : []),'b'];
console.log(arr); //['a','b']

//6.与解构赋值结合,用于生成数组
const a1 = [1, 2];
const a2 = [...a1]; //写法1
const [...a2] = a1; //写法2
const [first, ...rest] = [1, 2, 3, 4, 5];
first //1
rest //[2, 3, 4, 5]
const [first, ...rest] = [];
first //undefined
rest //[]
const [first, ...rest] = ["foo"];
first //"foo"
rest //[]
//1234567891011121314151617

//7.合并数组
[...arr1, ...arr2, ...arr3] //[ 'a', 'b', 'c', 'd', 'e' ]
123
//8.数组的克隆
var arr1 = [0, 1, 2];
var arr2 = [...arr1];
arr1[0]=100;
console.log(arr2); //[0, 1, 2]
/* 乍一看,arr2与arr1不共用引用地址,arr2不随着arr1变化,接着往下看 */
var arr1 = [0, [1,11,111], 2];
var arr2 = [...arr1];
arr1[1][0]=100;
console.log(arr2); //[0, [100,11,111], 2]

1.34 实现异步的方法

1.34.1 回调函数

ajax(url, () => {
// 处理逻辑
    ajax(url1, () => {
    // 处理逻辑
        ajax(url2, () => {
        // 处理逻辑
        })
    })
})

优点:简单、容易理解和实现

缺点:不利阅读和维护,耦合度高,不能使用try...catch捕获,不能直接 return

1.34.2 promise

本意是承诺,这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了

Promise的三种状态

  • Pending----Promise对象实例创建时候的初始状态

  • Fulfilled----可以理解为成功的状态

  • Rejected----可以理解为失败的状态

let p = new Promise((resolve, reject) => {
    reject('reject')
    resolve('success')//无效代码不会执行
})
p.then( value => {
    console.log(value)
},
reason => {
    console.log(reason)//reject
})

当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的

new Promise((resolve, reject) => {
    console.log('1')
    resolve('success')
})
console.log('2')

promise的链式调用

  • 每次调用返回的都是一个新的Promise实例(这就是then可用链式调用的原因)

  • 如果then中返回的是一个结果的话会把这个结果传递下一次then中的成功回调

  • 如果then中出现异常,会走下一个then的失败回调

  • 在 then中使用了return,那么 return 的值会被Promise.resolve() 包装(见例1,2)

  • then中可以不传递参数,如果不传递会透到下一个then中(见例3)

  • catch 会捕获到没有捕获的异常

Promise.resolve(1)
.then(res => {
console.log(res)
return 2 //包装成 Promise.resolve(2)
})
.catch(err => 3)
.then(res => console.log(res))

ajax(url).then(res => {
    console.log(res)
    return ajax(url1)
}).then(res => {
    console.log(res)
    return ajax(url2)
}).then(res => console.log(res))

存在一个缺点:无法取消promise,错误需要通过回调函数捕获

1.34.3 生成器generator/yield

特点:控制函数的执行

  • Generator 函数是一个状态机,封装了多个内部状态
  • Generator 函数除了状态机,还是一个遍历器对象生成函数
  • 可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果
  • yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
function *foo(x) {
    let y = 2 * (yield (x + 1))      
    let z = yield (y / 5)           
    return (x + y + z)
}
let it = foo(2)
console.log(it.next())      // => {value: 3, done: false}
console.log(it.next(20))    // => {value: 8, done: false}
console.log(it.next(30))    // => {value: 72, done: true}

// 解决回调地狱
function *fetch() {
    yield ajax(url, () => {})
    yield ajax(url1, () => {})
    yield ajax(url2, () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()

1.34.3 async/await

特点:

  • async/await是基于Promise实现的,它不能用于普通的回调函数
  • async/await与Promise一样,是非阻塞的
async function fn() {
    return "async"
}
console.log(fn()) // -> Promise {<resolved>: "async"}

类似generator调用方式

async function ff() {
    await ajax(url1, ()=>{})
    await ajax(url2,()=>{})
    await ajax(url3,()=>{})
}
await ff()

模拟一个并发请求

function readAll() {
    
    read1()
    read2()//这个函数同步执行
}
async function read1() {
    let r = await ajax(url1,()=>{})
    console.log(r)
}
async function read2() {
    let r = await ajax(url2,()=>{})
    console.log(r)
}
readAll()

1.35 循环i 一次性定时器中输出什么,如何解决?

下段代码输出

for (var i = 0; i< 10; i++){
    setTimeout((i) => {
        console.log(i)
    }, 0)
}

期望输出0-9

结果输出 10个9,原因:setTimeout是异步的,而var 这个时候是全局变量,所以打印10个9

方法一:

for (var i = 0; i< 10; i++){
    setTimeout((i) => {
        console.log(i)
    }, 1000,i)
}

方法二:

for (let i = 0; i< 10; i++){
    setTimeout(() => {
        console.log(i)
    }, 1000)
}

方法三:

for (var i = 0; i< 10; i++){
    ((i)=>{
        setTimeout(() => {
            console.log(i)
        },1000);
    })(i)
}

方法四:

for (var i = 0; i< 10; i++){
    setTimeout(console.log(i),1000);
}
for (var i = 0; i< 10; i++){
    setTimeout((()=>console.log(i))(),1000);
}

方法五:

for (var i = 0; i< 10; i++){
    try{
        throw i
    }catch(i){
        setTimeout(() => {
            console.log(i)
        }, 1000)
    }
}

1.36 为什么js是单线程

用途:js在创立之初主要是应用于用户与浏览器的交互,以及操作dom,这一特性决定了,只能是单线程,否则会带来复杂的同步问题。

    例如: 如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom,这时候就不能处理。避免这个问题,浏览器就设计了单线程,避免了这个麻烦

1.37 死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源而造成阻塞的现象,若无外力作用,它们都将无法继续执行

产生原因:

  • 竞争资源引起进程死锁
  • 可剥夺和非剥夺资源
  • 竞争非剥夺资源
  • 竞争临时性资源
  • 进程推进顺序不当

产生条件:

  1. 互斥条件:涉及的资源是非共享的

    • 涉及的资源是非共享的,一段时间内某资源只由一个进程占用,如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放
  2. 不剥夺条件:不能强行剥夺进程拥有的资源

    • 进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放
  3. 请求和保持条件:进程在等待一新资源时继续占有已分配的资源

    • 指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放 环路等待条件:存在一种进程的循环链,链中的每一个进程已获得的资源同时被链中的下一个进程所请求 在发生死锁时,必然存在一个进程——资源的环形链

解决办法

只要打破四个必要条件中的一个就能有效防止死锁的发生

1.38 暂时性死区

暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量

let 、const具有暂时性死区

var 不具有暂时性死区

相关文章

网友评论

      本文标题:前端基础之JS(三)

      本文链接:https://www.haomeiwen.com/subject/gdedgdtx.html