关于前端:字节前端面试题

55次阅读

共计 21499 个字符,预计需要花费 54 分钟才能阅读完成。

寄生组合继承

题目形容: 实现一个你认为不错的 js 继承形式

实现代码如下:

function Parent(name) {
  this.name = name;
  this.say = () => {console.log(111);
  };
}
Parent.prototype.play = () => {console.log(222);
};
function Children(name) {Parent.call(this);
  this.name = name;
}
Children.prototype = Object.create(Parent.prototype);
Children.prototype.constructor = Children;
// let child = new Children("111");
// // console.log(child.name);
// // child.say();
// // child.play();

—- 问题知识点分割线 —-

Webpack Proxy 工作原理?为什么能解决跨域

1. 是什么

webpack proxy,即 webpack 提供的代理服务

根本行为就是接管客户端发送的申请后转发给其余服务器

其目标是为了便于开发者在开发模式下解决跨域问题(浏览器安全策略限度)

想要实现代理首先须要一个两头服务器,webpack中提供服务器的工具为webpack-dev-server

2. webpack-dev-server

webpack-dev-serverwebpack 官网推出的一款开发工具,将主动编译和主动刷新浏览器等一系列对开发敌对的性能全副集成在了一起

目标是为了进步开发者日常的开发效率,「只实用在开发阶段」

对于配置方面,在 webpack 配置对象属性中通过 devServer 属性提供,如下:

// ./webpack.config.js
const path = require('path')

module.exports = {
    // ...
    devServer: {contentBase: path.join(__dirname, 'dist'),
        compress: true,
        port: 9000,
        proxy: {
            '/api': {target: 'https://api.github.com'}
        }
        // ...
    }
}

devServetr外面 proxy 则是对于代理的配置,该属性为对象的模式,对象中每一个属性就是一个代理的规定匹配

属性的名称是须要被代理的申请门路前缀,个别为了分别都会设置前缀为/api,值为对应的代理匹配规定,对应如下:

  • target:示意的是代理到的指标地址
  • pathRewrite:默认状况下,咱们的 /api-hy 也会被写入到 URL 中,如果心愿删除,能够应用pathRewrite
  • secure:默认状况下不接管转发到 https 的服务器上,如果心愿反对,能够设置为false
  • changeOrigin:它示意是否更新代理后申请的 headershost 地址

2. 工作原理

proxy工作原理本质上是利用 http-proxy-middleware 这个http 代理中间件,实现申请转发给其余服务器

举个例子:

在开发阶段,本地地址为 http://localhost:3000,该浏览器发送一个前缀带有/api 标识的申请到服务端获取数据,但响应这个申请的服务器只是将申请转发到另一台服务器中

const express = require('express');
const proxy = require('http-proxy-middleware');

const app = express();

app.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));
app.listen(3000);

// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar

3. 跨域

在开发阶段,webpack-dev-server 会启动一个本地开发服务器,所以咱们的利用在开发阶段是独立运行在 localhost的一个端口上,而后端服务又是运行在另外一个地址上

所以在开发阶段中,因为浏览器同源策略的起因,当本地拜访后端就会呈现跨域申请的问题

通过设置 webpack proxy 实现代理申请后,相当于浏览器与服务端中增加一个代理者

当本地发送申请的时候,代理服务器响应该申请,并将申请转发到指标服务器,指标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地

在代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候浏览器就能失常接收数据

留神:「服务器与服务器之间申请数据并不会存在跨域行为,跨域行为是浏览器安全策略限度」

—- 问题知识点分割线 —-

Compositon api

Composition API也叫组合式 API,是 Vue3.x 的新个性。

通过创立 Vue 组件,咱们能够将接口的可重复部分及其性能提取到可重用的代码段中。仅此一项就能够使咱们的应用程序在可维护性和灵活性方面走得更远。然而,咱们的教训曾经证实,光靠这一点可能是不够的,尤其是当你的应用程序变得十分大的时候——想想几百个组件。在解决如此大的应用程序时,共享和重用代码变得尤为重要

  • Vue2.0 中,随着性能的减少,组件变得越来越简单,越来越难保护,而难以保护的根本原因是 Vue 的 API 设计迫使开发者应用 watch,computed,methods 选项组织代码,而不是理论的业务逻辑。
  • 另外 Vue2.0 短少一种较为简洁的低成本的机制来实现逻辑复用,尽管能够 minxis 实现逻辑复用,然而当 mixin 变多的时候,会使得难以找到对应的 data、computed 或者 method 来源于哪个mixin,使得类型推断难以进行。
  • 所以 Composition API 的呈现,次要是也是为了解决 Option API 带来的问题,第一个是代码组织问题,Compostion API能够让开发者依据业务逻辑组织本人的代码,让代码具备更好的可读性和可扩展性,也就是说当下一个开发者接触这一段不是他本人写的代码时,他能够更好的利用代码的组织反推出理论的业务逻辑,或者依据业务逻辑更好的了解代码。
  • 第二个是实现代码的逻辑提取与复用,当然 mixin 也能够实现逻辑提取与复用,然而像后面所说的,多个 mixin 作用在同一个组件时,很难看出 property 是来源于哪个 mixin,起源不分明,另外,多个mixinproperty存在变量命名抵触的危险。而 Composition API 刚好解决了这两个问题。

