乐趣区

随手记

JavaScript 基础

1. 引用类型和基本数据类型有什么不同?

 基本数据类型有 null,undefined、string、number、boolean;引用类型有 Object、Array、Date、RegExp、Function;基本数据类型的值是不可变的,引用类型是可变的。

2. 什么是事件委托?

 如将 onclick、onmouseover 等事件委托到一个目标节点的父节点上来实现多个事件只需绑定一次。例如 list 中每一个元素都需要注册点击事件,可以将事件委托到它们的父节点上再通过 target 属性获取到当前点击的是哪一个子节点。事件委托的原理是通过事件冒泡的原理来实现的。

3. 数据双向绑定的原理是什么?

vue 中数据绑定的原理使用的是 Object.defineProperty() 这个 api 实现的。该 api 提供了 getter 和 setter 两个方法。当调用该属性或则重新赋值时会分别触发这两个方法。

4.Object.defineProperty() 有什么缺陷?

Js 是可以动态的为对像增加新属性或删除原有属性,但是该 API 并不能检测到新增或删除。为了弥补这个缺陷可以使用 ES6 的 Proxy 来代替,目前 vue3.0 已经有部分数据响应使用了 Proxy

5.typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?

 对于基本数据类型除了 null 以外都可以使用 typeof 得到数据类型,typeof null 会返回 object; 对于引用类型除了函数会返回 function 外,其他都会返回 object; 如果想要返回一个对象的正确类型用 instanceof,instanceof 是通过原型链判断的。

6. 如何正确判断 this?箭头函数的 this 是什么?

  谁调用了函数,谁就是 this;如果直接调用函数,那么 this 指向 window;对于 new 函数来说,this 绑定在了它创建的实例上;箭头函数的 this 取决于包裹它的第一个函数。

7. 什么是闭包?

 闭包是一个函数嵌套了另一个函数,内部函数可以访问外部函数的变量,并以返回值返回内部函数。这样当外部函数执行完毕销毁后仍然可以访问它的变量,且只能通过返回的函数访问从而实现了私有变量和方法。但是也正因为闭包的作用可以使数据保留在内存中,所以滥用闭包会内存占用过多影响性能,当确定已经不再使用时可以将数据设为 null。

8. 什么是深浅拷贝?如何实现深浅拷贝?

 浅拷贝拷贝的是对象的内存指针而不是内存本身,被拷贝的对象和对象共享内存,当其中一个改变时,另外一个也会受到影响。而深拷贝指的是内存本身。深浅拷贝只针对引用数据类型。
// 浅拷贝实现方式 Object.assgin()
let person = {
    name: 'louis',
    age: 50
}
let ahother = Object.assgin({}, person);
ahother = {...person} // ... 运费符也可以实现
console.log(another); // louis, 50
// 深拷贝实现方式
// 注意只有当对象中没有 undefined/symbol
let person = {
    name: 'louis',
    age: 50,
    address: {
        street: 'swswdad',
        number: 123
    }
}
let ahother = JSON.parse(JSON.stringify(person));

9. 如何理解原型和原型链?

 所有的对象都有一个__proto__的隐式原型属性属性,指向创建该对象的构造函数的原型; 如果想在构造函数中添加自定义的方法还可以用 

xx.constructor.method 来添加。

 所有的函数都有一个 prototype,它是一个对象仅有 constructor 一个属性且不能枚举,如果重写了它的 prototype 那该属性也会被重写。

函数的 prototype.constructor 也有一个__proto__ 来链接函数的原型。

 所谓原型链就是通过__proto__来链接它的 prototype 形成了原型链 
function Person() {this.country = 'china';}
let louis = new Person();
// louis 的原型属性指向了 Person.prototype, 通过 constructor 可以知道该实例来自哪个原型
// Person.prototype 也是一个对象,该对象也有 constructor 指向了 Object 
 因此可以知道,所有的对象通过他们的__proto__一层层链式查询会发现它们最终来自于 Object,也就是说所有的对象继承了 Object。Object.prototype 和 Function.prototype 是由 js 引擎创建的
函数的 prototype 是一个对象,也就是原型,然后又通过对象的__proto__将对象和原型链接形成了原型链

10. 原型如何实现继承?

 简言之就是子类的构造方法继承父类的构造方法,子类的原型继承父类的原型 具体如下代码 
