JS中的THIS处理及正则表达式 — 1、call&&apply&&json

246 阅读43分钟

1、大纲

面向对象深入了解

  • 函数的三种角色:函数也是对象
  • Function
  • Function.prototype:call/apply/bind
  • 面试题讲解
  • ...

call && apply

获取数组最大值最小值

  • eval及括号表达式

获取平均数

  • 类数组转换数组
  • try、catch、finally
  • 浏览器中常用的报错信息

综合案例:商品排序

AJAX获取数据及数据绑定

  • 前后端协作模型
  • AJAX获取数据四步操作
  • JSON
  • ES6模板字符串做数据绑定

实现表格排序

  • sort原理
  • DOM映射机制
  • 单列升序
  • 单列升降序切换
  • 多列升降序切换

2、面向对象深入(函数的三种角色)

函数的三种角色

第一种角色:普通函数

  • 栈内存(私有作用域)

  • 作用域链

  • 形参

  • arguments

  • return

  • 匿名函数、实名函数、函数表达式

  • ...

第二种角色:类

  • 实例
  • 私有和公有属性
  • prototype(原型)
  • _proto_(原型链)
  • ...

第三种角色:普通对象

  • 键值对操作
  • ...
function Fn(){

}
Fn();//普通函数
new Fn();//类
Fn.prototype;//普通对象(只有对象才有属性名和属性值,这里函数也有自己的属性prototype,所以函数也是一个对象)

三种角色之间没有直接的关系


function Fn(){
    var name='珠峰培训';
    this.age=8;
}
Fn.prototype.say = function(){}
Fn.eat = function(){}
Fn();
var f = new Fn();

=> 私有变量name和f没有关系,这个name只是把它作为普通函数执行的时候形成一个私有作用域中的一个私有变量,这是作为 普通函数角色里面应该具备的东西

=> age和f有关系,age属于new Fn执行的时候this就是当前实例f,属于给f加的一个私有属性age,跟实例f有关系

=> say和f有关系,实例f可以通过__proto__这个属性找到它所属类Fn的原型prototype,所属类原型下的方法都属于公有的属性和方法

=> eat和f没有关系,Fn.eat只是相当于给普通对象Fn加了一个普通属性eat,跟实例没有关系(实例是把Fn当做类这个角色才有的东西) Fn.eat:当前函数作为普通对象的一个私有属性,跟实例没有关系

绘制原型图,突出函数的三种角色

function Fn(){
    var name='珠峰培训';
    this.age=8;
}
Fn.prototype.say = function(){}
Fn.eat = function(){}
var f = new Fn();

栈内存(作用域)堆内存全部画出来(直角:栈内存;圆角:堆内存;绿色:函数堆内存;橙色:对象堆内存)

=> 全局作用域window下变量提升:声明var f;声明加定义Fn,Fn是一个函数(函数是引用数据类型)所以开辟一个堆内存AAAFFF111存储代码字符串,把堆内存地址AAAFFF111赋值给Fn

=> Fn是一个函数,函数都天生自带prototype属性(只有对象才有属性,所以这个堆内存AAAFFF111代表函数堆内存但是也属于对象数据类型)

prototype本身就是一个对象数据类型的(函数有对象这个角色,但是对象没有函数这个角色),所以浏览器会开辟一个新的堆内存BBBFFF111里面有constructor:Fn(constructor的值就是当前函数本身),把这个堆内存地址BBBFFF111赋值给prototype

函数除了天生自带prototype属性,还有以下属性(这里我们关注两个重要属性length:形参个数和name:函数名)

结论: 函数(图中绿色)有三种角色:对象、类、函数。作为普通函数存储的是代码字符串;作为对象里面有很多属性(name、length、_proto_),而且有一个属性__proto__(所有对象都有这个属性);作为类里面有个prototype属性,prototype原型的值本身又是一个对象,所以开辟新的堆内存存储给实例提供的公共属性和方法。

prototype开辟的堆内存(图中橙色)只有一种角色:就是对象,由于它是类原型的一个对象,所以有一个constructor(这是特殊的)

=> 由于所有的对象数据类型都天生自带__proto__属性,所以所有的prototype(prototype本身就是个对象)对应的堆内存里面都会有属性__proto__

=> 由于函数也是一个对象,所以函数也有__proto__属性

结论: 所以图中的堆内存中都有__proto__属性

=> 由于所有的对象都是Object类的实例,Object是一个类,所有的类都是函数(绿色代表函数):所以作为函数存储代码字符串;作为内置类拥有prototype属性并且值是一个纯对象,浏览器个这个纯对象开辟一个新的堆内存BBBFFF222(橙色)天生自带属性有constructor:Object(constructor的值就是当前函数本身),由于Object是一个内置类,所以有一些自己的方法(toString、hasOwnProperty);作为对象有自己的一些属性(name:Object、length:1)(所有内置类的length值一般都是1,都默认只有一个形参)

=> 所有的对象都是Object类的实例,所有对象的__proto__都指向当前对象所属类的原型,所以: 浏览器为prototype这个对象开辟的所有堆内存(橙色)的__proto__都指向Object的原型(BBBFFF222这个堆内存)

=> Object是对象类型的基类(最底层类),在它的原型上没有__proto__这个属性:因为即使有这个属性指向的也是自己,这个没有必要。其实准确来说是有的,只不过存储的值是null,我们可以理解为没有

=> 函数(绿色:即是函数又是对象)里面的__proto__指向哪里?

由于每个__proto__都指向当前对象所属类的原型,那函数的类是?

由于内置类里面还有一个Function,大写的Function是所有的函数的所属类,所有的函数都是大写的Function这个内置类的一个实例

所以所有函数(绿色)中的__proto__都指向Function的原型(BBBFFF333)

所有对象的类都是Object,所有对象是Object类的一个实例,

=> Function和Object一样都是内置类,所有的类都是函数(绿色画出Function):作为函数存储代码字符串(native code);作为类有一个prototype属性,也是一个对象,浏览器为其开辟新的堆内存BBBFFF333存储一些内置的方法(call、apply、bind...)并且有个属性constructor:Function(constructor的值就是当前函数本身);作为对象有属性name:Function、length:1(所有内置类的length值一般都是1,都默认只有一个形参)、_proto_,

=> 由于堆内存BBBFFF333是一个纯对象(浏览器为prototype对象开辟的所有堆内存都是一个纯对象),所有对象都是Object的实例,所以堆内存BBBFFF333的__proto__指向Object的prototype(所有__proto__都指向当前对象所属类的原型)

结论: 浏览器为prototype对象开辟的所有堆内存(橙色)的__proto__最终都指向Object的prototype(BBBFFF222)

结论: 所有类、函数(绿色)都是大写Function的一个实例,所以所有函数、类的__proto__最终都指向Function的prototype(BBBFFF333)