艰深的讲:

没有 Composition API 之前 vue 相干业务的代码须要配置到 option 的特定的区域,中小型我的项目是没有问题的,然而在大型项目中会导致前期的维护性比较复杂,同时代码可复用性不高。Vue3.x 中的 composition-api 就是为了解决这个问题而生的

compositon api 提供了以下几个函数:

  • setup
  • ref
  • reactive
  • watchEffect
  • watch
  • computed
  • toRefs
  • 生命周期的hooks

都说 Composition API 与 React Hook 很像,说说区别

从 React Hook 的实现角度看,React Hook 是依据 useState 调用的程序来确定下一次重渲染时的 state 是来源于哪个 useState,所以呈现了以下限度

  • 不能在循环、条件、嵌套函数中调用 Hook
  • 必须确保总是在你的 React 函数的顶层调用 Hook
  • useEffect、useMemo等函数必须手动确定依赖关系

而 Composition API 是基于 Vue 的响应式零碎实现的,与 React Hook 的相比

  • 申明在 setup 函数内,一次组件实例化只调用一次setup,而 React Hook 每次重渲染都须要调用 Hook,使得 React 的 GC 比 Vue 更有压力,性能也绝对于 Vue 来说也较慢
  • Compositon API的调用不须要顾虑调用程序,也能够在循环、条件、嵌套函数中应用
  • 响应式零碎主动实现了依赖收集,进而组件的局部的性能优化由 Vue 外部本人实现,而 React Hook 须要手动传入依赖,而且必须必须保障依赖的程序,让 useEffectuseMemo 等函数正确的捕捉依赖变量,否则会因为依赖不正确使得组件性能降落。

尽管 Compositon API 看起来比 React Hook 好用,然而其设计思维也是借鉴 React Hook 的。

—- 问题知识点分割线 —-

深浅拷贝

浅拷贝:只思考对象类型。

function shallowCopy(obj) {if (typeof obj !== 'object') return

    let newObj = obj instanceof Array ? [] : {}
    for (let key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = obj[key]
        }
    }
    return newObj
}

简略版深拷贝:只思考一般对象属性,不思考内置对象和函数。

function deepClone(obj) {if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
        }
    }
    return newObj;
}

简单版深克隆:基于简略版的根底上,还思考了内置对象比方 Date、RegExp 等对象和函数以及解决了循环援用的问题。

const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;

function deepClone(target, map = new WeakMap()) {if (map.get(target)) {return target;}
    // 获取以后值的构造函数:获取它的类型
    let constructor = target.constructor;
    // 检测以后对象 target 是否与正则、日期格局对象匹配
    if (/^(RegExp|Date)$/i.test(constructor.name)) {// 创立一个新的非凡对象 (正则类 / 日期类) 的实例
        return new constructor(target);  
    }
    if (isObject(target)) {map.set(target, true);  // 为循环援用的对象做标记
        const cloneTarget = Array.isArray(target) ? [] : {};
        for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop], map);
            }
        }
        return cloneTarget;
    } else {return target;}
}

—- 问题知识点分割线 —-

viewport

<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
    // width    设置 viewport 宽度,为一个正整数,或字符串‘device-width’// device-width  设施宽度
    // height   设置 viewport 高度,个别设置了宽度,会主动解析出高度,能够不必设置
    // initial-scale    默认缩放比例(初始缩放比例),为一个数字,能够带小数
    // minimum-scale    容许用户最小缩放比例,为一个数字,能够带小数
    // maximum-scale    容许用户最大缩放比例,为一个数字,能够带小数
    // user-scalable    是否容许手动缩放
  • 延长发问

    • 怎么解决 挪动端 1px 被 渲染成 2px问题

部分解决

  • meta标签中的 viewport属性,initial-scale 设置为 1
  • rem依照设计稿规范走,外加利用transfromescale(0.5) 放大一倍即可;

全局解决

  • mate标签中的 viewport属性,initial-scale 设置为 0.5
  • rem 依照设计稿规范走即可

—- 问题知识点分割线 —-

如果 new 一个箭头函数的会怎么样

箭头函数是 ES6 中的提出来的,它没有 prototype,也没有本人的 this 指向,更不能够应用 arguments 参数,所以不能 New 一个箭头函数。

