1. 什么是MVVM,MVC,MVP模型?
软件架构模式:
MVC:
M: 模型,拉取数据的类。
V: 视图,展现给用户的视觉效果。
C: 控制器,通知M拉取数据,并且给V。
> MVC 仅限于后端,前端只完成后端开发中view层。采用服务端渲染。
> 数据流混乱,若model层数据改变很难判断是model层直接更改还是view用户操作更改的,导致数据流混乱,相互依赖耦合度高。
MVP:
P: 负责V,M之间的数据流动,防止V,M直接交流。
> 分离两端联系,解决耦合度问题,但是presenter内容就多。
MVVM:
ViewModel: 响应式机制自动响应model中数据变化。
> 将数据变动在vm上处理,vm中利用diff算法,虚拟DOM等方法实现一套数据响应式机制自动适应model中的数据变化同时实现一套更新策略自动将数据变化转化为视图更新,减少大量代码,提高效率利于维护。
2. vue双向数据绑定的原理?
数据劫持+发布者和订阅者模式
observe 给每个属性都添加一个getter和setter,getter当数据被读取就会被触发, setter当数据改变时会被触发。会对数据对象递归遍历。
complie 解析模板指令,将变量替换为数据,然后初始化渲染页面。
watcher 订阅者是observe和compile之间通信桥梁。
3. vue生命周期:
new Vue():
beforeCreate(): 无法通过vm访问到data中的数据,methods中的方法。
created()
4. v-if 和 v-show有什么区别?
都是元素的显示和隐藏效果。
v-if:实际上是dom元素的创建或销毁。
v-show:相当与display: none/block。
5. async await是什么,有什么作用?
async: 指明一个函数是异步函数, await 等待一个异步方法执行完成。
async函数返回一个promise对象。
promise三个状态:
pending:初始状态
fulfilled: 操作成功
rejected: 操作失败
6. 常用数组方法(更改原数组和不更改原数组)?
改变原数组:
1. pop():删除数组的最后一个元素,且返回值为删除的元素的值,若数组为空,则返回undefined。
2. push(): 添加元素至数组中,修改的原数组, 返回新数组的新长度。
3. shift(): 删除数组的第一个元素,若数组为空,则该方法不进行任何操作,返回该元素的值。 –> 不接受参数,传入参数是没有意义的。
4. unshift(): 在数组头部添加元素,并且返回新数组长度。
5. reverse(): 对数组内容进行反转,改变原数组。
6. sort(): 对原数组进行排序。
7. splice(): 增加,替换和删除元素, 。 (index,howmany,item1…itemX)
—
不改变原数组:
concat(): 合并两个数组,返回一个新数组。
find(): 返回数组满足提供的测试函数的第一个元素的值,不满足返回undefined。
indexOf(): 找到一个给定元素的第一个索引,不存在就返回-1。
join(): 将数组所有元素连接成一个字符串并返回这个字符串。
slice(start, end – 表示数组下标): 一个含有被提取元素的新数组。
—
CSR: 浏览器渲染, 页面上的内容是加载JS文件渲染出来的,JS文件运行在浏览器上面,服务端只返回一个HTML文件。
SSR: 服务端渲染, 页面上的内容是通过服务端渲染生成的,浏览器直接显示服务端返回的HTML。
> 服务端渲染减少白屏时间还可以大幅度减少首屏加载时间。
从**输入页面URL到页面渲染**完成大致流程为: 本质就是两个IP通信。
解析URL
浏览器本地缓存
DNS解析
建立TCP/IP连接
发送HTTP请求
服务器处理请求并返回HTTP报文
浏览器根据深度遍历的方式把html节点遍历构建DOM树
遇到CSS外链,异步加载解析CSS,构建CSS规则树
遇到script标签,如果是普通JS标签则同步加载并执行,阻塞页面渲染,如果标签上有defer / async属性则异步加载JS资源
将dom树和CSS DOM树构造成render树
渲染render树
—
7. 数组的循环方式?
every() 数组内的所有元素是否通过某个指定函数的测试,返回一个布尔值。
filter() 创建一个新数组, 不会对空数组进行检测。
forEach() 对数组的每一个元素执行一次提供的函数。
some() 检测是否有一个元素可以通过被提供的函数方法,返回一个布尔值。
8. 原型链?
原型对象:
“`js
function Star(uname){
this.uname = this.uname
}
// Star.prototype = {
// // 指向构造函数,这里是赋值操作,没有指明构造函数,需要重新指向
// constructor: Star,
// sing: function(){
// console.log(‘111’);
// },
// dance: function(){
// console.log(‘222’);
// }
// }
// 此处是给原型对象添加一个函数。
Star.prototype.sing = function(){
console.log(‘333’);
}
console.log(Star.prototype.constructor);
“`
> 每一个构造函数都有一个prototype属性,最初是一个空对象,指向另一个对象,称为原型对象,对象中有一个constructor指向构造函数。
对象原型:
“`js
function Star(uname){
this.uname = this.uname
}
Star.prototype = {
// 指向构造函数,这里是赋值操作,没有指明构造函数,需要重新指向
constructor: Star,
sing: function(){
console.log(‘111’);
},
dance: function(){
console.log(‘222’);
}
}
// 此处是给原型对象添加一个函数。
// Star.prototype.sing = function(){
// console.log(‘333’);
// }
const str = new Star()
// 实例对象上有一个__proto__属性指向原型对象。
console.log(str.__proto__);
“`
> 实例对象的对象原型(__proto__)指向原型对象(prototype)。
> 对象原型中也有一个constructor属性,指向创建该实例对象的构造函数。
原型链:
每一个实例对象上都有proto属性,指向构造函数的原型对象,但是构造函数的原型对象也是一个对象其中也有proto属性,这样一层一层就形成了原型链。
“`js
function Star(uname){
this.uname = this.uname
}
Star.prototype = {
// 指向构造函数,这里是赋值操作,没有指明构造函数,需要重新指向
constructor: Star,
sing: function(){
console.log(‘111’);
},
dance: function(){
console.log(‘222’);
}
}
// 此处是给原型对象添加一个函数。
// Star.prototype.sing = function(){
// console.log(‘333’);
// }
const str = new Star()
// 实例对象上有一个__proto__属性指向原型对象。
console.log(str.__proto__.constructor);
console.log(str instanceof Star); //true
console.log(str instanceof Object);//true
“`
> instanceof 检测构造函数prototype属性是否出现在某个实例对象的原型链上。
9. 闭包?
一个作用域可以访问另外一个函数内部的局部变量。
“`js
function demo() {
const num = 0;
function str(){
console.log(num);
}
return str;
}
const fn = demo()
fn() //输出0,访问到num属性
“`
> JS具有自动垃圾回收机制,不会造成内存泄漏(JS已经分配内存地址的对象由于长时间未进行内存释放或无法清除,造成长期占用内存,使得内存资源浪费。)
闭包会导致内存泄漏。
10. 常见继承方法?
– 原型链继承:
“`js
// 父类构造函数
function Person() {
this.name = ‘ly’;
this.eat = [‘apple’];
// 若是箭头函数,this指向父级
this.getName = function(){
console.log(this.name);
}
}
Person.prototype.get = () => {
console.log(“父类原型对象上的get方法”);
}
// 子类构造函数
function Student() {}
// 子类 原型链继承 父类
Student.prototype = new Person()
// 子类实例化
const student = new Student()
// 通过原型链继承,若父类也有相同的基本数据类型变量,相当于给对象添加一个属性
// 若是相同的引用类型,则就是更改父类原型对象。
student.name = ‘lyyy’
student.eat.push(‘pink’)
// 若为箭头函数就是输出,ly,普通函数输出lyyy
student.getName()
console.log(student.eat); //lyyy
console.log(student.name); //[‘apple’, ‘pink’]
const student1 = new Student()
// 若为箭头函数就是输出,ly,普通函数输出lyyy
student.getName()
console.log(student1.eat);//ly
console.log(student1.name);//[‘apple’, ‘pink’]
“`
> 缺点:
1. 父类所有的引用类型数据(对象,数组)会被子类共享,更改一个子类的数组,其他数据都会变化。
2. 子类实例不建议给父类构造函数传参, 若给父类传入一个引用类型的数据,若造成改变,那么其他子类都会收到影响。
—
– 构造函数继承:
“`js
// 父类构造函数
function Person() {
this.name = ‘ly’;
this.eat = [‘apple’];
// 若是箭头函数,this指向父级
this.getName = function(){
console.log(this.name);
}
}
Person.prototype.get = () => {
console.log(“父类原型对象上的get方法”);
}
// 子类构造函数
function Student() {
Person.call(this)
}
// 子类实例化
const student = new Student()
// 通过原型链继承,若父类也有相同的基本数据类型变量,相当于给对象添加一个属性
// 若是相同的引用类型,则就是更改父类原型对象。
student.name = ‘lyyy’
student.eat.push(‘pink’)
// 若为箭头函数就是输出,ly,普通函数输出lyyy
student.getName()
console.log(student.eat); //lyyy
console.log(student.name); //[‘apple’, ‘pink’]
const student1 = new Student()
// 若为箭头函数就是输出,ly,普通函数输出lyyy
student.getName()
console.log(student1.eat);//ly
console.log(student1.name);//[‘apple’]
“`
> 缺点:
子类不能访问父类原型属性(Person.prototype)上的方法和参数。
—
– 组合继承(组合原型继承和借用构造函数继承):
“`js
// 父类构造函数
function Person() {
this.name = ‘ly’;
this.eat = [‘apple’];
// 若是箭头函数,this指向父级
this.getName = function(){
console.log(this.name);
}
}
Person.prototype.get = () => {
console.log(“父类原型对象上的get方法”);
}
// 子类构造函数
function Student() {
Person.call(this)
}
Student.prototype = new Person()
// 子类实例化
const student = new Student()
// 通过原型链继承,若父类也有相同的基本数据类型变量,相当于给对象添加一个属性
// 若是相同的引用类型,则就是更改父类原型对象。
student.name = ‘lyyy’
student.eat.push(‘pink’)
// 若为箭头函数就是输出,ly,普通函数输出lyyy
student.getName()
console.log(student.eat); //lyyy
console.log(student.name); //[‘apple’, ‘pink’]
student.get()
const student1 = new Student()
// 若为箭头函数就是输出,ly,普通函数输出lyyy
student.getName()
console.log(student1.eat);//ly
console.log(student1.name);//[‘apple’]
student.get()
“`
> 优点:
1. 父类可以复用
2. 父类构造函数中的引用属性不会被共享
> 缺点: 会调用两次父类构造函数,会有两份一样的属性和方法,会影响性能。
—
– 寄生组合式继承:
“`js
// 父类构造函数
function Person() {
this.name = ‘ly’;
this.eat = [‘apple’];
// 若是箭头函数,this指向父级
this.getName = function(){
console.log(this.name);
}
}
Person.prototype.get = () => {
console.log(“父类原型对象上的get方法”);
}
// 子类构造函数
function Student() {
Person.call(this)
}
// 利用一个第三方插入,相当于只是复制Person.prototype
const Fn = function(){}
Fn.prototype = Person.prototype
console.log(Person.prototype);
console.log(Fn);
// 实例化一个空对象
Student.prototype = new Fn()
console.log(Student.prototype);
// 子类实例化
const student = new Student()
student.name = ‘lyyy’
student.eat.push(‘pink’)
student.getName()
console.log(student.eat); //lyyy
console.log(student.name); //[‘apple’, ‘pink’]
student.get()
const student1 = new Student()
student.getName()
console.log(student1.eat);//ly
console.log(student1.name);//[‘apple’]
student.get()
“`
优点: 避免调用两次父类构造函数
—
– ES6class类继承extends
“`js
// 父类构造函数
class Person {
constructor(){
this.name = ‘ly’
this.eat = [‘apple’]
this.getName = function(){
console.log(this.name);
}
}
get = () => {
console.log(“父类原型对象上的get方法”);
}
}
// 采用ES6继承
class Student extends Person {}
const student = new Student()
student.name = ‘lyyy’
student.eat.push(‘pink’)
// 若为箭头函数就是输出,ly,普通函数输出lyyy
student.getName()
console.log(student.eat); //lyyy
console.log(student.name); //[‘apple’, ‘pink’]
student.get()
const student1 = new Student()
// 若为箭头函数就是输出,ly,普通函数输出lyyy
student.getName()
console.log(student1.eat);//ly
console.log(student1.name);//[‘apple’, ‘pink’]
student.get()
“`
11. es6新特性:
const let (块作用域)
箭头函数
模板字符串
promise
解构赋值…
12. v-for为什么要绑定key?
页面上的标签都是具体的虚拟dom对象,循环中,如果没有唯一的key,页面上删除一条标签,由于并不知道删除的是那一条,所以要把全部虚拟dom重新渲染,如果知道key为x标签被删除掉,只需要把渲染的dom为x的标签去掉即可。
13. 组件中data为什么要定义成一个函数而不是一个对象?
每一个组件都是vue实例,组件共享data属性,当data的值是同一个引用类型的值时,改变其中一个就会影响其他的。
14. 常见盒子垂直居中方式?
– 子绝父相 (知道元素高度, position: relative; position: absolute;)
– transform (未知元素高度情况下实现元素垂直居中, translate() )
– flex (justify-content: center; align-items: center)
15. 如何实现跨域问题?
CORS: 跨域资源共享。(服务器实现cors接口)
JSONP: 利用标签没有跨域限制的漏洞,网页可以得到从来源动态产生的JSON数据。JSONP请求一定需要对象的服务器做支持才可以。(只支持get请求,不安全可能会遭受XSS攻击)
16. cookie、localstorage、sessionstrorage之间有什么区别?
与服务器交互:
cookie: 始终会在同源HTTP请求头中携带,在浏览器和服务器间来回传递。
sessionStorage, localStorage: 不会自动把数据发给服务器,仅在本地保存。
存储大小:
cookie 数据根据不同浏览器限制,一般不超过4K
sessionStorage和localStorage有5M升至更大
有效时间:
localStorage: 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据
sessionStorage: 浏览器窗口关闭后自动删除
cookie:设置过期时间。
17. this指向问题?
18. 什么是递归?
函数内部自己调用自己,可能会导致调用栈溢出。
19. 实现性能优化?
网络优化:
– 使用缓存: 减轻服务端压力,快速得到数据。
– DNS预解析: link标签的ref属性设置dns-prefetch,提前获取域名对应的IP地址。
– 使用CDN:内容分发网络,将静态资源放在CDN上。
– 避免图片src为空:虽然src属性为空字符串,但浏览器依旧会向服务器发起一个HTTP请求。
页面渲染优化:
– 避免css选择器的复杂度: 减少嵌套
– 避免使用css表达式
– 使用外链式的js和css
– 使用字体图标iconfont代替图片图表
– 减少重绘和回流
JS中性能优化:
– 使用事件委托
– 防抖和节流
– 尽量不要使用JS动画
图片的优化:
– 精灵图
– 图片懒加载: 在图片即将进入可视区域的时候进行加载。
– 图片压缩
—
### 重排和重绘:
重绘: 某些元素外观被改变,比如元素的填充颜色
产生重绘:
– color
– visibility
– border-style
– text-decoration…
重排(回流): 重新生成布局,重新排列元素。比如元素的几何信息
产生重排:
– 页面初始化渲染
– 添加和删除可见的DOM元素
– 改变元素位置
– 改变元素尺寸,边距、填充、边框、宽度。。。
– 改变元素内容,文字数量、图片大小
– 改变元素字体大小
– 浏览器窗口尺寸。。。
—
### 事件委托:
事件流: 捕获阶段(window) -》 目标阶段(到达目标元素或开始该事件的元素) -》 冒泡阶段(向父元素传播,直到window对象)
也叫事件托管,把目标节点的事件绑定到祖先节点上。逐层冒泡总能被祖先节点捕获。
优点:
– 节省监听数,节省内存。
– 可以监听动态元素。
阻止默认动作:
在a标签会跳转到href指定的网页,若想阻止该行为:
调用事件对象preventDefault()方法取消事件的默认操作。
事件冒泡:
浏览器从用户点击的按钮从下往上遍历至window,逐个触发事件处理函数。
如何阻止事件冒泡:
– event.stopPropagation()
– return false
事件捕获只发生在被点击的元素和目标上,该事件不会传播到子元素。
—
20. vue实例是挂载到那个标签上的?
vue实例最后会挂载在body标签里面,所以我们在vue中获取不了body标签的,如果要使用body标签的话需要用原生的方式获取。
21. 深拷贝和浅拷贝?
浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝,若为基本类型,拷贝的就是基本类型的值,若是引用类型,拷贝的就是内存地址。
– … 展开运算符
– Object.assign() 合并对象,第二个参数的值和第一个参数对象的值合并,只改变第一个参数对象的值。
深拷贝:拷贝的只是对象,不是地址。
– lodash 中 _.cloneDeep()
– JSON.parse(JSON.stringify())
JSON.parse()是将JSON字符串转化为对象
JSON.stringify()将对象转化为JSON字符串
22. js执行机制?
**JS是一门单线程和非阻塞的脚本语言**
事件循环: Event Loop
– 同步任务
– 异步任务
同步任务放入主线程执行栈中,异步任务放入事件队列中。当同步任务执行完毕(执行栈为空后),会从任务队列读取对应函数,放入主线程执行。
异步任务分为 **宏任务**和**微任务**:微任务在宏任务执行之前。
宏任务:setInterval(), setTimeout()
微任务:promise.then(), Async/Await(实际上就是Promise), new MutaionObserver()
> 注意: promise的then()函数中才是异步任务,在promise中的执行语句是同步任务。
“`js
(function test() {
setTimeout(function() {console.log(4)}, 0);
new Promise(function executor(resolve) {
console.log(1);
for( var i=0 ; i<10000 ; i++ ) {
i == 9999 && resolve();
}
console.log(2);
}).then(function() {
console.log(5);
});
console.log(3);
})()
// 1 2 3 5 4
“`
23. 数组去重方法?
– ES6中set去重。 — 弊端:无法去掉{}空对象
“`js
function unique(arr) {
return Array.from(new Set(arr))
}
// set返回一个对象,array.from()将对象转为数组。
“`
– filter 返回一个满足条件的新数组。 indexOf()满足条件返回下标,不满足返回-1。
“`js
function unique(arr) {
return arr.filter(function (item, index, arr){
// indexOf第二个参数表示开始要搜索的索引
return arr.indexOf(item, 0) === index
})
}
“`
24. 数组排序方法(原生JS)?
快排, 插入, 冒泡
25. set和map是什么?
set()是返回一个对象,确保一个数组没有重复项。
map()返回一个满足回调参数(回调函数)的新数组。
26. 清除浮动的方法?
清除浮动是为了解决,父元素(没有设置高度)因为子元素浮动引起的内部高度为0的问题。
– 额外标签法:给最后一个浮动标签后,新添加一个标签,给其设置为clear: both.
> 添加无意义标签,语义化差
– 父级添加overflow属性(overflow: hidden)
> 内容增多的时候容易造成不会自动换行导致内容被隐藏掉,无法显示要溢出的元素。
– 使用伪元素after清除浮动
“`css
.clearfix:after{/*伪元素是行内元素 正常浏览器清除浮动方法*/
content: “”;
display: block;
height: 0;
clear:both;
visibility: hidden;
}
.clearfix{
*zoom: 1;/*ie6清除浮动的方式 *号只有IE6-IE7执行,其他浏览器不执行*/
}
big
small
“`
> ie6-7不支持伪元素,使用zoom: 1触发。
– 使用before和after双伪元素清除浮动。
“`css
.clearfix:after,.clearfix:before{
content: “”;
display: table;
}
.clearfix:after{
clear: both;
}
.clearfix{
*zoom: 1;
}
big
small
“`
> 使用zoom:1触发。
27. 常见布局方法?
– 浮动
– 绝对定位
– flex布局
– 网格布局
28. 图片懒加载是怎末实现的?
通过img的data-src属性。 将src替换为data-src.
29. vue中computed和watch的区别是什么?
computed:
– 支持缓存,只有依赖数据发生变化,才会重新进行计算。
– 不支持异步。
– 监测的是依赖值。
watch:
– 不支持缓存。
– 支持异步操作。
– 监听某一个数据。
– 监听的函数接收两个参数,第一个参数是最新的值,第二个参数是输入之前的值。
30. vue是如何实现父向子、子向父、兄弟之间传值的?
父向子:props属性来传值,props只读。
子向父传值:$emit
兄弟组件:全局事件总线$bus
插槽
31. vuex理解?
vuex是专为vue.js开发的状态管理模式,采用集中式存储管理应用的所有组件的状态,解决组件数据通信。
– state:统一定义管理公共数据
– mutations:用于修改数据
– getters:vue的计算属性
– actions:发送异步请求
– modules:模块拆分
vue组件实例派发(dispatch)到actions中,通过异步请求获取数据,然后commit提交到mutations中,在mutations中修改数据。
32. 数据类型的判断方法?优缺点?
– typeof:只能检测出基本类型。(返回结果为number, Boolean, string, function, object, undefined)
– instanceof:返回一个布尔值,由构造类型判断数据类型。
– object.prototype.toString.call()判断类型
– constructor:不能检测null和undefined。
33. 盒子模型?
标准盒子模型:width和height指的是内容区域的宽度和高度。
IE盒子模型中:width和height指的是内容区域+border+padding的宽度和高度。
34. promise是什么?有什么作用?
promise是异步编程的一种解决方案。promise存在三种状态:pending,fulfiled,rejected。
– 解决地狱回调问题
– 支持多个并发请求,获取并发请求中的数据
– promise解决异步问题。
promise构造函数接收一个参数:函数,这个函数需要传入两个参数(resolve, reject)
– resolve:异步操作执行成功后的回调函数
– reject:异步操作执行失败后的回调函数
35. 箭头函数特性?
箭头函数没有自己的this,this指向定义箭头函数时所处的外部执行环境的this。
即使调用call/apply/bind也无法改变箭头函数的this箭头函数本省没有名字,箭头函数不能new,会报错。
箭头函数没有arguments,在箭头函数内访问这个变量访问的是外部执行环境的arguments 箭头函数没有prototype。
36. post和get请求有哪些区别?
– post请求更安全(不会被缓存)
– post发送的数据更大(请求数据在body中)
– post(产生两个TCP包)比get(只产生一个TCP包)慢
– post用于修改和写入数据,get用于搜索排序和筛选之类。
37. 同源策略?
同域名,同协议,同端口
38. HTTP状态码分别代表?
– 1xx 表示 HTTP 请求已经接受,继续处理请求
– 2xx 表示 HTTP 请求已经处理完成(200)
– 3xx 表示把请求访 问的 URL 重定向到其他目录(304 资源没有发生变化,会重定向到本地资源)
– 4xx 表示客户端出现错误 (403 禁止访问、404 资源不存在)
– 5xx 表示服务端出现错误
39. BFC是什么?
(块级格式化上下文)
创建了新的BFC的盒子是独立布局的,盒子内元素的布局不会影响盒子外面的元素,在同一个BFC中的两个相邻的盒子在垂直方向发生margin重叠的问题。
触发BFC:
– display:flex
– overflow: hidden
– position: absolute
– display: inline-block
40. JS的数据类型?
基本数据类型:
– 布尔值(Boolean)
– null
– undefined
– 数字(number)
– 字符串(string)
– symbol
– bigInt
引用类型:
– object (包括object, array, function)
41. CSRF和XSS攻击?
CSRF:跨站请求伪造
XSS:跨域脚本攻击
区别一:
CSRF :需要用户先登录网站 A ,获取 cookie
XSS :不需要登录。
区别二:(原理的区别)
CSRF :是利用网站 A 本身的漏洞,去请求网站 A 的 api 。
XSS :是向网站 A 注入 JS 代码,然后执行 JS 里的代码,篡改网站 A 的内容。
42. cookies和session区别?
– cookie 数据存放在客户的浏览器上,session 数据放在服务器上。
– cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗
考虑到安全应当使用 session。
– session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
考虑到减轻服务器性能方面,应当使用 COOKIE。
– 单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie。
– 所以个人建议:
将登陆信息等重要信息存放为 SESSION ;其他信息如果需要保留,可以放在 COOKIE 中
43. call、apply、bind三者的异同?
共同点 : 都可以改变 this 指向;
不同点: call 和 apply 会调用函数, 并且改变函数内部 this 指向. call 和 apply传递的参数不一样,call 传递参数使用逗号隔开,apply 使用数组传递 bind 不会调用函数, 可以改变函 数内部 this 指向. 应用场景
call 经常做继承.
apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
bind 不调用函数,但是还想改变 this 指向. 比如改变定时器内部的 this 指向