到目前为止:全局下的变量提升完了

最后总结: 对象就是对象,只有一个角色,函数不仅仅是函数,还可以是类和对象。(可以理解为:类就是函数,函数就是类,只不过执行的时候有区别,所以函数有三种角色也可以理解为函数有两种角色:函数和对象,函数又分为普通函数和类。)

既然函数有两种角色:函数和对象,那函数既有函数的特点也有对象的特点,作为函数的特点存储代码字符串;作为对象的特点有自己的属性名和属性值,比如:name、length、prototype和__proto__ 其中prototype(作为函数有prototype属性)和__proto__(作为对象有__proto__属性)是之前(15、原型的基础操作(核心操作原理))讲过

函数的prototype属性是一个对象数据类型,浏览器会为其开辟一个堆内存(纯对象),Object、Fn、Function都是属于函数类型,所以都有各自prototype对应的堆内存(三个橙色框框)

所有对象都有__proto__属性,(函数是对象,prototype属性是对象数据类型),所以所有框框都有__proto__

__proto__属性指向自己所属类的原型,所有对象都是Object类的一个实例,所以最终都指向了Object的原型BBBFFF222(Object.prototype.proto = null)

所有对象是Object类的一个实例,所有的函数都是大写的Function这个内置类的一个实例,所以所有函数的__proto__属性都指向了大写Function的原型BBBFFF333

prototype和__proto__的区别:

prototype:仅仅是给函数原型开辟的一个新的堆内存,存储公共属性和方法,跟原型链没有关系

_proto_:原型链,指向所属类的原型(是谁的实例就执行谁的原型)

** Function和Object的区别:**

Object:是一个函数,是Function的一个实例,Object类是Function类的一个子类,但是Function类的原型通过__proto__又指向了Object的原型,所以我们应该这么说:

Object这个类本身是Function这个类的实例,Function这个类的原型是Object这个类的一个实例,但是由于Function即是函数又是对象也有__proto__,Function通过__proto__找到原型对应的堆内存BBBFFF333,再通过BBBFFF333堆内存下的__proto__最终找到Object原型对应的堆内存BBBFFF222,所以说也可以理解为:Function也是Object这个类的实例(因为Function它就是一个函数,函数就是对象,对象就是Object类的实例)

这六个框框其实都是对象,所有对象都是Object类的实例,Object原型上的方法所有对象都能用:所有的对象(函数也是对象)都可以调取Object.prototype的方法toString/hasOwnProperty

所有的函数(绿色框框)都是Function类的实例,Function原型上的方法所有函数都能用:所有的函数(类也是函数)都可以调取Function.prototype的方法:call/apply/bind

3、阿里巴巴面试题(有难度)

www.cnblogs.com/faith3/p/62… js中的new()到底做了些什么??(可以看看)

function Foo() {//Foo()即是函数也是对象
    getName = function () {
        console.log(1);
    };
    return this;
}
Foo.getName = function () {//把Foo当成对象角色增加属性getName
    console.log(2);
};
Foo.prototype.getName = function () {//Foo.prototype作为类在原型生增加公有方法getName
    console.log(3);
};
var getName = function () {//全局变量getName
    console.log(4);
};
function getName () {//全局作用域下有个函数getName
    console.log(5);
}


Foo.getName();//=>2 把Foo作为对象,找其私有属性
getName();//=>4 执行全局下的getName() 
Foo().getName();//=>1 先把Foo作为普通函数执行,把执行的结果调取getName()再执行
getName();//=>1
new Foo.getName();//=>2 先获取Foo.getName的值(假设B),然后在new B()相当于创建B的实例
new Foo().getName();//=>3 new Foo()获取实例,把得到的实例再调取getName()
new new Foo().getName();//=>3
//=> var f = new Foo()
//=> f.getName(); => f是Foo()的实例,先找私有方法没有getName,再找公有下的getName()执行输出3

=> window全局作用域下变量提升:

  • 声明变量var getName,声明加定义getName(),这两个变量重名,不需要重新声明, 但是需要定义(定义的时候开辟堆内存AAAFFF111存储输出5这个函数的代码字符串),这里先考虑getName,

  • 变量提升 var getName = function () {console.log(4);}只会执行了等号左边的,不会执行等号右边的,

  • 然后走到 function getName () {console.log(5);} 需要声明加定义 getName,但是 getName 已经声明过了,所以 这里不需要再声明只需要定义,定义的时候开辟堆内存AAAFFF111存储输出5这个函数的代码字符串,然后把这个堆内存地址 AAAFFF111赋值给变量getName

  • 声明加定义Foo,开辟堆内存AAAFFF222存储代码字符串(Foo = 堆内存AAAFFF222),函数(对象)有自己的内置属性prototype和_proto_(函数的 _proto__指向Function原型),prototype是个对象,浏览器为其开辟堆内存BBBFFF111并且有默认属性constructor:Foo (constructor属性存储的值就是当前函数本身)和属性__proto_:Object.prototype(属性值是当前对象所属类的原型prototype),

