今天的知识怎么说呢,说多也多,说少也少,有些凌乱。主要的知识点是闭包的三个应用、严格模式的相关知识点、ES5数组新方法、创建对象的方式、继承的方式。下面对各个知识点做详细介绍。
一、闭包的三个应用
1. 参数复用
小案例:音乐模板的js代码
onload = function() { //入口函数 function bindNav(fn) { var lis = document.querySelectorAll("#nav li"); for(var i = 0, l = lis.length; i < l; i++){ lis[i].addEventListener("click", function() { switch(this.getAttribute('data-name')){ case "find": fn("这是发现音乐。"); break; case "mine": fn("这是我的音乐。"); break; case "friend": fn("这是我的朋友。"); break; } }); } } function curryingBindContent(content) { return function(text) { document.querySelector(content).innerHTML = text; }; } bindNav(curryingBindContent("#content")); };
2. 延迟计算/执行
小案例:统计某地一周售楼的数量
/** * [curryingSales 创建可以获取和保存售楼数闭包函数] * @param {Function} fn [fn函数决定是获取周总数] * @return {[type]} [闭包函数] */ function curryingSales(fn){ var dailySales=[]; return function(val){ if(val==undefined){ return fn.apply(null,dailySales); }else{ dailySales.push(val); } }; } var weeklyTotal=curryingSales(function(){ var sum=0; for(var i=0;l=arguments.length,i<l;i++){ sum+=arguments[i]; } return sum; }); weeklyTotal(2); weeklyTotal(1); weeklyTotal(2); weeklyTotal(3); weeklyTotal(1); weeklyTotal(5); weeklyTotal(4); console.log(weeklyTotal());
3. 提前返回
小案例:对addEventListener和attachEvent的兼容代码
var addEvent = function() { // 浏览器支持addEventListener方法 if(window.addEventListener){ return function(element, type, callback, captrue) { elem.addEventListener(type, callback, captrue); }; } else { // 浏览器支持attachEvent方法 return function(element, type, callback) { elem.attachEvent('on' + type, callback); }; } }();
二、严格模式的相关知识点
1. 出现严格模式的目的
(1)消除JavaScript语法的一些不合理、不严谨之处,减少一些怪异行为;
(2)消除代码运行的一些不安全之处,保证代码运行的安全;
(3)提高编译效率,增加运行效率;
(4)为未来新版本的JavaScript做好铺垫。
2. 严格模式标记
直接在js代码中直接添加 "use strict";即可, 在老版本浏览器会将其当做一行普通字符串忽略。
3. 使用模式
(1)针对整个脚本文件
将"use strict;"放在脚本文件的第一行,则整个脚本将以“严格模式”运行。如果此语句不放在第一行,则无效,整个脚本以“正常模式”运行。
<script> "use strict"; console.log("这是严格模式"); </script> <script> console.log("这是正常模式"); "use strict"; </script>
(2)针对单个函数
将"use strict;"放在函数体的第一行,则整个函数以"严格模式"运行。
function strict() { "use strict"; console.log("这是严格模式"); } function noStrict() { console.log("这是正常模式"); "use strict"; }
(3)脚本文件的变通写法
由于第一种方式不利于文件合并,所以最好的做法是:将整个脚本文件放在一个沙箱模式(立即执行的匿名函数)中。
(function() { "use strict"; // 代码块 }());
4. 语法以及行为变化
严格模式下,对JavaScript的语法和行为,都做一些变化。
(1)全局变量的隐式声明
在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。但是在严格模式已禁止这种用法,全局变量必须显式声明。
"use strict";
v = 1; // 报错,v is not defined。
在严格模式下,变量必须先使用var定义,再赋值。
(2)静态绑定
JavaScript语言一个特点,就是允许"动态绑定",即某些属性和方法属于哪一个对象,不是在编译时确定的,而是在运行时确定的。
严格模式对动态绑定做一些限制。某些情况下,只允许静态绑定。也就是说,属性和方法到底归属哪个对象,在编译阶段就确定。这样做有利于编译效率的提高,也使代码更易阅读,更少出现bug。
a. 禁止使用with语句
原因:with语句无法在编译阶段就确定属性到底归属哪个对象。
"use strict"; var a = 1; // 报错,语法异常 with(obj){ a = 2; }
b. eval作用域
正常模式,JavaScript语言具有两种变量作用域:全局作用域 和 函数作用域(局部作用域)。
严格模式,具有第三种作用域:eval作用域。
正常模式下,eval语句的作用域取决于 它处于全局作用域,还是函数作用域。
严格模式下,eval语句本身就是一个作用域,不再能够产生全局变量,其所生产的变量只能用于eval内部。
<script> "use strict"; var x = 2; console.log(eval("var x = 3; x")); // 3 console.log(x); // 2 </script>
c. 增强安全性
在普通函数执行模式下,禁止this关键字指向全局对象. 因此,在使用构造函数时,忘记写new,this就不再指向window对象,而是报错。就不会意外给window对象添加属性或方法。
function foo() { console.log(this); // window } function foo() { "use strict"; console.log(this); // undefined } function foo() { "use strict"; this.name = "tom"; } var f = foo(); // 报错
d. 禁止在函数内部访问caller以及arguments
function fn() { "use strict"; fn.caller; // 报错 fn.arguments; // 报错 } fn();
e. 禁止删除变量
在严格模式下无法删除变量。只有configurable设置为true的对象属性,才能被删除。
"use strict"; var x; delete x; // 报错 var obj = Object.create(null, { "x": { value: 10, configurable: true } }); delete obj.x; // success
f. 显式报错
(1) 在正常模式下,为一个对象的只读属性进行赋值,不会报错,只会默默的失败;而严格模式下,会抛出异常。
"use strict"; var obj = {}; Object.defineProperty(o, "a", { value: 1, writable: false }); obj.a = 2; // 报错
(2) 严格模式下,对一个使用getter方法读取的属性进行赋值,会报错。
"use strict"; var obj = { get a() { return 1; } }; obj.a = 2; // 报错
(3) 严格模式下,对禁止扩展的对象添加新属性,会报错。
"use strict"; var obj = {}; Object.preventExtensions(obj); obj.a = 1; // 报错
(4)严格模式下,删除一个不可删除的属性,会报错。
"use strict"; delete Object.prototype; // 报错
g. 重名错误: 严格模式新增了一些语法错误。
(1)对象不能有重名的属性
正常模式下,如果对象有多个重名属性,最后赋值的那个属性会覆盖前面的值。严格模式下,这属于语法错误。
"use strict"; var obj = { p: 1, p: 2 }; // 语法错误
(2)函数不能有重名的参数
正常模式下,如果函数有多个重名的参数,可以用arguments[i]读取。严格模式下,这属于语法错误。
"use strict"; function f(a, a, b) { // 语法错误 return; }
(3)arguments对象的限制
arguments是函数的参数对象,严格模式对它的使用做了限制。
a. 不允许对arguments赋值
"use strict"; arguments++; // 语法错误 var obj = { set p(arguments) { } }; // 语法错误 try { } catch (arguments) { } // 语法错误 function arguments() { } // 语法错误 var f = new Function("arguments", "'use strict'; return 17;"); // 语法错误
b. arguments不再追踪参数的变化
function f(a) { a = 2; return [a, arguments[0]]; } f(1); // 正常模式为[2,2] function f(a) { "use strict"; a = 2; return [a, arguments[0]]; } f(1); // 严格模式为[2,1]
c. 禁止使用arguments.callee
这意味着,无法在匿名函数内部调用自身了。
"use strict"; var f = function() { return arguments.callee; }; f(); // 报错
(4)函数必须声明在顶层
将来Javascript的新版本会引入"块级作用域"。为了与新版本接轨,严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。
"use strict"; if (true) { function f1() { } // 语法错误 } for (var i = 0; i < 5; i++) { function f2() { } // 语法错误 }
(5)保留字
为了向将来Javascript的新版本过渡,严格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。
使用这些词作为变量名将会报错。
function package(protected) { // 出现保留字,语法错误 "use strict"; var implements; //出现保留字, 语法错误 }
此外,ECMAscript第五版本身还规定了另一些保留字(class, enum, export, extends, import, super),以及各大浏览器自行增加的const保留字,也是不能作为变量名的。
三、ES5数组的新方法
1. forEach
forEach
是Array新方法中最基本的一个,就是遍历,循环。Array在ES5新增的方法中,参数都是function
类型,默认有传参,forEach
方法中的function
回调支持3个参数,第1个是遍历的数组内容;第2个是对应的数组索引,第3个是数组本身。
[].forEach(function(value, index, array) { // ... });
2. map
这里的map
不是“地图”的意思,而是指“映射”。[].map();
基本用法跟forEach
方法类似:array.map(callback,[ thisObject]);callback参数与forEach类似:
[].map(function(value, index, array) { // ... });
案例:数组项求平方
var data = [1, 2, 3, 4]; var squares = data.map(function (item) { return item * item; }); alert(squares); // 1, 4, 9, 16
3. filter 过滤器
filter
为“过滤”、“筛选”的意思。指数组filter
后,返回过滤后的新数组。用法跟map
极为相似:array.filter(callback,[ thisObject]);
filter
的callback
函数需要返回布尔值true
或false
.
4. some和every
some:指“某些”的意思,指是否“某些项”合乎条件。every:表示是否“每一项”都要靠谱.用法如下:
array.some(callback,[ thisObject]);
简单演示:
var scores = [5, 8, 3, 10]; var current = 7; function higherThanCurrent(score) { return score > current; } if (scores.some(higherThanCurrent)) { alert("朕准了!"); }
array.every(callback,[ thisObject]);
简单演示:
var scores = [5, 8, 3, 10]; var current = 7; if (scores.every(higherThanCurrent)) { console.log("朕准了!"); } else { console.log("来人,拖出去斩了!"); }
5. indexOf
indexOf
方法在字符串中自古就有,string.indexOf(searchString, position)
。数组的indexOf
方法与之类似。用法:array.indexOf(searchElement[, fromIndex]); 返回整数索引值,如果没有匹配(严格匹配),返回-1
. fromIndex
可选,表示从这个位置开始搜索,若缺省或格式不合要求,使用默认值0.
小案例:
var data = [2, 5, 7, 3, 5]; console.log(data.indexOf(5, "x")); // 1 ("x"被忽略) console.log(data.indexOf(5, "3")); // 4 (从3号位开始搜索) console.log(data.indexOf(4)); // -1 (未找到) console.log(data.indexOf("5")); // -1 (未找到,因为5 !== "5")
6. lastIndexOf
lastIndexOf
方法与indexOf
方法类似:array.lastIndexOf(searchElement[, fromIndex]);只是lastIndexOf
是从字符串的末尾开始查找,而不是从开头。还有一个不同就是fromIndex
的默认值是array.length - 1
而不是0
.
var data = [2, 5, 7, 3, 5]; console.log(data.lastIndexOf(5)); // 4 console.log(data.lastIndexOf(5, 3)); // 1 (从后往前,索引值小于3的开始搜索) console.log(data.lastIndexOf(4)); // -1 (未找到)
7. reduce
迭代”、“递归(recursion)”的含义,用法:array.reduce(callback[, initialValue]);callback
函数接受4个参数:之前值、当前值、索引值以及数组本身。initialValue
参数可选,表示初始值。若指定,则当作最初使用的previous
值;如果缺省,则使用数组的第一个元素作为previous
初始值,同时current
往后排一位,相比有initialValue
值少一次迭代。
小案例:
var arr = [10,11,12,13,14]; var sum = arr.reduce(function(prev, current, index, arr) { return prev + current; }, 0); console.log(sum); //60
实现上:
// 初始设置 previous = initialValue = 10, current = 11 // 第一次迭代 previous = (10 +11) = 21, current = 12 // 第二次迭代 previous = (21+ 12) = 33, current =13 // 第三次迭代 previous = (33 + 13) = 46, current = 14 //第四次迭代 previous = (46 + 14) = 60, current = undefined(退出)
8.reduceRight
用法与reduce相似,实现上差异在于reduceRight
是从数组的末尾开始实现。
小案例:
var data = [1, 2, 3, 4]; var special = data.reduceRight(function (previous, current, index) { if (index == 0) { return previous + current; } return previous - current; }); console.log(special); // 0
实现上:
// 初始设置 index = 3, previous = initialValue = 4, current = 3 // 第一次迭代 index = 2, previous = (4- 3) = 1, current = 2 // 第二次迭代 index = 1, previous = (1 - 2) = -1, current = 1 // 第三次迭代 index = 0, previous = (-1 + 1) = 0, current = undefined (退出)
四、创建对象的方式
1. 工厂模式
创建对象,返回带有属性和方法的person对象
function createPerson(name, age,gender) { var person = new Person(); person.name=name; person.age=age; person.gender=gender person.sayName=function() { alert(this.name); }; return person; } createPerson("tom",20,"男").sayName();
2. 构造函数模式
创建对象,这种方式有个缺陷是sayName这个方法,它的每个实例都是指向不同的函数实例,而不是同一个。
function Person(name,age,gender) { this.name=name; this.age=age; this.gender=gender; this.sayName=function() { console.log(this.name); }; } var person = new Person("rose",23,"女"); person.sayName();
3. 原型模式
创建对象,解决了上述中提到的缺陷,使不同的对象的函数(如sayFriends)指向了同一个函数。但它本身也有缺陷,就是实例共享了引用类型friends,从下面的代码执行结果可以看到,两个实例的friends的值是一样的。
function Person() { } Person.prototype = { constructor : Person, name:"Tom", age:20, gender:"男", friends:["Jack","Rose"], sayFriends:function() { console.log(this.friends); } }; var person1 = new Person(); person1.friends.push("Mary"); person1.sayFriends(); //Jack,Rose,Mary var person2 = new Person(); person2.sayFriends(); //Jack,Rose,Mary
4. 组合模式(推荐)
使用原型模式和构造函数创建对象,解决了上述方法中提到的缺陷,而且这也是使用最广泛、认同度最高的创建对象的方法,强烈推荐使用。
function Person(name,age,gender) { this.name=name; this.age=age; this.gender=gender; this.friends=["Jack","Rose"]; } Person.prototype.sayFriends=function() { console.log("My name is"+this.name+","+"My friends are"+this.friends); }; var person1 = new Person("Tom",20,"男"); var person2 = new Person("Alice",18,"女"); person1.friends.push("Marck"); person1.sayFriends(); //My name is Tom ,My friends are Jack,Rose,Marck person2.sayFriends(); //My name is Alice,My friends are Jack,Rose
5. 动态原型
这个模式的好处在于看起来更像传统的面向对象编程,具有更好的封装性,因为在构造函数里完成了对原型创建。这也是一个推荐的创建对象的方法。
function Person(name, age) { this.name = name; this.age = age; // method if(typeof this.sayName !== 'function'){ Person.prototype.sayName = function() { console.log(this.name); } } }
6. 寄生构造函数模式 (不常用,不推荐)
function Person(name, age) { var obj = new Object; obj.name = name; obj.age = age; obj.sayName = function() { console.log(this.name); }; return obj; }
7. 稳妥构造函数模式 (不常用,不推荐)
function Person(name, age) { var obj = new Object; obj.sayName = function() { console.log(name); }; }
五、继承方式
1. 原型式
利用原型让一个引用类型继承另外一个引用类型的属性和方法
function A() { } A.prototype.location = "地球上。"; A.prototype.say = function() {}; A.prototype.talk = function() {}; A.prototype.run = function() {}; var a = new A; console.log(a.location); A.prototype = { say:function() {}, talk:function() {}, run:function() {} }; var na = new A; console.log(na);
2. 混入式
var o1 = { name: 'tom' }; var o2 = { age: 18 }; var o3 = { gender: '男' }; console.log(o1); o1.extend = function() { var k, i = 0, l = arguments.length, args = arguments; // 遍历在arguments上的每一对象 for (; i < l; i++) { // 枚举当前对象上的所有属性,添加到this上 for (k in args[i]) { this[k] = args[i][k]; } } }; o1.extend(o2, o3); console.log(o1);
3. 借用构造函数
在子类型构造函数的内部调用超类构造函数,通过使用call()和apply()方法可以在新创建的对象上执行构造函数。
function SuperType() { this.colors = ["red","blue","green"]; } function SubType() { SuperType.call(this);//继承了SuperType } var instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors);//"red","blue","green","black" var instance2 = new SubType(); console.log(instance2.colors);//"red","blue","green"
4. 对象冒充
function parent(name, age) { this.name = name; this.age = age; } function child(name, age, gender, address) { // 将parent作为child对象的一个方法来调用 this.parent = parent; this.parent(name, age); delete this.parent; this.gender = gender; this.address = address; } var ch = new child('tom', 18, 'boy', 'beijing'); console.log(ch);
5. 组合继承(借用构造函数、原型)
将原型链和借用构造函数的技术组合在一块,从而发挥两者之长的一种继承模式。
6. 寄生式(不常用)
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真正是它做了所有工作一样返回对象。
7. 组合寄生(不常用)
通过借用函数来继承属性,通过原型链的混成形式来继承方法