new 操作符的实现步骤如下:

  1. 创立一个对象
  2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的 prototype 属性)
  3. 指向构造函数中的代码,构造函数中的 this 指向该对象(也就是为这个对象增加属性和办法)
  4. 返回新的对象

所以,下面的第二、三步,箭头函数都是没有方法执行的。

—- 问题知识点分割线 —-

Promise 的根本用法

(1)创立 Promise 对象

Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已胜利)和 rejected(已失败)。

Promise 构造函数承受一个函数作为参数,该函数的两个参数别离是 resolvereject

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作胜利 */){resolve(value);
  } else {reject(error);
  }
});

个别状况下都会应用 new Promise() 来创立 promise 对象,然而也能够应用 promise.resolvepromise.reject这两个办法:

  • Promise.resolve

Promise.resolve(value)的返回值也是一个 promise 对象,能够对返回值进行.then 调用,代码如下:

Promise.resolve(11).then(function(value){console.log(value); // 打印出 11
});

resolve(11)代码中,会让 promise 对象进入确定 (resolve 状态),并将参数 11 传递给前面的 then 所指定的onFulfilled 函数;

创立 promise 对象能够应用 new Promise 的模式创建对象,也能够应用 Promise.resolve(value) 的模式创立 promise 对象;

  • Promise.reject

Promise.reject 也是 new Promise 的快捷模式,也创立一个 promise 对象。代码如下:

Promise.reject(new Error(“我错了,请原谅俺!!”));

就是上面的代码 new Promise 的简略模式:

new Promise(function(resolve,reject){reject(new Error("我错了!"));
});

上面是应用 resolve 办法和 reject 办法:

function testPromise(ready) {return new Promise(function(resolve,reject){if(ready) {resolve("hello world");
    }else {reject("No thanks");
    }
  });
};
// 办法调用
testPromise(true).then(function(msg){console.log(msg);
},function(error){console.log(error);
});

下面的代码的含意是给 testPromise 办法传递一个参数,返回一个 promise 对象,如果为 true 的话,那么调用 promise 对象中的 resolve() 办法,并且把其中的参数传递给前面的 then 第一个函数内,因而打印出“hello world”, 如果为 false 的话,会调用 promise 对象中的 reject() 办法,则会进入 then 的第二个函数内,会打印No thanks

(2)Promise 办法

Promise 有五个罕用的办法:then()、catch()、all()、race()、finally。上面就来看一下这些办法。

  1. then()

当 Promise 执行的内容合乎胜利条件时,调用 resolve 函数,失败就调用 reject 函数。Promise 创立完了,那该如何调用呢?

promise.then(function(value) {// success}, function(error) {// failure});

then办法能够承受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 resolved 时调用,第二个回调函数是 Promise 对象的状态变为 rejected 时调用。其中第二个参数能够省略。then办法返回的是一个新的 Promise 实例(不是原来那个 Promise 实例)。因而能够采纳链式写法,即 then 办法前面再调用另一个 then 办法。

当要写有程序的异步事件时,须要串行时,能够这样写:

let promise = new Promise((resolve,reject)=>{ajax('first').success(function(res){resolve(res);
    })
})
promise.then(res=>{return new Promise((resovle,reject)=>{ajax('second').success(function(res){resolve(res)
        })
    })
}).then(res=>{return new Promise((resovle,reject)=>{ajax('second').success(function(res){resolve(res)
        })
    })
}).then(res=>{})

那当要写的事件没有程序或者关系时,还如何写呢?能够应用all 办法来解决。

2. catch()

Promise 对象除了有 then 办法,还有一个 catch 办法,该办法相当于 then 办法的第二个参数,指向 reject 的回调函数。不过 catch 办法还有一个作用,就是在执行 resolve 回调函数时,如果呈现谬误,抛出异样,不会进行运行,而是进入 catch 办法中。

p.then((data) => {console.log('resolved',data);
},(err) => {console.log('rejected',err);
     }
); 
p.then((data) => {console.log('resolved',data);
}).catch((err) => {console.log('rejected',err);
});

3. all()

all办法能够实现并行任务,它接管一个数组,数组的每一项都是一个 promise 对象。当数组中所有的 promise 的状态都达到 resolved 的时候,all办法的状态就会变成 resolved,如果有一个状态变成了rejected,那么all 办法的状态就会变成rejected

javascript
let promise1 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(1);
    },2000)
});
let promise2 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(2);
    },1000)
});
let promise3 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(3);
    },3000)
});
Promise.all([promise1,promise2,promise3]).then(res=>{console.log(res);
    // 后果为:[1,2,3] 
})

调用 all 办法时的后果胜利的时候是回调函数的参数也是一个数组,这个数组按程序保留着每一个 promise 对象 resolve 执行时的值。

(4)race()