=> 全局下变量提升完后,代码自上而下执行:

  • Foo():在变量提升阶段已经完成了,不用管了

  • Foo.getName = function () {console.log(2);}:把Foo(堆内存AAAFFF222)当成普通对象增加属性getName,值是一个函数输出结果2

  • Foo.prototype.getName = function () {console.log(3):给Foo.prototype这个原型生增加公有属性getName,值是一个函数输出结果3

  • var getName = function () {console.log(4);}:等号左边已经变量提升过了,等号右边给全局变量getName赋值一个函数输出4(之前输出 5的堆内存AAAFFF111过段时间就销毁,所以代码执行中所有代码里面不可能输出5这个答案)

输出结果步骤:

=> Foo.getName()执行:Foo.getName相当于找Foo(Foo = 堆内存AAAFFF222)的属性getName,所以输出2

=> 全局下的getName()执行输出4

=> Foo().getName():先把Foo()作为普通函数执行,把执行的结果调取getName()再执行;

  • Foo()作为普通函数执行,开辟新的私有作用域Foo(),新参赋值和变量提升都无;

  • 代码自上而下执行,变量getName不是私有变量,找上级作用域(全局作用域), 所以getName = function () {console.log(1);}是把全局变量值改了,全局下的变量getName = function () {console.log(1);}, 最后return this = return window(普通函数执行this是window);(这一步就是执行Foo(),把全局变量getName改成输出1的函数,再调取getName()执行)

  • 把执行的结果调取getName()再执行,相当于window.getName() 输出结果1

=> getName()执行,输出结果1

=> new Foo.getName():(等价 new Foo.getName 这里可以不加括号)先获取Foo.getName的值(输出2的函数,假设B),然后再new B()相当于创建B的实例(类似:obj.getX();先获取obj的getX的属性值,然后把获取的值执行)

  • 等价于 new function () {console.log(2)};

new Fn():先把Fn()当普通函数执行,把执行完成默认创建的实例返回,也就是说new Fn()执行既有普通函数执行的一面也有构造函数执行的一面,

所以new function () {console.log(2)}执行相当于把这个函数执行了,只不过执行new function ()的时候,作为构造函数去执行的时候除了形参赋值、变量提升,进来第一步是先创建一个实例,然后以this为主题(比如这里是var aa = new Foo.getName(),那这个this就是aa),this.xxx = xxx都相当于给实例加的私有属性,最后不写return也默认返回this,但是这个函数里面这些步骤都没有,所以不用管了;

但是最起码也得作为普通函数去执行,让function () {console.log(2)}去执行输出结果:2

=> new Foo().getName():先执行new Foo()创建Foo()这个类的实例,实例再调取getName()

  • new Foo()相当于执行Foo()这个方法让全局变量 getName = 1(new Foo()先把Foo()执行才有构造函数创建实例),return this:this是当前Foo()这个类的实例,
  • 实例.getName(),由于Foo()这个类里面没有私有方法getName(),通过prototype找到公有方法getName输出3

=> new new Foo().getName():

  • 先执行new Foo(),假设它有个实例f,var f = new Foo()
  • new f.getName():先让f.getName执行,f是Foo()的实例,由于Foo()这个类里面没有私有方法getName(),所以最后执行的还是Foo()下的公有方法getName输出3

typeof typeof typeof [] => "string"

4、call、apply、bind的区别

call、apply、bind

  • 都是天生自带的方法(Function.prototype),所有的函数都可以调取这三个方法(因为所有函数都是Function这个类的实例)

call

fn.call(context,para1,...)

把fn方法执行,并且让fn方法中的this变为context,而para1...都是给fn传递的实参

非严格模式下

function fn(){
    console.log(this);
}
var obj = {fn:fn};
fn();//=>this:window
obj.fn();//=>this:obj

var opp={};
// opp.fn();//=>报错:opp中没有fn这个属性
fn.call(opp);//=>this:opp num1&&num2都是undefined
fn.call(1,2);//=>this:1 num1=2 num=undefined
fn.call(opp,1,2);//=>this:opp num1=1 num2=2

非严格模式下call方法的几个特殊性

fn.call();//=>this:window(非严格模式下没有指定执行主体默认是window) num1&&num2都是undefined
fn.call(null);//=>this:window
fn.call(undefined);//=>this:window

JS严格模式下call方法的几个特殊性(项目开发都用严格模式,因为bebal转化es5时都转化成严格模式)

"use strict";
fn.call();//=>this:undefined
fn.call(undefined);//=>this:undefined
fn.call(null);//=>this:null

apply

apply的语法和call基本一致,作用原理也基本一致,唯一的区别:apply把传递给函数的实参以数组形式存放(但是也相当于在给函数一个个的传递实参)

fn.call(null,10,20,30);
fn.apply(null,[10,20,30]);//虽然apply是通过数组传递参数的,但是传递参数给fn的时候也是一个个的传递进去的

bind

也是改变this的方法,它在IE6~8下不兼容;它和call(以及apply)改变this的原理不一样

call():是立马执行

bind():预先让fn中的this指向opp,并且把10和20预先传递给fn,此时的fn没有 被执行(只有当执行的时候this和实参才会起到应有的作用)

fn.call(opp,10,20);//=>把fn执行,让fn中的this变为opp,并且把10&&20分别传递给fn
fn.bind(opp,10,20);//=>预先让fn中的this指向opp,并且把10和20预先传递给fn,此时的fn没有被执行(只有当执行的时候
//this和实参才会起到应有的作用)

项目中什么时候会用bind不用call呢?

需求:点击box这个盒子的时候,需要执行fn,并且让fn中的this指向opp

oBox.onclick = fn;//=>点击的时候执行了fn,但此时fn中的this是oBox
oBox.onclick = fn.call(opp);//=>绑定事件的时候就已经把fn立即执行了(call本身就是立即执行函数),然后把fn执行的返回值绑定给事件
oBox.onclick = fn.bind(opp);

//=>fn.bind(opp):fn调取Function。prototype上的bind方法,执行这个方法返回了一个匿名函数
// function(){
//     fn.call(opp);
// }
// oBox.onclick = function(){
//     //=>this:oBox
//     fn.call(opp);
// }

总结:

Function内置类原型上有三个方法可以改变this指向:call、apply、bind

call是把方法执行(方法立马执行),this改变成第一个参数值;在非严格模式下,第一个参数不写或者写成undefined和null,this都是window;在严格模式下,第一个参数写什么this就是什么,不写就是undefined;第二个参数以及第二个参数以后的参数都相当于给方法执行的时候传递实参。

apply和call的区别都是把方法里面执行,只不过执行传参的时候,call是一个个传,apply是放在一个数组里传进来,但是也相当于一个个传

bind和call、apply的原理就不一样了,bind并没有让函数立马执行,只是把this指向和参数传递预先的准备好,只有当执行的时候this和实参才会起到应有的作用

5、有关call的面试题

function fn1(){
    console.log(1);
}
function fn2(){
    console.log(2);
}
fn1.call(fn2);
fn1.call.call.call(fn2);
Function.prototype.call(fn2);
Function.prototype.call.call.call(fn2);

xxx.fn():相当于把某一个函数执行,比如说

面向对象实例调取方法的执行步骤如下

ary.slice():执行slice方法,ary.slice:数组这个类的一个实例ary通过它的原型链查找机制,找到 Array原型上的slice方法:

Array.prototype.slice = function () {

};

ary.slice指的就是等号右边这个函数,ary.slice()就是让等号右边这个函数执行,只不过这个函数执行的时候 可以实现把ary当中的某一个索引到某一个位置之间的数据劫持到。

所以说: 实例调取方法的执行不是像我们说的ary.slice()从索引n开始找到索引m项把找到的新数组返回,原有数组不变,这只是我们总结的slice方法的作用,但是ary.slice()第一步并不是slice干什么,而是ary实例先通过原型链查找机制找到Array原型上的slice方法并且让slice方法执行,只不过在执行slice方法的时候,实现了我们想要的功能,而这些功能是因为slice是内置的方法,里面有很多内置的代码,都是浏览器赋予它的功能

(第一步永远是先找方法把方法执行;第二步才是执行方法的时候体现了我们需要的功能)

那以上这个明白了的话,我们就来写写fn1.call(fn2)的意思:

首先fn1.call的意思:fn1这个Function的实例通过__proto__找到Function.prototype上的call方法,然后 让call方法执行(传递fn2这个实参)

换句话说,以后遇到类似这种,比如(1).toFixed(),它的核心原理:1这个实例在Number这个类的原型上找到 toFixed的方法,让toFixed这个方法执行,只不过在执行toFixed这个方法的时候实现了把1保留小数点后面几位,( 首先第一步是去找toFixed这个方法并让这个方法执行,只不过这个方法在执行内置代码的时候实现了我们想要的需求而已)

同样call也是一样的,先找到Function.prototype上的call方法,让call方法执行内置代码实现需求,如下

Function.prototype.call = function call(context) {
    // [native code]
    //1、把指定函数中的this指向context
    //2、把指定函数执行
};

// 比如:fn.call(opp):把fn中的this指向opp并且让fn执行,之所以能让fn执行,让fn中的this变成opp主要原因是因为
// call这个方法来决定的,所以原理是fn这个Function的实例通过__proto__找到Function.prototype上的call方法,然后
// 让call方法执行,call方法执行的时候让fn中的this变成opp并且fn执行

以下都是让call方法执行,区别就是this不一样

fn.call();//call方法中的this是fn
fn.__proto__.call();//(不考虑兼容)call方法中的this是fn.__proto__(也相当于Function.prototype)
Function.prototype.call();//call方法中的this是Function.prototype

//call方法内置代码的执行机制如下
//----------------------------------------------------------------------------------------------
fn.call();//call方法中的this是fn
Function.prototype.call = function call(context) {
    //=>call方法中的this:fn
    
    // [native code]
    //1、把指定函数中的this指向context [把this:fn中的this关键字指向context]
    //2、把指定函数执行 [把this:fn执行]
    //this();
};

call的例子明白了的话,我们就可以来分析这道面试题了

需要明确知道call方法实现原理以及实例调取方法执行的操作步骤(需要把这些核心底层基础知识掌握得扎实才能一步步分析出来)

function fn1(){
    console.log(1);
}
function fn2(){
    console.log(2);
}
fn1.call(fn2);

// fn1.call的意思:fn1这个Function的实例通过__proto__找到Function.prototype上的call方法,然后
// 让call方法执行(传递fn2这个实参)

// 执行call的时候,call中的this:fn1,所以此处是把fn1执行输出1,让fn1中的this指向fn2(只不过我们fn1中不需要this)

// Function.prototype.call = function call(context) {
//     //=>call方法中的this:fn1
    
//     // [native code]
//     //1、把指定函数中的this指向context [把this:fn中的this关键字指向context]  this指向fn2
//     //2、把指定函数执行 [把this:fn执行]  fn1执行 => 输出1
//     //this();
// };

fn1.call.call.call(fn2);

// fn1.call.call.call:依然是找到原型上的call方法,并且让call执行(fn1.call:fn1找到原型上的call方法,call方法是个
// 函数,所以fn.call是个函数,fn.call.call = 函数.call = 找原型上的call方法 = 函数,所以fn.call.call最后还是一个函数
// ,所以fn1.call.call.call = 函数.call = 找原型上的call方法 = 函数,写一百个.call最终还是找的Function原型上的call,
// 整个js当中就Function原型上有个call,所以不管执行多少个call,最终都是找到原型上的call方法,并且让call执行)

// 此时最后一次执行call方法中的this是fn1.call.call(原型上的call)

// 所以当前代码执行:让原型上的call方法执行,并且让原型call方法中的this变成了fn1.call.call(还是原型上的call)

// Function.prototype.call = function call(context) {
//     //=>call方法中的this:fn1.call.call(原型上的call)
    
//     // [native code]
//     //1、把指定函数中的this指向context [把this:fn中的this关键字指向context]  call中的this执行fn2
//     //2、把指定函数执行 [把this:fn执行]  call()执行
//     //this();
// };

// call()执行的时候又会去执行Function类原型上的call方法

// Function.prototype.call = function call(context) {
//     //=>call方法中的this:fn2
    
//     // [native code]
//     //1、把指定函数中的this指向context [把this:fn中的this关键字指向context]  让fn2中的this指向context,但是函数fn2中没有传参,所以是undefined
//     //2、把指定函数执行 [把this:fn执行]  fn2()执行 => 输出2
//     //this();
// };


//------------------------------整个执行流程总结如下-------------------------------
// fn1.call.call.call:依然是找到原型上的call方法,并且让call执行
//     call中的this:fn1.call.call[原型上的call]
//     call中的context:fn2

//     让 [原型上的call(fn1.call.call)] 中的this指向 fn2
//     让 [原型上的call(fn1.call.call)] 执行
//         第二次执行原型上的call,只不过此时call中的this是fn2
//             让fn2中的this执行undefined
//             让fn2执行 => 输出2


Function.prototype.call(fn2);

// 找到原型上的call方法,让call方法执行
//     call执行:
//     =>this:Function.prototype
//     =>context:fn2
//     把Function.prototype中的this指向fn2,
//     让Function.prototype执行=>无输出(因为Function.prototype是个匿名空函数)

//     (在整个js中,只有Function.prototype的是特殊的,它的所有操作步骤和Object.prototype一样(有constructor,__proto__),
//     操作的时候按照一个对象来操作是没有问题的,js的核心原理也是这么来做的,但是js当中有一个函数:匿名函数
//     ,匿名函数是基于Function.prototype来做的,而Function.prototype是个匿名空函数(是个函数类型的))

Function.prototype.call.call.call(fn2);

// 等价于fn1.call.call.call(fn2);都是找到原型上的call执行

实现call方法

怎么让指定函数中的this指向context呢?函数没执行之前只是一些代码字符串,怎么让他this变成context呢?

比如想让这个this变成opp

1、把函数整体变成字符串

2、替换掉所有的this

3、eval()让字符串整体变成js表达式

4、让fn()执行,this变成{}

6、获取数组最大值和最小值(括号表达式应用)

4、解决方案一: 先排序,掐头去尾

var ary = [12, 23, 14, 25, 34, 11, 16];

ary.sort(function (a, b) {
    return a - b;
});

console.log(ary[ary.length - 1]);
console.log(ary[0]);

3、解决方案二: 假设法,先假设一个值,然后再去验证真假即可(项目中经常用到的编程思想,还有个是私有属性思想)

先假设第一个是最大值,然后循环数组中的每一项,和假设值进行比较,如果当前遍历的这一项比假设的值 大,我们替换假设的值即可

var ary = [12, 23, 14, 25, 34, 11, 16];

var max = ary[0],
    min = ary[0];
    
for (var i = 1; i < ary.length; i++) {
    var item = ary[i];//获取每次循环对象遍历的值
    item > max ? max = item : null;
    item > min ? min = item : null;
}

console.log(max, min);

解决方案三: eval()字符串转换成js表达式执行


var ary = [12, 23, 14, 25, 34, 11, 16];

能否利用Math.max/Math.min实现我们的需求?

Math.max([12, 23, 14...]) =>NaN
Math.max(12, 23, 14...) =>34 执行max方法,我们需要把比较的一堆数值一项项的传递给这个方法才可以,传递一个数组整体是不行的


console.log(Math.max([12, 23, 14, 25, 34, 11, 16]));//NaN
console.log(Math.max(12, 23, 14, 25, 34, 11, 16));//34

需要把 Math.max([12, 23, 14, 25, 34, 11, 16]) 转化成 Math.max(12, 23, 14, 25, 34, 11, 16)

第一次尝试转化:
=> [12, 23, 14, 25, 34, 11, 16].toString() : "12, 23, 14, 25, 34, 11, 16"
=> eval("12, 23, 14, 25, 34, 11, 16") : 16 只有最后一项

为什么eval()只能得到最后一项呢,这里涉及到js中的一个概念=>js中的括号表达式:

(function(){
    console.log(1);
},function(){
    console.log(2);
})();//只输出了2(只把第二个函数执行了,第一个不操作)

1、有上代码可知:小括号中出现多项(每一项之间用逗号分隔),操作只有最后一项

所以 eval("12, 23, 14, 25, 34, 11, 16") 输出16,也只有最后一项

2、括号表达式会影响this的指向,比如:

=>括号中只有一项,this还是按照没加括号的时候处理即可

=>括号中有多项,执行最后一项的时候,方法中的this就是window(严格模式下this是undefined):[浏览器 把这种情况理解为自执行函数执行处理的]

window.name = 'WIN';
function fn(){
    console.log(this.name);
}
var obj = {name:'OBJ',fn: fn};
// obj.fn();//=>'OBJ'
(obj.fn)();//=>'OBJ'
(fn, obj.fn)();//=>'WIN' (严格模式下this是undefined,会报错)

=>项目中能不用括号表达式就不用,但是面试会问到,所以这里写写

解决方案三的正确转换思路:

先使用字符串拼接,把需要执行的语句拼成为字符串,最后EVAL一下即可 真实项目中尽量较少eval的使用(防止代码压缩成为一行后,eval导致代码结构或者执行混乱)

var ary = [12, 23, 14, 25, 34, 11, 16];

// 思路如下:
// ary.toString()=>'12, 23, 14, 25, 34, 11, 16'

// 最终要执行的:
// Math.max(12, 23, 14, 25, 34, 11, 16)

// 所以想办法把字符串'12, 23, 14, 25, 34, 11, 16'变成字符串'Math.max(12, 23, 14, 25, 34, 11, 16)'再整个
// eval('Math.max(12, 23, 14, 25, 34, 11, 16)')=>输出34

var max = eval('Math.max(' + ary.toString() + ')'),
    min = eval('Math.min(' + ary.toString() + ')');
console.log(max, min);

2、解决方案四: 用Math.max的另外一种方法实现(利用applay的特征)

var ary = [12, 23, 14, 25, 34, 11, 16];
// 思路如下:
// 把数组中的每一项一个个的传递给MAX即可
// Math.max(12, 23, 14, 25, 34, 11, 16);

// apply的特点:虽然编写的是一个数组,但是也相当于在执行函数的时候一项项的传递实参
// fn.call(null,100,200);
// fn.apply(null,[100,200]); <=> fn(100,200)

// 既然apply有这样的语法,Math.max()这样执行max方法中的this是Math,Math.max.apply(Math)在执行apply方法的时候让Math.max()执行,让max方法中的this指向Math,这两种执行是一样的,但是执行Math.max()时用不到this问题,所以Math.max.apply(null)传null也行
//Math.max() <=> Math.max.apply(Math)
<!--// 所以-->
<!--Math.max.apply(null,[12, 23, 14, 25, 34, 11, 16]);-->
<!--// 等价于:-->
<!--Math.max(12, 23, 14, 25, 34, 11, 16);-->

// 所以可以利用apply特征来实现
var max = Math.max.apply(null,ary),
    min = Math.min.apply(null,ary);
    console.log(min, max);

1、解决方案五: 利用ES6的展开运算符(解构赋值才是扩展运算符)来实现需求(原理:和apply这个一样)

var ary = [12, 23, 14, 25, 34, 11, 16];
let max = Math.max(...ary),
    min = Math.min(...ary);
    console.log(min, max);

面试写最先写解1、决方案五,再是2、解决方案四,3、解决方案二,4、解决方案一

7、获取平均数(借用原型方法,把类数组转换为数组)

//=>为了防止作弊的嫌疑:去除最高和最低分数,剩下的值就平均数
function avgFn() {
    // arguments:类数组,它不是Array的实例,不能调取数组中的方法
    // console.log(arguments instanceof Array);//检测arguments是不是Array的实例  false
    // console.dir(arguments);

    // 1、先把实参集合进行排序(去除第一个和最后一个)
    // 1)先把arguments(类数组)转换为数组
    var ary = [];
    for (var i = 0; i < arguments.length; i++) {
        ary[ary.length] = arguments[i];
    }
    ary.sort(function (a, b) {
        return a - b;
    });
    ary.shift();
    ary.pop();

    // 2、把集合中剩下的值求平均数(求和后除以长度即可)
    var total = null;
    for (var j = 0; j < ary.length; j++) {
        total += ary[j];
    }
    return (total/ary.length).toFixed(2);
}
console.log(avgFn(9.8, 9.6, 9.8, 8.8, 8.9));

代码优化

function avgFn() {
    var ary = [];
    for (var i = 0; i < arguments.length; i++) {
        ary[ary.length] = arguments[i];
    }
    
    // 链式写法:
    ary.sort(function (a, b) {
        return a - b;
    }).shift();
    ary.length--;//ary.length--比ary.pop()性能好

    // 利用join把数组转换成字符串和eval转换成js表达式求和
    return (eval(ary.join('+')) / ary.length).toFixed(2);
}
console.log(avgFn(9.8, 9.6, 9.8, 8.8, 8.9));

继续优化

function avgFn() {

    // var ary = [];
    // for (var i = 0; i < arguments.length; i++) {
    //     ary[ary.length] = arguments[i];
    // }
    // 以上是想把类数组装化成数组,因为arguments不能用sort,数组才能用sort
    // 但是arguments之所以不能用sort是因为arguments原型上没有Array的原型,arguments的原型链上找不到Array,所以不
    // 能用sort方法,所以我们可以通过arguments.__proto__ = Array.prototype:让arguments的原型链指向Array的原型
    //(利用面向对象原型指向的问题)

    //但是IE浏览器不兼容(IE下为了保护原型链禁止我们使用__proto__)
    arguments.__proto__ = Array.prototype;
    // console.dir(arguments);//输出可以看到arguments的__proto__指向了Array

    arguments.sort(function (a, b) {
        return a - b;
    }).shift();
    arguments.length--;
    
    return (eval(arguments.join('+')) / arguments.length).toFixed(2);
}
console.log(avgFn(9.8, 9.6, 9.8, 8.8, 8.9));

为了IE下也能兼容

借用数组原型上的slice方法,实现将类数组装换为数组

function avgFn() {
    // // 把arguments克隆成一个数组
    // var ary = [];
    // for (var i = 0; i < arguments.length; i++) {
    //     ary[ary.length] = arguments[i];
    // }

     // =>数组中实现克隆使用slice()一个参数都不传,借用数组原型上的slice方法,实现将类数组装换为数组
    // 原理:执行数组原型上的slice方法,让方法中的this指向要转换的类数组,这样在执行内置代码的时候,this已经变为arguments,相当
    //      于在操作arguments
    // 前提:必须是类数组才可以,因为类数组虽然不是数组,但是他的结构和数组基本上类似,也就是操作数组的那些循环判断等js语句,同样
    //       也能操作arguments这种类数组
    // =>类数组不仅可以借用slice,Array原型上的大部分方法都可以借来使用(原理都是this改变)
    var ary = Array.prototype.slice.call(arguments);
    // var ary = Array.prototype.slice.call(arguments,3);//从索引3开始截取

    ary.sort(function (a, b) {
        return a - b;
    }).shift();
    ary.length--;

    return (eval(ary.join('+')) / ary.length).toFixed(2);
}
console.log(avgFn(9.8, 9.6, 9.8, 8.8, 8.9));



// // 思路来自于:数组中实现克隆
// // ary.slice()
// 为什么ary.slice()能实现数组克隆,因为slice方法里面做了一些操作,下面来重写数组原型上的slice实现克隆
// Array.prototype.slice = function slice () { //实现数组克隆不需要传参
//     //=>this:需要克隆的数组ary
       //实现克隆ary的思路:创建一个空数组,循环原有数组中的每一项,把每一项分别放到空数组当中
//     // [native code] 浏览器slice的内置代码
//     var ary = [];
//     for (var i = 0; i < this.length; i++) {
//         ary[ary.length] = this[i];
//     }

//     //  // 把arguments克隆成一个数组
//     //  var ary = [];
//     //  for (var i = 0; i < arguments.length; i++) {
//     //      ary[ary.length] = arguments[i];
//     //  }

    // [native code] 浏览器slice的内置代码和我们写的把arguments克隆成一个数组对比发现区别:就是this的问题,我们用
    // 的是arguments,内置代码用的是this,所以如果我们可以把slice执行,并且让方法中的this执行arguments,相当于把
    //类数组转化为数组,这些代码就可以不用写了,直接用内置代码就可以了,那怎么样内置的slice方法执行呢?
    

//     //=>如果我们可以把slice执行,并且让方法中的this执行arguments,相当于把类数组转化为数组
//     //=>1、以下三种方法法都可以让内置的slice方法执行  
//     // Array.prototype.slice()
//     // [].__proto__.slice();
//     // [].slice();
//     //2、让slice中的this变为arguments
//     Array.prototype.slice.call(arguments);//让方法执行并且让方法中的this改变用call和apply  让当前slice这个方法执行,并让slice方法中的this变成arguments

//     return ary;
// };
// ary.slice();

把类数组转换成数组,基于面向对象思想,原型方法借用改变this...一系列原理来解决

由于:类数组不仅可以借用slice,Array原型上的大部分方法都可以借来使用(原理都是this改变),所有方法都借用数组再次优化

function avgFn() {
    // arguments借用数组原型上的sort方法,实现把arguments从小到大排序
    [].sort.call(arguments, function (a, b) {
        return a - b;
    });
    // arguments借用数组原型上的shift、pop方法,掐头去尾
    [].shift.call(arguments);
    [].pop.call(arguments);
    // arguments借用数组原型上的join方法,按'+'分隔符转换成字符串,再eval()字符串转换成js表达式执行
    return (eval([].join.call(arguments, '+')) / arguments.length).toFixed(2);
}
console.log(avgFn(9.8, 9.6, 9.8, 8.8, 8.9));

8、浏览器异常信息捕获

在借用数组原型上的方法把类数组转换为数组的时候

在IE低版本浏览器中(IE6~8)arguments可以转换,但是元素集合或者节点集合这些类数组是无法借用slice转换的, 报错:Array.prototype.slice:'this'不是Javascript对象

function fn () {
    var ary = [].slice.call(arguments);
    console.log(ary);
}
fn(10, 20, 30);
var oList = document.getElementsByTagName('*');
console.log([].slice.call(oList));
// =>在借用数组原型上的方法把类数组转换为数组的时候
// 在IE低版本浏览器中(IE6~8)arguments可以转换,但是元素集合或者节点集合这些类数组是无法借用slice转换的,
// 报错:Array.prototype.slice:'this'不是Javascript对象


// 解决方案;
var oList = document.getElementsByTagName('*');
// 兼容
var ary = Array.prototype.slice.call(oList);
// 不兼容
var ary = [];
for (var i = 0; i < oList.length; i++) {
    ary[oList.length] = ary[i];
}
console.log(ary);

JS中的异常信息捕获 try catch finally

1、捕获到执行的错误(异常)信息

2、防止浏览器抛出异常错误导致下面都不再执行的问题

console.log(a);//=>Uncaught ReferenceError: a is not defined  JS中当前行代码报错了,下面的代码将不再执行
var b = 13;
console.log(b);

try catch finally 语法规则:

try {
    // 需要执行的js代码
} catch (e) {
    // =>如果try中的js代码执行发生了异常,就会执行catch中的代码
    // 1)必须要有一个形参变量(我们一般命名为e[error])
    // 2)e中存储了当前代码执行的异常信息

} finally {
    // =>不管try中代码是否出现异常,最后都会执行finally中的操作
    // 1)真实项目中很少使用finally
    // 2)try catch必须成对使用
}

小列子:

try {
    console.log(a);
} catch (e) {
    // console.log(e.message);//=>a is not defined 异常信息 
    console.log('您的人品欠费,请先充值');
}
var b = 13;
console.log(b);

我们需要捕获到异常信息(try catch),但是上面代码如果报错,我们希望下面的代码不执行(在浏览器当中抛出异常信息会导致下面代码不执行,此时我们需要手动抛出异常信息,来阻止下面的代码执行)

throw new Error('需要抛出的异常信息~~');

JS中手动抛出异常信息的方法 new Error()是创建Error这个内置类的一个实例

try {
    console.log(a);
    var b = 13;
    console.log(b);
} catch (e) {
    console.dir(e);//=>把错误信息发送给服务器进行统计
}

Error:错误

  • SyntaxError:语法错误

  • ReferenceError:引用错误(一般都是未定义或者不存在)

  • TypeError:类型错误

  • RangeError:范围错误

throw new Error();

throw new ReferenceError();

9、将类数组转换为数组的兼容处理

function fn () {
    var ary = [].slice.call(arguments);
    console.log(ary);
}
fn(10, 20, 30);
var oList = document.getElementsByTagName('*');
console.log([].slice.call(oList));
// =>在借用数组原型上的方法把类数组转换为数组的时候
// 在IE低版本浏览器中(IE6~8)arguments可以转换,但是元素集合或者节点集合这些类数组是无法借用slice转换的,
// 报错:Array.prototype.slice:'this'不是Javascript对象


// 解决方案;
var oList = document.getElementsByTagName('*');
// 兼容
var ary = Array.prototype.slice.call(oList);
// 不兼容
var ary = [];
for (var i = 0; i < oList.length; i++) {
    ary[ary.length] = oList[i];
}
console.log(ary);

写个公共的兼容处理方法:

利用 try catch 是否能检测到报错的这种机制做兼容处理

// utils:属于一个项目的公共方法库,在这里存放了常用到的一些方法
// 利用try catch异常信息捕获机制做兼容处理,不报错执行try里面的代码块,报错执行catch里面的代码块
var utils = (function () {

    // =>把类数组转换为数组(兼容所有浏览器的)
    var toArray = function toArray(classAry) {
        var ary = [];
        try {
            ary = Array.prototype.slice.call(classAry);
        } catch (e) {
            for (var i = 0; i < classAry.length; i++) {
                ary[ary.length] = classAry[i];
            }
        }
        return ary;
    }

    return {
        // 把私有函数toArray作为属性值赋值给当前对象的一个属性名toArray,把这个对象返回给全局变量utils
        // 外界通过utils.toArray()执行。
        toArray: toArray
    }

})();


// //高级单例模式-------------------------------------------------------------------------------------------
// var utils = (function () {
     
//     return {

//     }

// })();

10、前后端数据交互模型

11、初步接触ajax

ajax获取数据四步操作

ajax.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ajax</title>
</head>
<body>
<script src="ajax.js"></script>
</body>
</html>

ajax.js

//XML:格式文档 Http:传输协议 Request:请求 => 通过http协议从服务器端请求到xml格式数据
// =>创建一个ajax对象
var xhr = new XMLHttpRequest();
// =>打开请求的url,需要传三个参数
// [HTTP METHOD]:HTTP请求方式 GET POST PUT DELETE HEAD...
// [URL]:请求数据的地址
// [ASYNC]:设置同步或者异步请求,默认是true异步,我们暂时写false同步
xhr.open('GET', 'data.txt', false);
// =>监听状态改变,完成数据的获取
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
        var result = xhr.responseText;
        console.log(result);
    }
};
//=>发送AJXA请求
xhr.send(null);

