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 支持更多的编码类型且不对数据类型限制


someone
218 声望7 粉丝

前端工程师