我就不信这些js知识你都知道!<一>

380 阅读12分钟



第一篇分为6个部分

  1. this的指向问

  2. 闭包

  3. call、apply、bind详解

  4. prototype、__proto__和constructor的关系

  5. 高阶函数

  6. ES6中类和继承与ES5的区别



一.This的指向问题


this的判断只要记住一点,就是在执行的时候动态绑定的(es6的箭头函数除外)


01. new出来的实例,this指向返回的实例。

02. 有对象调用,this指向这个对象。

03. call,apply 绑定,this指向绑定的对象。

04. 匿名函数或者全局调用函数,this指向window。

05. 事件绑定函数,this指向绑定的对象(event.currentTarget)。

06. ES6箭头函数里this的指向就是上下文里对象this指向,偶尔没有上下文对象,this就指向window。


案例详解:


var a = 1;var o = {  a: 10,  b: {    a: 12,    fn: function () {      console.log(this.a);    }  },  globalFun: (function () {    // "use strict"    // 如果这里把上句注释打开开启严格模式就会报错    return this.a;  })(),  arrowFun: function () {    //箭头函数    setTimeout(() => {      console.log(this); // this ==> o    });    //普通函数    setTimeout(function () {      console.log(this); // this ==> window    });  }}o.b.fn(); //this=>b,输出12console.log(o.globalFun); //this=>window,输出1o.b.fn.call(o); //this=>o,输出10var f = new o.b.fn(); //this=>f,输出undefinedo.arrowFun();​var testDiv = document.createElement("div");testDiv.innerHTML = "测试";testDiv.a = "asd";document.body.appendChild(testDiv);testDiv.addEventListener("click", o.b.fn) //this=>testDiv,输出asd


知识点补充:

1.在严格版中的默认的this不再是window,而是undefined

"use strict"function A() {  this.a = 10;}A();console.log(a); //报错


2. 当new一个有返回值的function时,this的指向问题


当方法返回的值是引用类型时,this指向此对象

原因在于:

被调用的函数没有显式的 return 表达式(仅限于返回对象),

则隐式的会返回 this 对象 - 也就是新创建的对象



function A() {  this.n = 10;  return {    n: 11  };}var a = new A();console.log(a.n); //11


3.new干了什么,new是一种语法糖



01.创建临时对象

02.绑定原型

03.执行构造函数

04.原函数返回非引用类型时,return临时对象(this)。


实现一下:


方案1:

function A() {  this.n = 10;}var _new = function (fn) {  var temp = Object.create(fn.prototype);  var rv = fn.apply(temp, [].slice.call(arguments, 1));  return (typeof rv === "object" && rv) || temp;}​var a = _new(A);

方案2:

var _new = function (fn) {  var temp = {};  temp.__proto__ = fn.prototype;  var rv = fn.apply(temp, [].slice.call(arguments, 1));  return (typeof rv === "object" && rv) || temp;}




二.闭包


优点:

01.缓存

02.面向对象中的对象

03.实现封装,防止变量跑到外层作用域中,发生命名冲突

04.访问到非自身的作用于内的变量


缺点:

01.常驻内存,会增大内存使用量,使用不当很容易造成内存泄露

02.使用闭包时,会涉及到跨作用域访问,每次访问都会导致性能损失


实例1(缓存)


var db = (function () {  var data = {};  return function (key, val) {    if (val === undefined) {      return data[key]    } // get           else {      return data[key] = val    } // set      }})();​db('x'); // 返回 undefineddb('x', 1); // 设置data['x']为1db('x'); // 返回 1


实例2(封装和面向对象,模块)


var person = function () {  var name = "default";  return {    getName: function () {      return name;    },    setName: function (newName) {      name = newName;    }  }}();console.log(person.name); //直接访问,结果为undefined    console.log​(person.getName());person.setName("abruzzi");console.log​(person.getName());


实例2.1(扩展上一个例子,模块管理器)


var MyModules = (function Manager() {  var modules = {};​  function define(name, deps, impl) {    for (var i = 0; i < deps.length; i++) {      deps[i] = modules[deps[i]];    }    modules[name] = impl.apply(impl, deps);  }​  function get(name) {    return modules[name];  }  return {    define: define,    get: get  };})();​​MyModules.define("bar", [], function () {  function hello(who) {    return "Let me introduce: " + who;  }  return {    hello: hello  };});​MyModules.define("foo", ["bar"], function (bar) {  var hungry = "hippo";​  function awesome() {    console.log(bar.hello(hungry).toUpperCase());  }  return {    awesome: awesome  };});​var bar = MyModules.get("bar");var foo = MyModules.get("foo");console.log(bar.hello("hippo")); // Let me introduce: hippo foo.awesome(); // LET ME INTRODUCE: HIPPO​


实例3(回调函数)


在定时器、事件监听器、 Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使 用了回调函数,就是闭包


for (var i = 1; i <= 5; i++) {  (function (j) {    setTimeout(function timer() {      console.log(j);    }, j * 1000);  })(i);}



三.Call、apply、bind


apply和call,bind都是为了改变某个函数运行时的上下文而存在的(就是为了改变函数内部this的指向);


apply和call两者的区别:


如果使用apply或call方法,那么this指向他们的第一个参数,apply的第二个参数是一个参数数组,call的第二个及其以后的参数都是数组里面的元素


var numbers = [5, 458, 120, -215];var maxNum = Math.max.apply(Math, numbers),  var maxNum1 = Math.max.call(Math, 5, 458, 120, -215);Math.max(5, 458, 120, -215)


bind和apply、call两者的区别:


bind不会立即调用(新的方法),其他两个会立即调用


var obj = {  b: 10};var a = (function () {  console.log(this.b)}).bind(obj);a();


进阶知识:


1.call,apply不仅可以改变函数的上下文环境,还可以让绑定的对象拥有目标this上的属性


function A() {  this.a = 10;  this.test = function () {}}var obj = {};A.call(obj);console.log(obj);// obj 此时拥有A中的属性

2.call,apply实现bind功能(IE8下)


Function.prototype.bind = function (oThis) {  var aArgs = Array.prototype.slice.call(arguments, 1),    fToBind = this,    fNOP = function () {},    fBound = function () {      return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));    };  if (this.prototype) {    fNOP.prototype = this.prototype;  }  fBound.prototype = new fNOP();  return fBound;};



四.prototype、__proto__和constructor的关系


先了解什么是本地对象和内置对象


本地:  Object、Function、Array、String、Boolean、
Number、Date、RegExp、Error、EvalErrorRangeError、
ReferenceError、SyntaxError、TypeError、URIError


内置:  Global 、Math 、JSON 、arguments


概念:


prototype是函数的属性,这个属性是一个指针,指向一个对象,它代表了对象的原型(Function.prototype函数对象是个例外,没有prototype属性),


用处:扩展原型功能,new和继承时使用


__proto__是一个对象拥有的内置属性(prototype是函数的内置属性,__proto__是对象的内置属性),用chrome和FF都可以访问到对象的__proto__属性,IE10-没有,规范使用Object.

getPrototypeOf()获取原型,


用处:内部查找、维持原型链


constructor 实例自动拥有,指向其构造器,


用处:保持对其构造函数的引用



01.所有构造器/函数的__proto__都指向Function.prototype,它是一个空函数(Empty function)


console.log(Number.__proto__ === Function.prototype) // true  console.log(Boolean.__proto__ === Function.prototype) // true  console.log(String.__proto__ === Function.prototype) // true  console.log(Object.__proto__ === Function.prototype) // true  console.log(Function.__proto__ === Function.prototype) // true   console.log(Array.__proto__ === Function.prototype) // true  console.log(RegExp.__proto__ === Function.prototype) // true  console.log(Error.__proto__ === Function.prototype) // true  console.log(Date.__proto__ === Function.prototype) // true  


02.所有实例的__proto__都指向其构造函数的prototype


function A() {}var a = new A();console.log(a.__proto__ === A.prototype) // truevar a = 1;console.log(a.__proto__ === Number.prototype) // true


03.所有的构造器/函数的constructor都指向Function,函数都是Function的实例.实例的constructor指向该构造器函数,该构造函数的prototype的constructor属性, 又等于该构造器函数.


function A() {}var a = new A();console.log(A.constructor === Function); // trueconsole.log(a.constructor === A.prototype.constructor) // trueconsole.log(a.constructor === A) // true//a.constructor === A.prototype.constructor === A



几种特殊的情况:


1.Math,JSON等内置对象的构造函数等于Object

console.log(Math.constructor === Object,JSON.constructor === Object)


2.Function.constructor等于其自身Function

console.log(Function.constructor === Function)


3.Function.prototype的类型的”function”并且与其__proto__的值恒等

console.log(typeof Function.prototype === "function");
console.log(Function.prototype === Function.__proto__);


4.Function.prototype没有prototype属性

console.log(Function.prototype.prototype === undefined);


5.Function.prototype.__proto__指向Object.prototype

console.log(Function.prototype.__proto__ === Object.prototype);


6. Object.prototype.__proto__为null,原型链终结于此

console.log(Object.prototype.__proto__ === null)


7.只有本地对象和自定义的函数才有prototype属性

console.log(Array.prototype.push.prototype === undefined);


实例讲解(继承)


es5:

function Super() {  this.type = "super";}Super.prototype.say = function () {  console.log(this.type)};​function Sub() {  Super.call(this);  this.type = "sub";}Sub.prototype = new Super();Sub.prototype.constructor = Sub;var sub = new Sub();sub.say();


下面我画了一张原型的图希望可以帮助大家理解其中的奥妙



五.高阶函数


高阶函数就是可以把函数作为参数,或者是将函数作为返回值的函数


1) 回调函数

例如:ES5新增数组的forEach,map,filter,every,some,reduce

用reduce方法举例(reduce()可以实现一个累加器的功能,将数组的每个值(从左到右)将其降低到一个值):

//实现数组中相同的值出现的次数:
var arr = ["apple", "orange", "apple", "orange", "pear", "orange"];​function getWordCnt() {  return arr.reduce(function (prev, next) {    prev[next] = (prev[next] + 1) || 1;    return prev;  }, {});}console.log(getWordCnt())


2) AOP

AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块.


Function.prototype.before = function (beforefn) {  var __self = this;  return function () {    beforefn.apply(this, arguments);    return __self.apply(this, arguments);  }};​Function.prototype.after = function (afterfn) {  var __self = this;  return function () {    var ret = __self.apply(this, arguments);    afterfn.apply(this, arguments);    return ret;  }};


用法:

在一个方法之后调用另一个方法:


var a = function () {  console.log("2")};a = a.before(function () {  console.log("1")}).after(function () {  console.log("3")});a();


防止window.onload被二次覆盖:


window.onload = function () {  console.log(1)};window.onload = (window.onload || function () {}).after(function () {  console.log(2)});


3) currying

柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术


function currying(fn) {  var slice = Array.prototype.slice,    __args = slice.call(arguments, 1);  return function () {    var __inargs = slice.call(arguments);    return fn.apply(this, __args.concat(__inargs));  };}


用法

  1. 提高适用性


function Ajax() {  this.xhr = new XMLHttpRequest();}Ajax.prototype.open = function (type, url, data, callback) {  this.onload = function () {    callback(this.xhr.responseText, this.xhr.status, this.xhr);  }  this.xhr.open(type, url, data.async);  this.xhr.send(data.paras);}'get post'.split(' ').forEach(function (mt) {  Ajax.prototype[mt] = currying(Ajax.prototype.open, mt);});var xhr = new Ajax();xhr.get('/a.php', {}, function (datas) {});var xhr1 = new Ajax();xhr1.post('/b.php', {}, function (datas) {});


2.延迟执行


var add = function () {  var _this = this,    _args = arguments  return function () {    if (!arguments.length) {      var sum = 0;      for (var i = 0, c; c = _args[i++];) sum += c      return sum    } else {      Array.prototype.push.apply(_args, arguments);      return arguments.callee    }  }}add(1)(2)(3)(4)(); //10


3.固定易变因素

bind函数用以固定this这个易变对象(想不到吧bind也是curring化的一种是应用)


var obj = {  b: 10};var a = (function () {  console.log(this.b)}).bind(obj);a();



4) unCurrying(反柯里化)