data.txt

hello world

12、JS中的JSON操作

一般从服务器端获取到的数据是json格式的

JSON不是数据类型,它仅仅是一种数据格式

JSON在真实项目中经常被使用,客户端和服务器端的数据传输,目前大部分项目都是依托JSON格式的数据来传输的

// =>JSON只是一种数据格式:除了格式不一样,其它和正常的数据类型操作都一样
var obj = {name:"珠峰培训",age:8};//=>普通格式的对象
var opp = {"name":"珠峰培训","age":8};//=>JSON格式的对象:把属性名用双引号(只能是双引号不能是单引号)包起来
// 的格式叫做JSON格式的数据


// =>项目中客户端和服务器传输的数据一般都是JSON格式的字符串
 var str = '[{"name":"张三","age":28},{"name":"李四","age":25}]';//=>JSON格式的字符串


// =>JSON格式的对象和字符串之间的相互转换
console.log(window.JSON);//=>它是一个对象,里面提供了两个方法:parse、stringify

1、JSON.parse:把字符串(必须是JSON格式的字符串)装换为JSON格式的对象,转换成JSON格式的对象方便获取数据,如果转换的字符串不是 JSON格式的,转换的过程中会报错

var str = '[{"name":"张三","age":28},{"name":"李四","age":25}]';
var jsonAry = JSON.parse(str);
console.log(jsonAry[0].age);//获取数据


