复盘Js

本文深入探讨JavaScript中的核心概念,包括call、apply、bind的用法,函数的节流与防抖,原型链及其继承方式,对象与数组的操作,以及HTTP协议的理解。详细解析了call、apply、bind的异同,介绍了节流和防抖在处理事件时的应用,讲解了如何通过原型链实现继承,同时阐述了对象属性的判断方法和数组的各种操作技巧,如扁平化、去重和排序。此外,还讨论了Proxy在对象拦截和数据绑定中的角色,以及HTTP请求的过程和状态码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、call,apply和bind

call和apply可以调用函数,改变this,实现继承和借用别的对象的方法;

1.1 call和apply用法

  • 间接调用函数,改变作用域的this值
  • 劫持其他对象的方法
var foo = {
  name:"张三",
  logName:function(){
    console.log(this.name);
  }
}
var bar={
  name:"李四"
};
foo.logName.call(bar);//李四
// 实质是call改变了foo的this指向为bar,并调用该函数
  • 两个函数实现继承
function Animal(name){   
  this.name = name;   
  this.showName = function(){   
    console.log(this.name);   
  }   
}   
function Cat(name){  
  Animal.call(this, name);  
}    
var cat = new Cat("Black Cat");   
cat.showName(); //Black Cat
  • 为类数组(arguments和nodeList)添加数组方法push,pop
(function(){
  Array.prototype.push.call(arguments,'王五');
  console.log(arguments);//['张三','李四','王五']
})('张三','李四')
  • 合并数组
let arr1=[1,2,3]; 
let arr2=[4,5,6]; 
Array.prototype.push.apply(arr1,arr2); //将arr2合并到了arr1中
  • 求数组最大值
Math.max.apply(null,arr)
  • 判断数据类型(这种方式比较常用)
Object.prototype.toString.call({})

1.2 bind

bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()第一个参数的值,例如,f.bind(obj),实际上可以理解为obj.f(),这时,f函数体内的this自然指向的是obj。

详解:bind方法

this.name = "test";
let testObj = {
	name:'zhangsan',
	introduce:function(){
		return this.name;
	}
}
let test = {
	name:"lisi"
}
let test1 = {
	name:"wangwu"
}
let fn = testObj.introduce;
console.log(fn());//test   
console.log(fn.bind(test)());//lisi
console.log(fn.bind(test1)());//王五

 1.3 三者异同

  • 同:都是改变this指向,都可接收参数
  • 异:bind和call是接收单个参数,apply是接收数组

 

2、函数的节流和防抖

2.1 节流

  • 概念:事件触发后每隔一段时间触发一次,可触发多次
  • 应用:scroll,resize事件一段时间触发多次
// 节流函数
let throttle = function(func, delay) {
  let timer = null;
  return function() {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, arguments);
        // 或者直接 func()
        timer = null;
      }, delay);
    }
  };
};

// 处理函数
function handle() {
  console.log(arguments);
  console.log(Math.random());
}
// 测试用例
document
  .getElementsByClassName("scroll-box")[0]
  .addEventListener("scroll", throttle(handle, 3000));

2.2 防抖

  • 概念:事件触发动作完成后一段时间触发一次
  • 应用:scroll,resize事件触发完后一段时间触发
// 防抖函数
let debounce = function(fn, wait) {
  let timeout = null;
  return function() {
    if (timeout !== null) clearTimeout(timeout); //如果多次触发将上次记录延迟清除掉
    timeout = setTimeout(() => {
      fn.apply(this, arguments);
      // 或者直接 fn()
      timeout = null;
    }, wait);
  };
};
// 处理函数
function handle() {
  console.log(arguments);
  console.log(Math.random());
}
// 测试用例
document
  .getElementsByClassName("scroll-box")[0]
  .addEventListener("scroll", debounce(handle, 3000));

3、原型链

3.1 构造函数,实例与原型对象的关系

3.2 创建对象的几种方式

1.字面量

let obj={'name':'张三'}

2.Object构造函数创建

let Obj=new Object()
Obj.name='张三'

3.使用工厂模式创建对象

function createPerson(name){
 var o = new Object();
 o.name = name;
 return o; 
}
var person1 = createPerson('张三');

4.使用构造函数创建对象

function Person(name){
 this.name = name;
}
var person1 = new Person('张三');

5.内置方法

let test = Object.create({x:123,y:345});
console.log(test);//{}
console.log(test.x);//123
console.log(test.__proto__.x);//123
console.log(test.__proto__.x === test.x);//true

3.2 new 的时候做了什么事

  • 创了一个新对象;
  • this指向构造函数;
  • 构造函数有返回,会替换new出来的对象,如果没有就是new出来的对象;

4、继承

4.1 原型链继承

将父类的实例作为子类的原型

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

