
第一篇分为6个部分
this的指向问
闭包
call、apply、bind详解
prototype、__proto__和constructor的关系
高阶函数
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); //113.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) // true03.所有的构造器/函数的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)(); //103.固定易变因素
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(); //22ES6的继承
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,和实例化子类
这一篇内容比较多,谢谢大家能够看完,希望能够给大家能够喜欢。
最后,大家关注我的公众号哦。
欢迎大家转发,尽力一周一篇高质量原创文章。