// =>这个方法在IE6~7中不兼容:不兼容的原因是因为,在IE6~7中,window对象中没有JSON这个对象:那怎么办呢?
// =>在IE6~7中,我们可以使用eval来代替JSON.parse这个方法,实现把字符串转换为对象


var str = '[{"name":"张三","age":28},{"name":"李四","age":25}]';
var jsonAry = eval('('+ str +')');//=>使用EVAL转换的时候,需要手动的给字符串外层加一个小括号,目的是
// 为了防止有些数据不加括号报错
console.log(jsonAry);

JSON格式的字符串转化成json格式的对象有两种方式:

标准浏览器下用JSON.parse();

IE6~7中用eval('('+ str +')');需要手动加小括号

utils里面封装个兼容方法:

var utils = (function () {
    // =>把类数组转换为数组(兼容所有浏览器的)
    var toArray = function toArray(classAry) {
        var ary = [];
        try {
            ary = Array.prototype.slice.call(classAry);
        } catch (e) {
            for (var i = 0; i < classAry.length; i++) {
                ary[ary.length] = classAry[i];
            }
        }
        return ary;
    }

    // =>把JSON格式的字符串转换为JSON格式的对象(跟jQuery原理一样)
    var toJSON = function toJSON(str) {
        //JSON.parse()不兼容的原因是因为window下没有JSON这个属性
        return "JSON" in window ? JSON.parse(str) : eval('('+ str +')');
    }

    return {
        toArray: toArray,
        toJSON:toJSON
    }

})();