race办法和 all 一样,承受的参数是一个每项都是 promise 的数组,然而与 all 不同的是,当最先执行完的事件执行完之后,就间接返回该 promise 对象的值。如果第一个 promise 对象状态变成 resolved,那本身的状态变成了resolved;反之第一个promise 变成rejected,那本身状态就会变成rejected

let promise1 = new Promise((resolve,reject)=>{setTimeout(()=>{reject(1);
    },2000)
});
let promise2 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(2);
    },1000)
});
let promise3 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(3);
    },3000)
});
Promise.race([promise1,promise2,promise3]).then(res=>{console.log(res);
    // 后果:2
},rej=>{console.log(rej)};
)

那么 race 办法有什么理论作用呢?当要做一件事,超过多长时间就不做了,能够用这个办法来解决:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

5. finally()

finally办法用于指定不论 Promise 对象最初状态如何,都会执行的操作。该办法是 ES2018 引入规范的。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

下面代码中,不论 promise 最初的状态,在执行完 thencatch指定的回调函数当前,都会执行 finally 办法指定的回调函数。

上面是一个例子,服务器应用 Promise 解决申请,而后应用 finally 办法关掉服务器。

server.listen(port)
  .then(function () {// ...})
  .finally(server.stop);

finally办法的回调函数不承受任何参数,这意味着没有方法晓得,后面的 Promise 状态到底是 fulfilled 还是 rejected。这表明,finally 办法外面的操作,应该是与状态无关的,不依赖于 Promise 的执行后果。finally实质上是 then 办法的特例:

promise
.finally(() => {// 语句});
// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

下面代码中,如果不应用 finally 办法,同样的语句须要为胜利和失败两种状况各写一次。有了 finally 办法,则只须要写一次。

—- 问题知识点分割线 —-

谈一谈 GET 和 POST 的区别

实质上,只是语义上的区别,GET 用于获取资源,POST 用于提交资源。

具体差异👇

  • 从缓存角度看,GET 申请后浏览器会被动缓存,POST 默认状况下不能。
  • 从参数角度来看,GET 申请个别放在 URL 中,因而不平安,POST 申请放在申请体中,相对而言较为平安,然而在抓包的状况下都是一样的。
  • 从编码角度看,GET 申请只能经行 URL 编码,只能承受 ASCII 码,而 POST 反对更多的编码类型且不对数据类型限值。
  • GET 申请幂等,POST 申请不幂等,幂等指发送 M 和 N 次申请(两者不雷同且都大于 1),服务器上资源的状态统一。
  • GET 申请会一次性发送申请报文,POST 申请通常分为两个 TCP 数据包,首先发 header 局部,如果服务器响应 100(continue),而后发 body 局部。

—- 问题知识点分割线 —-

组件之间通信

  • 父子组件通信
  • 自定义事件
  • redux 和 context

context 如何使用

  • 父组件向其下所有子孙组件传递信息
  • 如一些简略的信息:主题、语言
  • 简单的公共信息用 redux

在跨层级通信中,次要分为一层或多层的状况

  • 如果只有一层,那么依照 React 的树形构造进行分类的话,次要有以下三种状况:父组件向子组件通信 子组件向父组件通信 以及 平级的兄弟组件间相互通信
  • 在父与子的状况下,因为 React 的设计实际上就是传递 Props 即可。那么场景体现在容器组件与展现组件之间,通过 Props 传递 state,让展现组件受控。
  • 在子与父的状况下 ,有两种形式,别离是回调函数与实例函数。回调函数,比方输入框向父级组件返回输出内容,按钮向父级组件传递点击事件等。实例函数的状况有些特地,次要是在父组件中 通过 React 的 ref API 获取子组件的实例 ,而后是 通过实例调用子组件的实例函数。这种形式在过来常见于 Modal 框的显示与暗藏
  • 多层级间的数据通信,有两种状况。第一种是一个容器中蕴含了多层子组件,须要最底部的子组件与顶部组件进行通信。在这种状况下,如果一直透传 Props 或回调函数,不仅代码层级太深,后续也很不好保护。第二种是两个组件不相干,在整个 React 的组件树的两侧,齐全不相交。那么基于多层级间的通信个别有三个计划。

    • 第一个是应用 React 的 Context API,最常见的用处是做语言包国际化
    • 第二个是应用全局变量与事件。
    • 第三个是应用状态治理框架,比方 Flux、Redux 及 Mobx。长处是因为引入了状态治理,使得我的项目的开发模式与代码构造得以束缚,毛病是学习老本绝对较高

—- 问题知识点分割线 —-

Promise 是什么,解决了什么,之前怎么实现的

    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更正当和更弱小。解决来之前在申请中回调申请产生的回调天堂,使得当初的代码更加正当更加优雅,也更加容易定位查找问题。

—- 问题知识点分割线 —-

Sass、Less 是什么?为什么要应用他们?

他们都是 CSS 预处理器,是 CSS 上的一种形象层。他们是一种非凡的语法 / 语言编译成 CSS。例如 Less 是一种动静款式语言,将 CSS 赋予了动静语言的个性,如变量,继承,运算,函数,LESS 既能够在客户端上运行 (反对 IE 6+, Webkit, Firefox),也能够在服务端运行 (借助 Node.js)。

为什么要应用它们?

  • 构造清晰,便于扩大。能够不便地屏蔽浏览器公有语法差别。封装对浏览器语法差别的反复解决,缩小无意义的机械劳动。
  • 能够轻松实现多重继承。齐全兼容 CSS 代码,能够不便地利用到老我的项目中。LESS 只是在 CSS 语法上做了扩大,所以老的 CSS 代码也能够与 LESS 代码一起编译。

—- 问题知识点分割线 —-

如何判断一个对象是否属于某个类?

  • 第一种形式,应用 instanceof 运算符来判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。
  • 第二种形式,通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,然而这种形式不是很平安,因为 constructor 属性能够被改写。
  • 第三种形式,如果须要判断的是某个内置的援用类型的话,能够应用 Object.prototype.toString() 办法来打印对象的[[Class]] 属性来进行判断。

—- 问题知识点分割线 —-

JavaScript 类数组对象的定义?

一个领有 length 属性和若干索引属性的对象就能够被称为类数组对象,类数组对象和数组相似,然而不能调用数组的办法。常见的类数组对象有 arguments 和 DOM 办法的返回后果,还有一个函数也能够被看作是类数组对象,因为它含有 length 属性值,代表可接管的参数个数。

常见的类数组转换为数组的办法有这样几种:

(1)通过 call 调用数组的 slice 办法来实现转换

Array.prototype.slice.call(arrayLike);

(2)通过 call 调用数组的 splice 办法来实现转换

Array.prototype.splice.call(arrayLike, 0);

(3)通过 apply 调用数组的 concat 办法来实现转换

Array.prototype.concat.apply([], arrayLike);

(4)通过 Array.from 办法来实现转换

Array.from(arrayLike);

—- 问题知识点分割线 —-

渲染引擎什么状况下才会为特定的节点创立新的图层

层叠上下文 是 HTML 元素的三维概念,这些 HTML 元素在一条假想的绝对于面向(电脑屏幕的)视窗或者网页的用户的 z 轴上延长,HTML 元素根据其本身属性依照优先级程序占用层叠上下文的空间。

  1. 领有层叠上下文属性的元素会被晋升为独自的一层。

领有层叠上下文属性:

  • 根元素 (HTML),
  • z-index 值不为 “auto” 的 相对 / 绝对定位元素,
  • position, 固定(fixed)/ 沾滞(sticky)定位(沾滞定位适配所有挪动设施上的浏览器,但老的桌面浏览器不反对)
  • z-index 值不为 “auto” 的 flex 子项 (flex item),即:父元素 display: flex|inline-flex,
  • z-index 值不为 ”auto” 的 grid 子项,即:父元素 display:grid
  • opacity 属性值小于 1 的元素(参考 the specification for opacity),
  • transform 属性值不为 “none” 的元素,
  • mix-blend-mode 属性值不为 “normal” 的元素,
  • filter 值不为 ”none” 的元素,
  • perspective 值不为 ”none” 的元素,
  • clip-path 值不为 ”none” 的元素
  • mask / mask-image / mask-border 不为 ”none” 的元素
  • isolation 属性被设置为 “isolate” 的元素
  • 在 will-change 中指定了任意 CSS 属性(参考 这篇文章)
  • -webkit-overflow-scrolling 属性被设置 “touch” 的元素
  • contain 属性值为 ”layout”,”paint”,或者综合值比方 ”strict”,”content”
  • 须要剪裁(clip)的中央也会被创立为图层。

这里的剪裁指的是,如果咱们把 div 的大小限定为 200 200 像素,而 div 外面的文字内容比拟多,文字所显示的区域必定会超出 200 200 的面积,这时候就产生了剪裁,渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域。呈现这种裁剪状况的时候,渲染引擎会为文字局部独自创立一个层,如果呈现滚动条,滚动条也会被晋升为独自的层。

—- 问题知识点分割线 —-

apply/call/bind 原理

call、applybind 是挂在 Function 对象上的三个办法,调用这三个办法的必须是一个函数。

func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2,...])
func.bind(thisArg, param1, param2, ...)
  • 在浏览器里,在全局范畴内 this 指向 window 对象;
  • 在函数中,this 永远指向最初调用他的那个对象;
  • 构造函数中,this 指向 new 进去的那个新的对象;
  • call、apply、bind中的 this 被强绑定在指定的那个对象上;
  • 箭头函数中 this 比拟非凡, 箭头函数 this 为父作用域的 this,不是调用时的 this. 要晓得前四种形式, 都是调用时确定, 也就是动静的, 而箭头函数的 this 指向是动态的, 申明的时候就确定了下来;
  • apply、call、bind都是 js 给函数内置的一些 API,调用他们能够为函数指定 this 的执行, 同时也能够传参。
let a = {value: 1}
function getValue(name, age) {console.log(name)
    console.log(age)
    console.log(this.value)
}
getValue.call(a, 'poe', '24')
getValue.apply(a, ['poe', '24'])

bind 和其余两个办法作用也是统一的,只是该办法会返回一个函数。并且咱们能够通过 bind 实现柯里化

办法的利用场景

上面几种利用场景,你多加领会就能够发现它们的理念都是“借用”办法的思路。咱们来看看都有哪些。

  1. 判断数据类型

Object.prototype.toString 来判断类型是最合适的,借用它咱们简直能够判断所有类型的数据

function getType(obj){
  let type  = typeof obj;
  if (type !== "object") {return type;}
  return Object.prototype.toString.call(obj).replace(/^$/, '$1');
}
  1. 类数组借用办法

类数组因为不是真正的数组,所有没有数组类型上自带的种种办法,所以咱们就能够利用一些办法去借用数组的办法,比方借用数组的 push 办法,看上面的一段代码。

var arrayLike = { 
  0: 'java',
  1: 'script',
  length: 2
} 
Array.prototype.push.call(arrayLike, 'jack', 'lily'); 
console.log(typeof arrayLike); // 'object'
console.log(arrayLike);
// {0: "java", 1: "script", 2: "jack", 3: "lily", length: 4}

call 的办法来借用 Array 原型链上的 push 办法,能够实现一个 类数组的 push 办法,给 arrayLike 增加新的元素

  1. 获取数组的最大 / 最小值

咱们能够用 apply 来实现数组中判断最大 / 最小值,apply 间接传递数组作为调用办法的参数,也能够缩小一步开展数组,能够间接应用 Math.max、Math.min 来获取数组的最大值 / 最小值,请看上面这段代码。

let arr = [13, 6, 10, 11, 16];
const max = Math.max.apply(Math, arr); 
const min = Math.min.apply(Math, arr);

console.log(max);  // 16
console.log(min);  // 6

实现一个 bind 函数

对于实现以下几个函数,能够从几个方面思考

  • 不传入第一个参数,那么默认为 window
  • 扭转了 this 指向,让新的对象能够执行该函数。那么思路是否能够变成给新的对象增加一个函数,而后在执行完当前删除?
Function.prototype.myBind = function (context) {if (typeof this !== 'function') {throw new TypeError('Error')
  }
  var _this = this
  var args = [...arguments].slice(1)
  // 返回一个函数
  return function F() {// 因为返回了一个函数,咱们能够 new F(),所以须要判断
    if (this instanceof F) {return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}

实现一个 call 函数

Function.prototype.myCall = function (context) {
  var context = context || window
  // 给 context 增加一个属性
  // getValue.call(a, 'pp', '24') => a.fn = getValue
  context.fn = this
  // 将 context 前面的参数取出来
  var args = [...arguments].slice(1)
  // getValue.call(a, 'pp', '24') => a.fn('pp', '24')
  var result = context.fn(...args)
  // 删除 fn
  delete context.fn
  return result
}

实现一个 apply 函数

Function.prototype.myApply = function(context = window, ...args) {
  // this-->func  context--> obj  args--> 传递过去的参数

  // 在 context 上加一个惟一值不影响 context 上的属性
  let key = Symbol('key')
  context[key] = this; // context 为调用的上下文,this 此处为函数,将这个函数作为 context 的办法
  // let args = [...arguments].slice(1)   // 第一个参数为 obj 所以删除, 伪数组转为数组

  let result = context[key](...args); 
  delete context[key]; // 不删除会导致 context 属性越来越多
  return result;
}
// 应用
function f(a,b){console.log(a,b)
 console.log(this.name)
}
let obj={name:'张三'}
f.myApply(obj,[1,2])  //arguments[1]

—- 问题知识点分割线 —-

如何提取高度嵌套的对象里的指定属性?

有时会遇到一些嵌套水平十分深的对象:

const school = {
   classes: {
      stu: {
         name: 'Bob',
         age: 24,
      }
   }
}

像此处的 name 这个变量,嵌套了四层,此时如果依然尝试老办法来提取它:

const {name} = school

显然是不见效的,因为 school 这个对象自身是没有 name 这个属性的,name 位于 school 对象的“儿子的儿子”对象外面。要想把 name 提取进去,一种比拟笨的办法是逐层解构:

const {classes} = school
const {stu} = classes
const {name} = stu
name // 'Bob'

然而还有一种更规范的做法,能够用一行代码来解决这个问题:

const {classes: { stu: { name} }} = school

console.log(name)  // 'Bob'

能够在解构进去的变量名右侧,通过冒号 +{指标属性名}这种模式,进一步解构它,始终解构到拿到指标数据为止。

—- 问题知识点分割线 —-

数组去重

实现代码如下:

function uniqueArr(arr) {return [...new Set(arr)];
}

—- 问题知识点分割线 —-

响应式设计的概念及基本原理

响应式网站设计(Responsive Web design)是一个网站可能兼容多个终端,而不是为每一个终端做一个特定的版本。

对于原理:基本原理是通过媒体查问 (@media) 查问检测不同的设施屏幕尺寸做解决。
对于兼容:页面头部必须有 mate 申明的viewport

<meta name="’viewport’" content="”width=device-width," initial-scale="1." maximum-scale="1,user-scalable=no”"/>

—- 问题知识点分割线 —-

事件机制

涉及面试题:事件的触发过程是怎么样的?晓得什么是事件代理嘛?

1. 简介

事件流是一个事件沿着特定数据结构流传的过程。冒泡和捕捉是事件流在 DOM 中两种不同的流传办法

事件流有三个阶段

  • 事件捕捉阶段
  • 处于指标阶段
  • 事件冒泡阶段

事件捕捉

事件捕捉(event capturing):艰深的了解就是,当鼠标点击或者触发 dom 事件时,浏览器会从根节点开始由外到内进行事件流传,即点击了子元素,如果父元素通过事件捕捉形式注册了对应的事件的话,会先触发父元素绑定的事件

事件冒泡

事件冒泡(dubbed bubbling):与事件捕捉恰恰相反,事件冒泡程序是由内到外进行事件流传,直到根节点

无论是事件捕捉还是事件冒泡,它们都有一个独特的行为,就是事件流传

2. 捕捉和冒泡

<div id="div1">
  <div id="div2"></div>
</div>

<script>
    let div1 = document.getElementById('div1');
    let div2 = document.getElementById('div2');

    div1.onClick = function(){alert('1')
    }

    div2.onClick = function(){alert('2');
    }

</script>

当点击 div2时,会弹出两个弹出框。在 ie8/9/10chrome浏览器,会先弹出”2”再弹出“1”,这就是事件冒泡:事件从最底层的节点向上冒泡流传。事件捕捉则跟事件冒泡相同

W3C 的规范是先捕捉再冒泡,addEventListener的第三个参数决定把事件注册在捕捉(true)还是冒泡(false)

3. 事件对象

4. 事件流阻止

在一些状况下须要阻止事件流的流传,阻止默认动作的产生

  • event.preventDefault():勾销事件对象的默认动作以及持续流传。
  • event.stopPropagation()/ event.cancelBubble = true:阻止事件冒泡。

事件的阻止在不同浏览器有不同解决

  • IE 下应用 event.returnValue= false
  • 在非 IE 下则应用 event.preventDefault()进行阻止

preventDefault 与 stopPropagation 的区别

  • preventDefault通知浏览器不必执行与事件相关联的默认动作(如表单提交)
  • stopPropagation是进行事件持续冒泡,然而对 IE9 以下的浏览器有效

5. 事件注册

  • 通常咱们应用 addEventListener 注册事件,该函数的第三个参数能够是布尔值,也能够是对象。对于布尔值 useCapture 参数来说,该参数默认值为 falseuseCapture 决定了注册的事件是捕捉事件还是冒泡事件
  • 一般来说,咱们只心愿事件只触发在指标上,这时候能够应用 stopPropagation 来阻止事件的进一步流传。通常咱们认为 stopPropagation 是用来阻止事件冒泡的,其实该函数也能够阻止捕捉事件。stopImmediatePropagation 同样也能实现阻止事件,然而还能阻止该事件指标执行别的注册事件
node.addEventListener('click',(event) =>{event.stopImmediatePropagation()
    console.log('冒泡')
},false);
// 点击 node 只会执行下面的函数,该函数不会执行
node.addEventListener('click',(event) => {console.log('捕捉')
},true)

6. 事件委托

  • js 中性能优化的其中一个次要思维是缩小 dom 操作。
  • 节俭内存
  • 不须要给子节点登记事件

假如有 100li,每个 li 有雷同的点击事件。如果为每 个 Li都增加事件,则会造成 dom 拜访次数过多,引起浏览器重绘与重排的次数过多,性能则会升高。应用事件委托则能够解决这样的问题

原理

实现事件委托是利用了事件的冒泡原理实现的。当咱们为最外层的节点增加点击事件,那么外面的 ullia 的点击事件都会冒泡到最外层节点上,委托它代为执行事件

<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
<script>
  window.onload = function(){var ulEle = document.getElementById('ul');
    ul.onclick = function(ev){
        // 兼容 IE
        ev = ev || window.event;
        var target = ev.target || ev.srcElement;

        if(target.nodeName.toLowerCase() == 'li'){alert( target.innerHTML);
        }

    }
  }
</script>

—- 问题知识点分割线 —-

实现数组原型办法

forEach

语法:arr.forEach(callback(currentValue [, index [, array]])[, thisArg])

参数:

callback:为数组中每个元素执行的函数,该函数承受 1 - 3 个参数currentValue: 数组中正在解决的以后元素index(可选): 数组中正在解决的以后元素的索引array(可选): forEach() 办法正在操作的数组 thisArg(可选): 当执行回调函数 callback 时,用作 this 的值。

返回值:undefined

Array.prototype.forEach1 = function(callback, thisArg) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    // 创立一个新的 Object 对象。该对象将会包裹 (wrapper) 传入的参数 this(以后数组)。const O = Object(this);
    // O.length >>> 0 无符号右移 0 位
    // 意义:为了保障转换后的值为正整数。// 其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型
    const len = O.length >>> 0;
    let k = 0;
    while(k < len) {if(k in O) {callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
}

map

语法:arr.map(callback(currentValue [, index [, array]])[, thisArg])

参数:与 forEach() 办法一样

返回值:一个由原数组每个元素执行回调函数的后果组成的新数组。

Array.prototype.map1 = function(callback, thisArg) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this); 
    const len = O.length >>> 0;
    let newArr = [];  // 返回的新数组
    let k = 0;
    while(k < len) {if(k in O) {newArr[k] = callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
    return newArr;
}

filter

语法:arr.filter(callback(element [, index [, array]])[, thisArg])

参数:

callback: 用来测试数组的每个元素的函数。返回 true 示意该元素通过测试,保留该元素,false 则不保留。它承受以下三个参数:element、index、array,参数的意义与 forEach 一样。

thisArg(可选): 执行 callback 时,用于 this 的值。

返回值:一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,则返回空数组。

Array.prototype.filter1 = function(callback, thisArg) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this); 
    const len = O.length >>> 0;
    let newArr = [];  // 返回的新数组
    let k = 0;
    while(k < len) {if(k in O) {if(callback.call(thisArg, O[k], k, O)) {newArr.push(O[k]);
            }
        }
        k++;
    }
    return newArr;
}

some

语法:arr.some(callback(element [, index [, array]])[, thisArg])

参数:

callback: 用来测试数组的每个元素的函数。承受以下三个参数:element、index、array,参数的意义与 forEach 一样。

thisArg(可选): 执行 callback 时,用于 this 的值。
返回值:数组中有至多一个元素通过回调函数的测试就会返回 true;所有元素都没有通过回调函数的测试返回值才会为 false

Array.prototype.some1 = function(callback, thisArg) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this); 
    const len = O.length >>> 0;
    let k = 0;
    while(k < len) {if(k in O) {if(callback.call(thisArg, O[k], k, O)) {return true}
        }
        k++;
    }
    return false;
}

reduce

语法:arr.reduce(callback(preVal, curVal[, curIndex [, array]])[, initialValue])

参数:

callback: 一个“reducer”函数,蕴含四个参数:

preVal:上一次调用 callback 时的返回值。在第一次调用时,若指定了初始值 initialValue,其值则为 initialValue,否则为数组索引为 0 的元素 array[0]

curVal:数组中正在解决的元素。在第一次调用时,若指定了初始值 initialValue,其值则为数组索引为 0 的元素 array[0],否则为 array[1]

curIndex(可选):数组中正在解决的元素的索引。若指定了初始值 initialValue,则起始索引号为 0,否则从索引 1 起始。

array(可选):用于遍历的数组。
initialValue(可选): 作为第一次调用 callback 函数时参数 preVal 的值。若指定了初始值 initialValue,则 curVal 则将应用数组第一个元素;否则 preVal 将应用数组第一个元素,而 curVal 将应用数组第二个元素。
返回值:应用“reducer”回调函数遍历整个数组后的后果。

Array.prototype.reduce1 = function(callback, initialValue) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this);
    const len = O.length >>> 0;
    let k = 0;
    let accumulator = initialValue;
    // 如果第二个参数为 undefined 的状况下,则数组的第一个有效值(非 empty)作为累加器的初始值
    if(accumulator === undefined) {while(k < len && !(k in O)) {k++;}
        // 如果超出数组界线还没有找到累加器的初始值,则 TypeError
        if(k >= len) {throw new TypeError('Reduce of empty array with no initial value');
        }
        accumulator = O[k++];
    }
    while(k < len) {if(k in O) {accumulator = callback(accumulator, O[k], k, O);
        }
        k++;
    }
    return accumulator;
}

正文完
 0