第一篇分为6个部分javascript
this的指向问php
闭包java
call、apply、bind详解es6
prototype、__proto__和constructor的关系面试
高阶函数chrome
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)); };}复制代码
用法
提升适用性
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,和实例化子类
这一篇内容比较多,谢谢你们可以看完,但愿可以给你们可以喜欢。
最后,你们关注个人公众号哦。
欢迎你们转发,尽力一周一篇高质量原创文章。