2、JSON.stringify:把对象转化为JSON格式的字符串

var ary = [{name:'珠峰培训',age:8}];
console.log(JSON.stringify(ary));//=>[{"name":"珠峰培训","age":8}]

// 没有直接的办法替换JSON.stringify()这个方法,不过有js插件可以实现JSON.stringify()深度克隆深度解析,
// 把对象转化为JSON格式的字符串(这里涉及到深拷贝)

13、专题总结-JS中的this指向问题

this:函数执行的主体,谁把函数执行的,谁就是执行的主体(和函数在哪执行的,以及在哪定义的没有直接的关系)

js的非严格模式下

1、给元素的某一个事件绑定方法,当事件触发,方法执行的时候,绑定的这个方法中的this一般是当前操作的这个DOM元素

oBox.onclick = function(){
    // 当绑定的方法执行,方法中的this:oBox
    // 如何让绑定的方法执行:
    // 1、手动点击oBox,经由浏览器触发点击事件
    // 2、oBox.onclick();
    // ...
}

在ie6~8下,如果我们使用的是DOM2事件绑定,方法执行的时候,里面的this不是当前元素而是window

oBox.attachEvent('onclick',function(){
    // this:window
})

2、自执行函数执行,方法中的this一般都是window

var obj = {
    fn:(function(){
        // this:window //当给fn赋值时,属性名属性值赋值还没完成,自执行函数执行没有赋值完成是不会把这个对象的堆内存地址给obj
        // //obj和这个空间还没有关系,而且不管在哪执行,这个fn:也是自执行函数,自执行函数仔细里面的this都是window
        return function(){}
    })()
}
//--------------------------------------------------------------------------------------------------------
~function(){
    // this:window
}();