function Cat(){ 
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

var cat = new Cat();
console.log(cat.name);//cat
console.log(cat.eat('fish'));//cat正在吃:fish  undefined
console.log(cat.sleep());//cat正在睡觉! undefined
console.log(cat instanceof Animal); //true 
console.log(cat instanceof Cat); //true

优缺点:简单易于实现。但是要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,无法实现多继承

4.2 构造继承

实质是利用call来改变Cat中的this指向

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

优缺点:可以实现多继承。不能继承原型属性/方法

4.2 实例继承

为父类实例添加新特性,作为子类实例返回

function Cat(name){
  var instance = new Animal();
  instance.name = name || 'Tom';
  return instance;
}

优缺点:不限制调用方式。但不能实现多继承

4.3 拷贝继承

将父类的属性和方法拷贝一份到子类中

function Cat(name){
  var animal = new Animal();
  for(var p in animal){
    Cat.prototype[p] = animal[p];
  }
  Cat.prototype.name = name || 'Tom';
}

优缺点:支持多继承。但是效率低占用内存

4.4 组合继承

通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

4.5 寄生组合继承

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
(function(){
  // 创建一个没有实例方法的类
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Cat.prototype = new Super();
})();

4.6 ES6的class继承

ES6 的继承机制是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this

//父类
class Person {
    //constructor是构造方法
    constructor(skin, language) {
        this.skin = skin;
        this.language = language;
    }
    say() {
        console.log('我是父类')
    }
}
//子类
class Chinese extends Person {
    constructor(skin, language, positon) {
        //console.log(this);//报错
        super(skin, language);
        //super();相当于父类的构造函数
        //console.log(this);调用super后得到了this,不报错,this指向子类,相当于调用了父类.prototype.constructor.call(this)
        this.positon = positon;
    }
    aboutMe() {
        console.log(`${this.skin} ${this.language}  ${this.positon}`);
    }
}
//调用只能通过new的方法得到实例,再调用里面的方法
let obj = new Chinese('红色', '中文', '香港');
obj.aboutMe();
obj.say();

 

5、对象

5.1 判断对象的属性

  • in:如果指定的属性在指定的对象或其原型链中,则in 运算符返回true。'name' in test //true
  • hasOwnProperty():只判断自身属性。test.hasOwnProperty('name') //true
  • .或[]:对象或原型链上不存在该属性,则会返回undefined。test.name //"lei" test["name"] //"lei"

 

6、proxy

  • ES6的方法,实质是对对象做了一个拦截,并提供了13个处理方法。
  • 两个参数:对象和行为函数
let handler = {
    get(target, key, receiver) {
      console.log("get", key);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      console.log("set", key, value);
      return Reflect.set(target, key, value, receiver);
    }
  };
  let proxy = new Proxy(obj, handler);
  proxy.name = "李四";
  proxy.age = 24;

6.1 defineProterty和proxy的对比

  • defineProterty是es5的标准,proxy是es6的标准;
  • proxy可以监听到数组索引赋值,改变数组长度的变化;
  • proxy是监听对象,不用深层遍历,defineProterty是监听属性;
  • 利用defineProterty实现双向数据绑定(vue2.x采用的核心);
  • 利用proxy实现双向数据绑定(vue3.x会采用)。

 

7、数组

7.1 扁平化n维数组

实质是利用递归和数组合并方法concat实现扁平

function flatten(arr) {
    while(arr.some(item=>Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}
flatten([1,[2,3]]) //[1,2,3]
flatten([1,[2,3,[4,5]]) //[1,2,3,4,5]

7.2 去重

Array.from(new Set([1,2,3,3,4,4])) //[1,2,3,4]
[...new Set([1,2,3,3,4,4])] //[1,2,3,4]
  • set是ES6新出来的一种定义不重复数组的数据类型。
  • Array.from是将类数组转化为数组
  • ...是扩展运算符,将set里面的值转化为字符串

es5实现,使用对象去重:

Array.prototype.distinct = function() {
    const map = {}
    const result = []
    for (const n of this) {
        if (!(n in map)) {
            map[n] = 1
            result.push(n)
        }
    }
    return result
}
[1,2,3,3,4,4].distinct(); //[1,2,3,4]

7.3 排序

数组的sort方法:

[1,2,3,4].sort((a, b) => a - b); // [1, 2,3,4],默认是升序
[1,2,3,4].sort((a, b) => b - a); // [4,3,2,1] 降序

冒泡排序:

Array.prototype.bubleSort=function () {
    let arr=this,
        len = arr.length;
    for (let outer = len; outer >= 2; outer--) {
      for (let inner = 0; inner <= outer - 1; inner++) {
        if (arr[inner] > arr[inner + 1]) {
          //升序
          [arr[inner], arr[inner + 1]] = [arr[inner + 1], arr[inner]];
          console.log([arr[inner], arr[inner + 1]]);
        }
      }
    }
    return arr;
  }
[1,2,3,4].bubleSort() //[1,2,3,4]    

选择排序:

Array.prototype.selectSort = function () {
  let arr = this
  let len = arr.length
  for (let i = 0, len = arr.length; i < len; i++) {
    for (let j = i, len = arr.length; j < len; j++) {
      if (arr[i] > arr[j]) {
        [arr[i], arr[j]] = [arr[j], arr[i]]
      }
    }
  }
  return arr
}
[1, 2, 3, 4].selectSort() // [1,2,3,4]

7.4 最大值

Math.max(...[1,2,3,4]) //4
Math.max.apply(this,[1,2,3,4]) //4
[1,2,3,4].reduce( (prev, cur,curIndex,arr)=> {
 return Math.max(prev,cur);
},0) //4

7.5 合并

[1,2,3,4].concat([5,6]) //[1,2,3,4,5,6]
[...[1,2,3,4],...[4,5]] //[1,2,3,4,5,6]
let arrA = [1, 2], arrB = [3, 4]
Array.prototype.push.apply(arrA, arrB))//arrA值为[1,2,3,4]

7.6 判断是否包含值

[1,2,3].includes(4) //false
[1,2,3].indexOf(4) //-1 如果存在换回索引
[1, 2, 3].find((item)=>item===3)) //3 如果数组中无值返回undefined
[1, 2, 3].findIndex((item)=>item===3)) //2 如果数组中无值返回-1
[1,2,3].some(item=>{
  return item===3
}) //true 如果不包含返回false

7.7 类数组转化

  • 类数组:表示有length属性,但是不具备数组的方法
  • call,apply:是改变slice里面的this指向arguments,所以arguments也可调用数组的方法
  • Array.from是将类似数组或可迭代对象创建为数组
  • ...是将类数组扩展为字符串,再定义为数组
Array.prototype.slice.call(arguments) //arguments是类数组(伪数组)
Array.prototype.slice.apply(arguments)
Array.from(arguments)
[...arguments]

 

8 HTTP

8.1 请求过程

HTTP(S) 请求地址 → DNS 解析 → 三次握手 → 发送请求 → 四次挥手

三次握手过程:

四次挥手过程:

8.2 HTTP协议

版本内容
http0.9只允许客户端发送 GET 这一种请求;且不支持请求头,协议只支持纯文本;无状态性,每个访问独立处理,完成断开;无状态码
http1.0解决 0.9 的缺点,增加 If-modify-since(last-modify)和 expires 缓存属性
http1.x增加 cache-control 和 If-none-match(etag)缓存属性
http2.0采用二进制格式传输;多路复用;报头压缩;服务器推送
http3.0采用 QUIC 协议,自定义连接机制;自定义重传机制;无阻塞的多路复用

缓存:

类型特性
强缓存通过 If-modify-since(last-modify)、expires 和 cache-control 设置,属性值是时间,所以在时间内不用请求
协商缓存通过 If-none-match(etag)设置,etag 属性是哈希值,所以要请求和服务器值对比

8.3 状态码

序列详情
1XX(通知) 
2XX(成功)301(永久移动)、302(临时移动)、303(查看其他位置)、304(未修改)、305(使用代理)、307(临时重定向)
3XX(重定向)301(永久移动)、302(临时移动)、303(查看其他位置)、304(未修改)、305(使用代理)、307(临时重定向)
4XX(客户端错误)400(错误请求)、401(未授权)、403(禁止)、404(未找到)、405(方法禁用)、406(不接受)、407(需要代理授权)
5XX(服务器错误)500(服务器异常)、501(尚未实施)、502(错误网关)、503(服务不可用)、504(网关超时)、505(HTTP 版本不受支持)

9、Proxy

先来做一道题:

提供一个createArr()方法,用此方法创建的数组满足arr[-1] = arr[arr.length - 1]

  • 可以使用Proxy代理每次传入进来的下标,也就是重写一下数组的get方法,在这个方法中我们去处理这方面的逻辑,一起来看看代码吧
function createArr (...elements) {
  let handler = {
    get (target, key, receiver) { // 第三个参数传不传都可以
      let index = Number(key) // 或者 let index = ~~key
      if (index < 0) {
        index = String(target.length + index)
      }
      return Reflect.get(target, index, receiver)
    }
  }
  let target = [...elements] // 创建一个新数组
  return new Proxy(target, handler)
}
var arr1 = createArr(1, 2, 3)
console.log(arr1[-1]) // 3
console.log(arr1[-2]) // 2

注意点:

  • get接收到的第二个参数key表示的是数组下标,它是字符串形式,所以需要转为Number。
  • 接下来只需要判断一下传入进来的下标是不是小于0的,小于0的话加上数组的长度就可以了。
  • 然后返回index这一项使用的是Reflect.get(target, index)。什么?为什么不直接用target[index]?当然这样也可以,对比target[index]的区别就是Reflect.get(target, index)如果传入的target不是一个Object的话(数组也属于对象),就会报一个类型错误TypeError,而target[index]返回的会是一个undefined。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值