** 前端js面试题!!!!**
1、闭包是什么,有什么特性,对页面有什么影响?
闭包他是一种特殊的对象,它由两部分构成,函数及创建函数的环境;
闭包我们可以理解为是将昂函数内部和外部函数连接起来的桥梁;
闭包的解释:函数嵌套函数,内部的函数调用外部的函数的变量或者参数。
2、ajax请求时,如何解析json数据?
使用JSON.parse
3、事件委托是什么?
利用事件冒泡原理,让自己所触发的事件,让他的父元素代替执行
4、解释一下jsonp的原理,以及为什么不是真正的ajax?
jsonp的原理:其原理就是利用浏览器动态的插入一段js并执行的特点完成的。
为什么不是真正的ajax:
1)ajax的核心是:通过xmlhttpRequest获取非本页内容
2)jsonp的核心是:动态添加
5、document.load和document.ready的区别?
document.load是在结构、样式和外部js以及图片加载完之后才执行js;
document.ready是在Dom树创建完成就执行的方法,在原生中没有这种方法,在jq中有 $().ready(function)
6、函数声明和函数表达式的区别?
1)以函数声明的方法定义的函数,函数名是必须的,而函数表达式的函数名是自定义的;
2)以函数声明的方法定义的函数,函数可以在函数声明之前调用,而函数表达式的函数只能在声明之后调用;
3)以函数声明的方法定义的函数并不是真正的声明,他们仅仅是出现在全局中或者是嵌套在其它函数中。
7、什么是js继承?
首先,js继承就是获取存在对象已有的属性和方法的一种方式;
js继承大致有三种方法:构造函数继承、原型继承、组合继承,其中,
1)构造函数继承:此方法会使得子类的多个实例中从父类继承过来的属性和方法都私有化,没有实现方法的共享;
2)原型继承:此方法使得子类的多个实例中从父类继承的过来的属性和方法实现方法的共享,但是没有实现私有化;
3)组合继承:此方法使得子类得多个实例中从父类继承过来的属性和方法即实现私有化又实现了方法的共享;
8、什么是原型和原型链?
原型:每个函数都有protoType(原型)这个属性,并且这个属性指向函数的原型对象,并且这个属性是一个对象数据类型的值;
原型链:javascript是面向对象的,每一个实例对象都有一个_proto_属性,该属性指向它的原型对象,这个实例对象的构造函数有一个有一个原型属性protoType,与实例的proto属性指向同一个对象。当一个对象在查找一个属性的时候,自身没有就会根据_proto_ 向它的原型进行查找,如果都没有,则向它的原型的原型继续查找,直到找到。
9、javascript的事件流模型都有什么?
“事件冒泡”:事件冒泡是由最具体的元素接收,然后逐级向上传播;
“事件捕捉”:事件捕捉是由最不具体的节点先接收,然后逐级向下,一直到最具体的;
“DOM事件流”:三个阶段L:事件捕捉、目标阶段、事件冒泡。
10、new操作符具体都干了什么?
1)创建一个空对象,并且this变量引用该对象,同时还继承了该函数的原型;
2)属性和方法被加入到this引用的对象中;
3)新创建的对象由this所引用,并最后隐式返回this。
11、js延迟加载的方式有哪些?
defer和async、动态创建Dom方式(创建script,插入到Dom中,加载完毕后callback)、按需异步载入js
12、form中中的input可以设置为readOnly和disable,请问两者有什么区别?
readOnly不可编辑,但可以选择和复制;值可以传给后台;
disable不能编辑、复制、选择,值不可以传到后台;
13、说一下数据类型都有哪些?
首先,我们需要介绍的是数据类型都分为两种:
1)基本数据类型:string、Number、null、undefined、Boolean、还有es6新增symbol
2)复杂数据类型:object、Array、Function
14、解释一下什么是json?
1)json是一种轻量级的数据交换格式;
2)json独立于语言和平台,json解析器和json库支持许多不同的编程语言;
3)json的语法表示三种类型值,简单值(字符串、布尔值、null、数值)、数组、对象
15、闭包的好处?
1)希望一个变量长期驻扎在内粗当中(不被垃圾回收机制回收);
2)模块化开发,避免全局变量的污染;
3)可以实现封装,属性私有化;
4)可以做缓存(存储结构)
16、什么是FOUC,如何来避免FOUC?
由于css引入了多个@import或者存在多个style标签以及css文件在底部引入使的css文件加载在html之后导致页面闪烁;
所以这是我们最好的解决办法就是利用link加载css文件,放在head标签里。
17、说除几种降低页面加载的方法。
1)压缩css、js文件;
2)合并css、js文件,减少http请求;
3)外部css、js文件放在最底部;
4)减少dom操作,尽可能用变量代替不必要的dom操作。
18、为什么利用多个域名来存储网站资源会更有效?
确保用户在不同的地区可以用更快的速度打开网站,其中某个域名崩溃,用户也可以通过其它域名对网站进行访问。
19、你如何优化自己的代码?
1)HTML结构要清晰,尽可能使用语义化标签
2)减少外部HTTP请求
3)压缩 CSS, JS 和 HTML
4)使用 CDN 和缓存提高速度
5)尽量使用使用轻量级框架
6)使用异步加载,延迟加载依赖
7)尽可能使用CSS3动画,相比JavaScript驱动的动画效率更高
8)使用事件委托,防止绑定不必要的大量事件
9)公用的css和js尽量写成公共样式/函数来条调用
10)控制DOM大小,清除多余的DOM节点
11)链接CSS,避免使用@import,避免CSS表达式
12)CSS Sprites雪碧图、icon图标,base64图片
13)尽量合并样式表和js,减少请求次数
14)Html标签、css类名、js变量命名尽量规范,见名知意。
15)将样式表放在头部,脚本放在底部
20、描述一下ajax工作的原理。
1)创建Ajax(XMLHttpRequest / ActiveXObject(Microsoft.XMLhttp))
2)判断传输数据方式(post / get);
3)打开链接open();
4)发送send();
5)当ajax完成第四步(onreadystatechange)数据接收完成,判断http响应状态(status)200-300之间或者304(缓存)执行回调函数。
21、什么是虚拟Dom?
虚拟dom是相对于浏览器渲染出来的真实dom的,在react和vue没有出来之前,我们要改变页面展示的内容只能通过遍历循环dom树的方式找到需要修改的dom然后修改样式行为或者结构,来达到更新ui的目的。然而,这种方式是极其的消耗计算资源,因为每次查询dom都需要几乎遍历整棵dom树,如果建立一个与dom树对应的虚拟dom对象(js对象),以对象嵌套方式来表示dom树,那么每次dom对象的更改就变成了js对象的属性的更改,这样一来就能查询js对象的属性的变化要比查询dom树开销要小。
22、dom选择器的优先级是什么?以及权重值计算
1)行内样式 1000
2)id 0100
3)类选择器、伪类选择器、属性选择器[Type=“Text”] 0010
4)标签选择器、伪元素选择器(::first-line) 0001
5)通配符*、子选择器、相邻选择器 0000
23、vue双向数据绑定原理?
vue实现双向数据绑定主要采用:数据劫持结合“发布-订阅” 模式的方式,通过 Object.defineProperty()的set和get,在数据变动时发布消息给订阅者出发监听。运用观察者模式实现watcher,用户数据和view视图的更新。
24、说一下缓存。
说到几种缓存有cookie,还有H5新增的webstorage即localStorage和sessionStorage。其中他们的共同点就是:都是保存在浏览器端的,且都是同源的。
不同点:
1)cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器之间来回传递。而sessionStorage和localStorage不会自动把数据发送给服务器,仅保存在本地;
2)cookie数据还有路径(path)的概念,可以限制cookie 只属于某个路径下,存储大小限制也不同,cookie数据不能超过4k,同时因为每次http请求都会携带cookie,五哦一cookie只适合保存很小的数据,如会话标识。
sessionStorage和localStorage虽然也有存储大小的限制,但是它们远比cookie大的多,可以达到5M甚至更大。
3)数据有效期不同:
sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;
localStorage:始终有效,窗口或者浏览器关闭也一直保存着,因此用作持久数据,除非手动删除,否则数据不会消失;
cookie:只在设置cookie过期时间之前有效,即使窗口或者浏览器关闭。
4)作用域不同:
不同浏览器无法共享localStorage和sessionStorage中的信息。相同浏览器的不同页面间可以共享相同的localStorage(页面属于相同域名和端口),但是不同页面和标签间无法共享sessionStorage的信息,cookie是在所有同源窗口都是共享的。
25、get和post有什么区别?
1)get使用url或cookie传参,而post将数据放在body中;
2)get的url会有长度上的限制,而post的数据则可以非常大;
3)post比get安全,因为不会出现在地址栏上;
4)get请求参数会被完整保留在浏览历史记录里,而post中的参数不会被保留;
5)get请求只能进行url编码,而post支持多种方式编码。
26、说一下定位有哪几种及区别。
1)static 默认值
2)relative 相对定位 相对于自身原有位置进行偏移,仍处于标准文档流中;
3)absolute 绝对定位 使元素完全脱离文档流,一般都是配合相对定位使用的;
4)fixed 固定定位 相对于浏览器窗口进行定位
27、怎么判断两个对象相等?
JSON.stringfiy(obj)== JSON.stringfiy(obj) //true
**28、vue router跳转和location.href跳转有什么区别?**
router是hash改变
location.href 是页面跳转,刷新页面
29、重排(回流)和重绘。
回流:当渲染树中的元素尺寸、布局隐藏等改变需要重新构建,每个页面至少需要一次回流
重绘:一个元素的外观改变所触发的浏览器行为,浏览器会根据元素新的属性来重新绘制,使元素呈现新的外观。触发条件:改变元素外观属性,如color、background-color等。
关系:回流必会引发重绘,但重绘不一定引发回流。
30、什么情况下会发生回流和重绘?
1)添加、删除、更新dom节点;
2)通过display:none 隐藏一个dom节点–触发回流和重绘;
3)通过visibility:hidden 隐藏一个dom节点–只会发生重绘,因为没有几何变化;
4)移动或者给页面中的dom节点添加动画;
5)添加一个样式表,调整样式属性
31、定时器有什么及区别。
一般常用的定时器就是setTimeout和setInterval了。
1)setTimeout:是指在时间内只执行一次;
2)setInterval:是每隔一段时间就只想一次即间歇调用。
32、判断数组的方法。
1)Array.isArray() (推荐使用)
2)Object.prototype.toString.call() 是否匹配 [object Array]
3)通过instanceOf() (局限性:多个全局时,iframe不可用) a instanceof Array
4)通过constructor判断 (局限性:多个全局时,iframe不可用) a.constructor === Array
5)使用.length (局限性 确保对象中没有length属性 ), 对象的.length为undefined(确保对象中没有length属性), 数组的.length为数字类型
33、遍历数组的几种方法及区别。
1)forEach:遍历开始以后无法停止,如果要遍历整个数组,那就使用这个方法;
2)map:根据当前数组映射出一个新的数组;
3)some:遍历整个数组,返回值true就停止循环(返回false继续循环)(数组被遍历完,那么结果为false,否则为true)
4)every:与some恰好相反,返回值为false就停止循环,为true就继续循环;
5)filter:过滤数组,返回一个新数组;
6)reduce:实现数据的累加
例:
const arr = [1,3,5]
const ret = arr.reduce(function(prev,cur){
return prev + cur
},0)
console.log(ret) //9
34、css选择器优先级。
ID>类>标签>相邻>子选择器>后代选择器>*>属性>伪类
**
35、Es6新特性。
**
1)const与let变量。(说到这两个,肯定会有一道必问题就是let、const、var的区别)
var:
存在变量提升、var的作用域是函数作用域,在函数内利用var声明一个变量,则在函数内这个变量有效。
let:
- let的作用域是块级作用域,es6之前的作用域只有全局、函数作用域没有块级作用域;
- let声明的变量不会有变量提升,所以在let声明的变量之前访问该变量会造成报错,也就是暂时性死区;
- 不能重复定义;
const:
-
const和let一样存在暂时性死区,没有变量提升,也不允许重复声明。
-
const定义之后再重复赋值也会报错。
-
const是用来声明一个常量的,且声明的常量不允许修改,只读属性。所以,在声明的时候必须赋值,否则会报错。
2)模板字面量
在es6之前将字符串连接到一起的方法是“+”或者contact()。
模板字面量本身上是包含嵌入式表达式的字符串字面量,模板字面量用倒引号(``)而不是(’’)或者(“”)表示,可以包含用$(expression)表示占位符。let message = `${student.name} please see ${teacher.name} in ${teacher.room} to pick up your report card.`;
3)解构
在es6中,可以使用解构从数组和对象提取值并赋值给独特的变量。 -
解构数组中的值
const point = [10, 25, -34];
const [x, y, z] = point;
console.log(x, y, z);
- 解构对象中的值
const gemstone = {
type: 'quartz',
color: 'rose',
karat: 21.29
};
const {type, color, karat} = gemstone;
console.log(type, color, karat);
注:花括号 {} 表示被解构的对象,type、color和karat表示要将对象中的属性存储到其中的变量
4)对象字面量的简写法
let type = 'quartz';
let color = 'rose';
let carat = 21.29;
const gemstone = {
type: type,
color: color,
carat: carat
};
console.log(gemstone);
注:使用和所分配的变量名称相同的名称初始化对象时如果属性名称和所分配的变量名称一样,那么就可以在属性名称中删掉这些重复的变量名称。
let type = 'quartz';
let color = 'rose';
let carat = 21.29;
const gemstone = {type,color,carat};
console.log(gemstone);
简写的方法名称:
const gemstone = {
type,
color,
carat,
calculateWorth: function() {
// 将根据类型(type),颜色(color)和克拉(carat)计算宝石(gemstone)的价值
}
};
匿名函数被分配给函数calculateWorth,但真的需要function关键字么?在es6中不需要!
let gemstone = {
type,
color,
carat,
calculateWorth() { ... }
};
5)for…of循环(通常提到for…of,就会问到for…of和for…in的区别)
for…of 循环是最新添加到javascript循环系列中的循环.。
- for…in Es5标准, 用于遍历数组或者对象的属性(对数组或者对象的属性进行循环操作),遍历key,其更适用于遍历对象。
for (var key in arr){
console.log(arr[key]);
}
- for … of Es6标准,此循环将只循环访问对象中的值,即遍历value
for (var value of arr){
console.log(value);
}
例:
let arr = ['a','b','c'];
for(let index in arr){
console.log(`${arr[index]}`); //a,b,c,'javascript'
}
for(var value of arr){
console.log(value);//a,b,c
}
// for…of不能循环出自定义的属性
6)展开运算符
展开运算符用(…)连续的三个点表示,是Es6中的新概念,使你能够将字面量对象展开为多个元素。
const books = ["Don Quixote", "The Hobbit", "Alice in Wonderland", "Tale of Two Cities"];
console.log(...books);
7)箭头函数
- 箭头函数的基本形式:
let func = (num) => num;
let func = () => num;
let sum = (num1,num2) => num1 + num2;
[1,2,3].map(x => x * x);
-
箭头函数的基本特点:
a、箭头函数的this为父作用域的this,不是调用时的this;(箭头函数的this永远指向其父作用域,任何方法都改变不了,包括call、apply、bind。然而普通函数的this指向调用它的那个对象)
b、箭头函数不能作为构造函数,不能使用new;c、箭头函数没有arguments、callee、caller(箭头函数本身就没有arguments,如果一个箭头函数在一个function内部,它会将外部函数arguments。箭头函数中要想接收不定参数,应该使用rest参数…解决)
let B = (b)=>{
console.log(arguments);
}
B(2,92,32,32); // Uncaught ReferenceError: arguments is not defined
let C = (...c) => {
console.log(c);
}
C(3,82,32,11323); // [3, 82, 32, 11323]
d、箭头函数通过call和apply调用,不会改变this指向,只会传入参数。
let obj2 = {
a: 10,
b: function(n) {
let f = (n) => n + this.a;
return f(n);
},
c: function(n) {
let f = (n) => n + this.a;
let m = {
a: 20
};
return f.call(m,n);
}
};
console.log(obj2.b(1)); // 11
console.log(obj2.c(1)); // 11
e、箭头函数没有原型属性。
var a = ()=>{
return 1;
}
function b(){
return 2;
}
console.log(a.prototype); // undefined
console.log(b.prototype); // {constructor: ƒ}
f、箭头函数返回对象时,要加一个小括号()。
var func = () => ({ foo: 1 }); //正确
var func = () => { foo: 1 }; //错误
g、箭头函数在es6 class中声明的方法为实例方法,不是原型方法。
@@@@箭头函数和普通函数的区别@@@@@@。
- 普通函数总是指向它的直接调用者;
- 在非严格模式下,没找到直接调用者,this是指向window;否则,this指向undefined;
- 箭头函数的this永远指向其上下文的this,任何方法都改变不了其指向,如call()、apply()、bind()。
8)class类
es6为了提供更接近传统语言的写法,引入了class(类)这个概念,作为对象的模板。通过class类关键字,可以定义类,与多数传统语言类似。
//定义类
class Point{
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '('+this.x+','+this.y+')' ;
}
}
9)set
set对象是值得集合,元素只会出现一次,即set中的元素是唯一的。
数组去重:[…set对象]
var arr=[1,2,3,3,3,3,14]
var set = new Set(arr)
var arr = Array.from(set) //将set结构的数组转换为真正的数组
cosole.log(arr) //1,2,3,14
36、js中常见的异步编程方式。
1)回调函数的实现;(回调函数就是把任务拆解成两部分,把任务的第二部分单独写在一个函数里,等到执行完其他任务重新执行这个任务的时候,就直接调用这个函数,从而达到异步效果)
2)发布订阅、通知;
3)事件监听;
4)promise/A+和生成器函数;
5)async/await
37、详解promise。
1)什么是promise?
promise就相当于事件和方法中的中转站,将事件和方法解耦,从而使整个异步过程变得清晰。
promise就是为异步事件的结果准备的占位符,该占位符承接异步事件的状态,针对不同状态来开启针对异步事件的方法的执行。promise通过它的生命周期来承接异步事件的状态。
2)promise的生命周期?
promise内部存在([promiseState])属性,该属性有“pending”,“fulfilled”,“rejected”来反映异步事件的状态。
- pending(挂起态):异步事件尚未结束;
- fulfilled(已完成):异步事件已成功结束;
- rejected(已拒绝):异步事件未成功结束,可能发生了一个错误。
该属性会伴随着异步事件的发生流程而改变,我们通过then()方法监听该属性,来开启针对不同流程所执行的方法。promise初始为挂起态,只可以单项转换为已完成或者已拒绝
。无论转换为哪种状态,promise都可以称为已决态。
3)promise的创建。
promise通过promise构造器来创建。该构造器接受单个参数:一个被称为执行器(executor)的函数,该函数包括初始化promise的代码,并且被传递两个名为resolve()与reject()的函数作为参数。resolve()在执行器结束后被调用,表示进入fulfillde的状态,而当执行操作失败时reject()被调用,表示进入rejected的状态。我们通过then()方法监听promise的状态,第一个参数为fulfilled状态调用的函数,第二个函数为rejected状态调用的函数。
let fs = require("fs");
let promise = new Promise(function(resolve,reject){
fs.readFile(filename,{encoding: "utf-8"},function(err,contents){
if(err){
//执行器操作失败,进入rejected状态
reject(err);
return
}
//执行器操作成功,进入fulfilled状态
resolve(contents);
});
});
//分别监听两个状态并输出执行器返回值
promise.then(function(contents){
console.log(contents);
},function(err){
console.error(err.message);
});
在promise中执行器会立刻执行,resolve()或者reject()的完成会作为异步事件操作结束的标志,转换为promise状态并添加进事件循环队列,以便then函数进行后续监听。
then()方法在所有promise上都存在,传递给then的两个参数都可选,因此可以进行各种形式与数量的监听与拒绝函数。
当只监听rejected状态时,可以使用更简单的catch()方法。
4)多个promise的响应。
有时我们可能想同时监视多个异步操作,以便根据总体情况决定接下来的行动,es6提供了两个方法:
- promise.all() (此方法用于将多个promise实例包装成一个新的promise实例,接收一个数组作为实例)
var p = Promise.all([a,b,c]); //a,b,c都是Promise的实例
该方法接受单个可迭代对象作为参数并返回一个Promise。这个参数元素都是Promise,只有在它们都被决议后,返回的Promise才会被决议。如果所有参数Promise都是fulfilled状态,则返回的Promise也是fulfilled状态并接收一个包含每个决议值的数组。若任意参数Promise被拒绝,那么返回的Promise会立刻被拒绝。
- promise.race() (同样是将多个Promise实例包装成一个新的Promise实例,与Promise.all()方法类似;将一个非Promise对象转换为一个Promise对象)
var promise = Promise.resolve($.ajax("./test.txt"));
该方法接受参数同上,但返回的promise状态如同该方法名,与最快转化为已决态的参数promise相同。
38、js的运行机制。
js是单线程的
js是浏览器脚本语言,用于与用户互动、操作dom,注定只能是单线程的。H5允许jd创建多个线程,但是子线程完全受主线程控制,且不得操作dom。
任务队列和事件循环
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
异步任务:不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行
过程: 只要主线程空了,就去读取任务队列,并不断重复。这个过程就是事件循环(Event Loop)
(1)主线程-执行栈(execution context stack)-同步任务
(2)任务队列(tash queue)-异步任务
任务队列中的事件:IO设备、用户产生事件、定时器事件、Promise
注:HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在苹果机上的最小时间间隔是10毫秒,在Windows系统上的最小时间间隔大约是15毫秒。
39、js垃圾回收机制
1)什么是垃圾回收机制?
js的垃圾回收机制是为了防止内存泄漏的,内存泄漏的含义就是当已经不需要某块内存时这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找不再使用的变量,并释放他们所指向的内存。
2)变量的生命周期
当一个变量的生命周期结束之后它所指向的内存就应该被释放。js有两种变量,全局变量和在函数中产生的局部变量。局部变量的生命周期在函数执行过后就结束了,此时便可将它引用的内存释放(即垃圾回收),但是全局变量生命周期会持续到浏览器关闭页面。
3)js垃圾回收方式
JS执行环境中的垃圾回收器怎样才能检测哪块内存可以被回收有两种方式:标记清除(mark and sweep)、引用计数(reference counting)。
- 标记清除(mark and sweep):
大部分浏览器以此方式进行垃圾回收,当变量进入执行环境(函数中声明变量)的时候,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”,在离开环境之后还有的变量则是需要被删除的变量。标记方式不定,可以是某个特殊位的反转或维护一个列表等。
垃圾收集器给内存中的所有变量都加上标记,然后去掉环境中的变量以及被环境中的变量引用的变量的标记。在此之后再被加上的标记的变量即为需要回收的变量,因为环境中的变量已经无法访问到这些变量。
- 引用计数(reference counting):
这种方式常常会引起内存泄漏,低版本的IE使用这种方式。机制就是跟踪一个值的引用次数,当声明一个变量并将一个引用类型赋值给该变量时该值引用次数加1,当这个变量指向其他一个时该值的引用次数便减一。当该值引用次数为0时就会被回收。
40、call、apply、bind之间的区别。
它们的共同点:改变函数运行时this的指向。
1)call:方法第一个参数是要绑定给this的值,后面传入的是一个参数列表。当第一个参数为null或者undefined的时候,默认指向window。
var obj = {
message: 'My name is: '
}
function getName(firstName, lastName) {
console.log(this.message + firstName + ' ' + lastName)
}
getName.call(obj, 'Dot', 'Dolby')//My name is: Dot Dolby
2)apply:接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。第一个参数为null或undefined的时候,默认指向window。
var obj = {
message: 'My name is: '
}
function getName(firstName, lastName) {
console.log(this.message + firstName + ' ' + lastName)
}
getName.apply(obj, ['Dot', 'Dolby'])// My name is: Dot Dolby
3)bind:和call很相似,从第二个参数开始是接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用。(返回对应函数,便于稍后调用;则call和apply是直接调用)
var obj = {
name: 'Dot'
}
function printName() {
console.log(this.name)
}
var dot = printName.bind(obj)
console.log(dot) // function () { … }
dot() // Dot