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



第一篇分为6个部分javascript

  1. this的指向问php

  2. 闭包java

  3. call、apply、bind详解es6

  4. prototype、__proto__和constructor的关系面试

  5. 高阶函数chrome

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



一.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,和实例化子类


这一篇内容比较多,谢谢你们可以看完,但愿可以给你们可以喜欢。



最后,你们关注个人公众号哦。

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


相关文章
相关标签/搜索