function Parent() {this.a = 'a';}
Parent.prototype.getAddress = function() {console.log('address');
}
function Child() {Parent.call(this);
}
Child.prototype = Object.create(Parent.prototype, {
    constructor: {
        value: Child,
        enumerable: false,
        configurable: true,
        writable: true
    }
});

11.apply、call、bind 的内部实现是怎么样的?区别是什么?

 这三个方法本质上都是改变了 this 的指向,可以让新的对象调用函数方法并传入参数;apply 的第二个参数是以数组传递,call 可以接受多个参数。apply 和 call 的方法是立即调用,bind 则需要绑定后再调用一次。
function sayName(name) {console.log(name);
}
let a;
sayName.call(a, 'louis');
sayName.apply(a, ['louis']);
let b = sayName({}, 'louis');
b(); // louis

手动实现 call()

Function.prototype.myCall = function(context) {if (typeof this !== 'function') {return new TypeError('Error');
    }
    context = context || window; // 如果没有传入新的 this,则指向 window
    contxet.fn = this;
    const args = [...arguments].slice(1); // 取第二个开始的参数
    const result = context.fn(args);
    delete context.fn;
    return result;
}
// 调用 mycall
let callObj = {
    name: 'call',
    sayName: function(arg) {console.log(this.name, arg);
    }
}
// 创建劫持实例
let cb = {
    name: 'louis',
    mood: 'f**'
}
callObj.sayName.myCall(cb, cb.mood); // this 已经指向 cb 实例 louis f**

手写实现 apply()

Function.prototype.myApply = function(context) {if (typeof context !== 'function') {return new TypeError('Error');
    }
    context = context || window;
    context.fn = this;
    const agrs = [...arguments].slice(1);
    let result;
    if (arguments[1]) {
        // 传入的参数存在
        result = context.fn(...args);
    } else {result = context.fn(); 
    }
    delete context.fn;
    return result;
}

手写实现 bind()

Function.prototype.myBind = function(context) {if (typeof this !== 'function') {return TypeError('Error');
    }
    const self = this; // 将被调用的函数保存为 self
    context = context || window;
    const args = [...arguments].slice(1);
    return function F() {if (this instanceof F) {return new self(...args, ...arguments);
        } else {return self.apply(context, args.concat(...arguments));
        }
    }
}

12.new 的过程中实现了什么?手动实现 new

1. 返回了一个新对象
2. 将对象链接到原型
3. 绑定了 this

13. 函数的节流与防抖


节流: 假设输入框连续输入触发联想搜索 或按钮频繁点击时都会触发请求,为提高性能可以使用节流函数限制频繁发送请求
function throttle(fn, interval) {
    let timer; // 创建倒计时
    let args = [].call
    return function() {if (timer) {return false; // 限制时间未结束}
        let self = this;
        timer = setTimeout(()=> {clearTimeout(timer); // 超过限制时间清除
            timer = null; // 重新将 timer 赋值为空
            fn.call(self); // 执行回调
        }, interval);
    }
}
 防抖: 当用户短时间内频繁进行操作时显示后续的回调在用户操作结束后才可以执行; 就好比用手去按住弹簧,只有当手放开时弹簧才会弹起, 这个弹起则是后续的操作
function deboundce(fn, interval) {
    let timer;
    return function() {
        let self = this;
        clearTimeout(timer); // 每次触发都会清空定时器
        timer = setTimeout(()=> {fn.call(self); // 最后一次触发的定时器结束后才会执行回调
        }, interval)
    }
}
 节流和防抖看起来很相似,都是通过定时器来延时执行。区别在于节流是每隔一个固定的时间段才会触发一次。防抖则是规定时间段内只会执行 1 次

HTTP

1、post 和 get 请求

 在语言上,get 是用于向服务器获取数据,post 请求用于向服务器提交数据。所以并不是说 post 更做的事情 get 就无法满足,只是用于规范我们应该使用更合理的请求方式。误区 1: 并不是 get 请求的参数会暴露在浏览器地址上就说明 post 请求更安全;不管是哪种请求通过控制台或者抓包都会将数据暴露,要实现真正的安全只能通过 https 请求。误区 2:get 请求的 url 长度有限制;通常这个限制是来自于服务器而不是 RFC 规定,不同的浏览器对长度的限制也不同

get 请求可以能缓存,post 不会。Post 支持更多的编码类型且不对数据类型限制

退出移动版