3、方法执行的时候,看方法名之前是否有“点”,有的话,“点”前面是谁this就是谁,没有的话,this是window

function fn(){
    console.log(this);
}
var obj={fn:fn};
fn();//=>this:window
obj.fn();//=>this.obj
//------------------------------------------------------------
//=>数组通过原型链查找机制,把Array.prototype上的slice方法找到,并且让
// 方法执行,执行过程中实现数组的按索引查找操作
[].slice(); // this:[]
[].__proto__.slice();// this:[].__proto__ 
Array.prototype.slice();// this:Array.prototype 
// ( 一个实例的__proto__ 和类的原型是同一个堆内存,所以 [].__proto__  = Array.prototype )

4、在构造函数执行的时候,构造函数体中的this都是当前类的实例

function Fn(){
    // this:当前Fn的实例(当前案列中指的是f);而this.xxx=xxx都是给当前
    // 实例设置的私有属性
    this.xxx=xxx;
}
var f=new Fn();

//这是因为 new Fn的时候,浏览器自己的一些操作,除了形成私有作用域,形参赋值,变量提升
// 接下来首先创建一个对象类型值,让this指向当前对象类型值,这个对象类型值就是我们的实例,
// 即使不写return也会把这个实例返回

5、使用Function.prototype上提供的call、apply、bind实现this改变(强制改变this指向)

