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。