在JavaScript中,当我们调用对象的某个方法时,其实不用去关心该对象原本是否被设计为拥有这个方法,这是动态类型语言的特点,也是常说的鸭子类型思想。

同理,一个对象也未必只能使用它自身的方法,那么有什么办法可以让对象去借用一个原本不属于它的方法呢?

答案对于我们来说很简单,call和apply都可以完成这个需求,因为用call和apply可以把任意对象当作this传入某个方法,这样一来,方法中用到this的地方就不再局限于原来规定的对象,而是加以泛化并得到更广的适用性。

而uncurrying的目的是将泛化this的过程提取出来,将fn.call或者fn.apply抽象成通用的函数。



代码实现:


Function.prototype.unCurrying = function () {  var that = this;  return function () {    return Function.prototype.call.apply(that, arguments);  }}


应用1:


var push = Array.prototype.push.unCurrying(),  obj = {};push(obj, 'first', 'two');console.log(obj); //{0:’first’,1:”two”,length:2}



5) 函数节流


当一个函数被频繁调用时,如果会造成很大的性能问题的时候,这个时候可以考虑函数节流,降低函数被调用的频率


var throttle = function (fn, interval) {  var __self = fn,    timer, firstTime = true;  return function () {    var args = arguments,      __me = this;    if (firstTime) { // 如果是第一次调用,不需延迟执行                      __self.apply(__me, args);      return firstTime = false;    }    if (timer) { // 如果定时器还在,说明前一次延迟执行还没有完成       return false;    }    timer = setTimeout(function () { // 延迟一段时间执行                         clearTimeout(timer);      timer = null;      __self.apply(__me, args);    }, interval || 500);  };};window.onresize = throttle(function () {  console.log(1);}, 500);



6) 惰性加载函数


在Web开发中,因为浏览器之间的实现差异,一些嗅探工作总是不可避免。比如我们需要一个在各个浏览器中能够通用的事件绑定函数addEvent,常见的写法如下:



var addEvent = function (elem, type, handler) {  if (window.addEventListener) {    return elem.addEventListener(type, handler, false);  }  if (window.attachEvent) {    return elem.attachEvent('on' + type, handler);  }};


缺点:当它每次被调用的时候都会执行里面的if条件分支,虽然执行这些if分支的开销不算大,但也许有一些方法可以让程序避免这些重复的执行过程。


var addEvent = function (elem, type, handler) {  if (window.addEventListener) {    addEvent = function (elem, type, handler) {      elem.addEventListener(type, handler, false);    }  } else if (window.attachEvent) {    addEvent = function (elem, type, handler) {      elem.attachEvent('on' + type, handler);    }  }  addEvent(elem, type, handler);};

此时addEvent依然被声明为一个普通函数,在函数里依然有一些分支判断。但是在第一次进入条件分支之后,在函数内部会重写这个函数,重写之后的函数就是我们期望的addEvent函数,在下一次进入addEvent函数的时候,addEvent函数里不再存在条件分支语句。


Vue的源码中(其他优秀开源库)也大量运用上述技巧。



六.ES6的类和继承与ES5的区别



ES6的类


1. 关键字class

class Parent {  constructor() {    ​}}​var p = new Parent();


2. Babel => ES5

"use strict";​function _classCallCheck(instance, Constructor) {  if (!(instance instanceof Constructor)) {    throw new TypeError("Cannot call a class as a function")  }}var Parent = function Parent() {  _classCallCheck(this, Parent)};var p = new Parent();


ES5继承


function Supertype(name) {  this.name = name;  this.colors = ["red", "green", "blue"];}Supertype.prototype.sayName = function () {  console.log(this.name);};

function Subtype(name, age) { //继承属性      Supertype.call(this, name);  this.age = age;} //继承方法Subtype.prototype = new Supertype();Subtype.prototype.constructor = Subtype;Subtype.prototype.sayAge = function () {  console.log(this.age);};

var instance1 = new Subtype('Annika', 21);instance1.colors.push("black"); console.log(instance1.colors); //["red", "green", "blue", "black"]instance1.sayName(); //Annikainstance1.sayAge();  //21

​var instance2 = new Subtype('Anna',22);console.log(instance2.colors);//["red", "green", "blue"]instance2.sayName();   //Annainstance2.sayAge();    //22


ES6的继承


1.关键字extends

class Parent {  constructor() {    ​}  p() {}}​class Son extends Parent {  constructor() {    super();  }  s() {}}​var p = new Son();


2. Babel => ES5


"use strict";var _get = function get(_x, _x2, _x3) {  var _again = true;  _function: while (_again) {    var object = _x,      property = _x2,      receiver = _x3;    _again = false;    if (object === null) {      object = Function.prototype    }    var desc = Object.getOwnPropertyDescriptor(object, property);    if (desc === undefined) {      var parent = Object.getPrototypeOf(object);      if (parent === null) {        return undefined      } else {        _x = parent;        _x2 = property;        _x3 = receiver;        _again = true;        desc = parent = undefined;        continue _function      }    } else {      if ("value" in desc) {        return desc.value      } else {        var getter = desc.get;        if (getter === undefined) {          return undefined        }        return getter.call(receiver)      }    }  }};var _createClass = (function () {  function defineProperties(target, props) {    for (var i = 0; i < props.length; i++) {      var descriptor = props[i];      descriptor.enumerable = descriptor.enumerable || false;      descriptor.configurable = true;      if ("value" in descriptor) {        descriptor.writable = true      }      Object.defineProperty(target, descriptor.key, descriptor)    }  }  return function (Constructor, protoProps, staticProps) {    if (protoProps) {      defineProperties(Constructor.prototype, protoProps)    }    if (staticProps) {      defineProperties(Constructor, staticProps)    }    return Constructor  }})();​function _inherits(subClass, superClass) {  if (typeof superClass !== "function" && superClass !== null) {    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass)  }  subClass.prototype = Object.create(superClass && superClass.prototype, {    constructor: {      value: subClass,      enumerable: false,      writable: true,      configurable: true    }  });  if (superClass) {    Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass  }}​function _classCallCheck(instance, Constructor) {  if (!(instance instanceof Constructor)) {    throw new TypeError("Cannot call a class as a function")  }}var Parent = (function () {  function Parent() {    _classCallCheck(this, Parent)  }  _createClass(Parent, [{    key: "p",    value: function p() {}  }]);  return Parent})();var Son = (function (_Parent) {  _inherits(Son, _Parent);​  function Son() {    _classCallCheck(this, Son);    _get(Object.getPrototypeOf(Son.prototype), "constructor", this).call(this)  }  _createClass(Son, [{    key: "s",    value: function s() {}  }]);  return Son})(Parent);var p = new Son();


ES6的继承区别和注意事项(注意了很多大厂面试都会问到的)


1.区别

ES6中 子类的__proto__ === 父类

ES5中 子类的__proto__ === Function.prototype

ES5中 子类不会继承父类的静态方法,ES6会



2. 注意事项

ES6 子类使用继承后,必须现在constructor调用super,才能使用this,和实例化子类


这一篇内容比较多,谢谢大家能够看完,希望能够给大家能够喜欢。



最后,大家关注我的公众号哦。

欢迎大家转发,尽力一周一篇高质量原创文章。