call基础语法的应用:

var obj={name:'珠峰培训'};
function fn(num1,num2){
    this.total=num1+num2;
}

fn(10,20);//this:window 给window下加了一个total属性等于30

obj.fn(10,20);//obj中并没有fn这个属性,属性值是undefined,
// 它不能作为函数执行,所以会报错:TypeError(undefined id not a function...)

fn.call(obj,10,20);//首先让fn中的this指向传递的第一个参数值obj,然后执行fn这个函数:
// 此时fn中的this=>obj  num1=>10  num2=>20

fn.call(10,20);// fn中的this=>10  num1=>20  num2=>undefined

fn.call();// fn中的this=>window  num1=num2=undefined <=> fn()

fn.call(null);
fn.call(undefined);//第一个参数不管写的是null还是undefined都代表没有指向的this,所以
// 函数中的this依然是window


//---------------------------------------------------------------------------------------------


apply基础语法的应用:

apply的语法和作用跟call基本完全类似,只有一个区别

var obj={name:'珠峰培训'};
function fn(num1,num2){
    this.total=num1+num2;
}

fn(obj,10,20);
// <=>
fn.apply(obj,[10,20]);//apply方法调用的时候,第一个参数是this指向,第二个参数是一个数组,数组
// 中包含了所有需要给函数传递的实参(语法要求是写成一个数组,但是和call一样也是一项项的给形参赋值的)

当之前总结的 1~5 所有this情况遇到call/apply的时候,都以call/apply指向的this为主,比如:

~function(){
    // this:obj
}.call(obj);
//------------------------
Array.prototype.slice.call(arguments);//slice中的this:arguments

bind基础语法的应用:

var obj={name:'珠峰培训'};
function fn(num1,num2){
    this.total=num1+num2;
}

fn.call(obj,10,20);//改变fn中的this,而且把fn立即执行了

fn.bind(obj,10,20);//虽然改变了fn中的this,但是并没有把fn执行,它属于预先处理this和实参,
//不会立即执行,只有达到某个特点条件,才会被触发执行的(ie6~8不兼容)

项目中使用bind的意义:

var obj={name:'珠峰培训'};
function fn(num1,num2){
    this.total=num1+num2;
}

// 需求:一秒后执行fn(定时器驱动),执行fn的时候,让fn中的this指obj,并且给fn传递两个实参10,20

setTimeout(fn,1000);//1s后确实执行了fn,但是此时fn中的this是window不是obj,而且没有传递10,20

setTimeout(fn,call(obj,10,20),1000);//虽然实现了this的改变和参数的传递,但是它是设置定时器得时候
// 就把fn执行了(因为call立马就执行了),而不是等到1000ms后,1000ms后执行的是fn执行的返回结果

setTimeout(function(){
    // 1000ms后执行的是匿名函数:我们可以在匿名函数里面把fn执行,并且实现需求
    fn.call(obj,10,20);
},1000);

setTimeout(fn,bind(obj,10,20),1000);//或者使用bind预处理(ie6~8不兼容)

6、在ES6中,新增了箭头函数,箭头函数中没有执行主体(没有this),箭头函数中的this会继承它宿主环境中的this

var obj = {
    fn:function(){
        // this:obj
        setTimeout(function(){
            // this:window 不管在哪执行,定时器中的this是window
        },1000);

        // 想让定时器函数中的this也是obj

        // 1、bind
        setTimeout(function(){
            // this:obj (通过bind方法把定时器中的this预先处理成上一级作用域中的this)
        }.bind(this),1000);

        // 2、但是bind有兼容性问题
        var _this=this;//提前把外面的this存放到变量_this中
        setTimeout(function(){
            // _this:obj
            _this.name='xxx';//(不是形参也不是私有变量,将会往上一级作用域查找)
        },1000);

        // 3、es6中的箭头函数
        setTimeout(()=>{
            // _this:obj  箭头函数中的this继承宿主环境(1s后当前函数在obj.fn这个方法中执行,
            // 他的宿主环境就是obj.fn,obj.fn中的this就是obj)中的this 
        },1000)
    }
};
obj.fn();

JS严格模式下的this情况

"use strict";//代码第一行加上这句话,就开启了JS中的严格模式

//---------------------------------------------------------------------------------------------------------------

function fn(){
    "use strict";//只在当前私有作用域中使用严格模式(也需要处于当前作用域第一行)
}

非严格模式下不明确执行主体,浏览器认为执行主体默认是window(this一般都是window);但是在严格模式下,执行主体不明确,this是undefined

"use strict"
~function(){
    // this:undefined
}();


fn();//this:undefined
window.fn();//this:window

fn.call();//this:undefined
fn.call(window);//this:window

fn.call(null/undefined);//this:null/undefined   非严格模式this:window