JS进阶(二)-ES5新增方法、函数进阶
ES5 中的新增方法
- ES5 中给我们新增了一些方法,可以很方便的操作数组或者字符串,这些方法主要包括:
- 数组方法
- 字符串方法
- 对象方法
数组方法
- 迭代(遍历)方法:
forEach()、map()、filter()、some()、every();
forEach
-
方法
array.forEach(function(currentValue, index, arr))
- currentValue:数组当前项的值
- index:数组当前项的索引
- arr:数组对象本身
-
举例
var arr = [1,3,5]; arr.forEach(function(currentValue, index, arr){ console.log('每个元素值=' + currentValue+'索引=' + index ) // 数组本身 console.log(arr); })
filter
-
方法
array.filter(function(currentValue, index, arr))
- filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组
- 注意它直接返回一个新数组
- currentValue: 数组当前项的值
- index:数组当前项的索引
- arr:数组对象本身
-
举例
var arr = [12,33,5,54]; var newarr = arr.filter(function(currentValue, index, arr){ //筛选出大于20的值 return currentValue >= 20; }) console.log(newarr)
some
-
方法
array.some(function(currentValue, index, arr))
- some() 方法用于检测数组中的元素是否满足指定条件. 通俗点 查找数组中是否有满足条件的元素
- 注意它返回值是布尔值, 如果查找到这个元素, 就返回true , 如果查找不到就返回false.
- 如果找到第一个满足条件的元素,则终止循环. 不在继续查找.
-
代码举例:
var arr = [10,30,4]; var flag = arr.some(function(currentValue, index, arr){ return currentValue>= 20; }) console.log(flag)
字符串方法
- trim() 方法会从一个字符串的两端删除空白字符。
-
trim() 方法并不影响原字符串本身,它返回的是一个新的字符串。
var str = ' hh '; // 提出字符串2端的空格 var newstr = str.trim(); console.log(str); console.log(newstr);
对象方法
Object.keys()
- Object.keys() 用于获取对象自身所有的属性
- 效果类似 for…in
- 返回一个由属性名组成的数组
-
举例
var obj = { id:1, name: '小米', price: 1999, num: 2000 }; // 获取对象的所有属性名 console.log(Object.keys(obj));
Object.defineProperty()
-
Object.defineProperty() 定义新属性或修改原有的属性。
Object.defineProperty(obj, prop, descriptor)
- obj:必需。目标对象
- prop:必需。需定义或修改的属性的名字
- descriptor:必需。目标属性所拥有的特性
- 第三个参数 descriptor 说明
- value:设置属性的值
- writable:值是否可以重写。
true | false
- enumerable:目标属性是否可以被枚举。
true | false
- configurable:目标属性是否可以被删除或是否可以再次修改特性
true | false
-
代码举例:
var obj = { id:1, name: '小米', price: 1999 }; // 以前方式 // // 新增属性 // obj.num = 2000; // // 修改 // obj.price = 1800; // 新方式 // 新增 Object.defineProperty(obj,'num',{ value:1000 }); // 修改 Object.defineProperty(obj,'price',{ value:1880 }); // 修改 Object.defineProperty(obj,'id',{ // 不允许修改属性值 writable: false }); // enumerable Object.defineProperty(obj,'address',{ value:'中国河南郑州', // 不允许修改属性值 writable: false, // 不允许被遍历查看 enumerable:false, // 不允许被删除 configurable:false }); // 尽管不报错,但是还是修改不了原来的id值 obj.id = 2; // 遍历中看不到address属性,也遍历不到num属性,因为enumerable默认为false console.log(Object.keys(obj)) // 删除address属性 delete obj.address; // 仍然可以看到address属性,不允许被删除 console.log(obj) // 不允许再次被修改,否则报错 Object.defineProperty(obj,'address',{ value:'中国河南郑州', // 不允许修改属性值 writable: false, // 不允许被遍历查看 enumerable:false, // 允许被删除 configurable:true });
经典案例
- 商品查询案例,视频观看
函数进阶
函数的定义和调用
函数的定义方式
- 函数声明方式 function 关键字 (命名函数)
- 函数表达式 (匿名函数)
-
new Function()
var fn = new Function('参数1','参数2'..., '函数体')
- Function 里面参数都必须是字符串格式
- 第三种方式执行效率低,也不方便书写,因此较少使用
- 所有函数都是 Function 的实例(对象)
- 所有函数都是通过Function这个类创建出来的
- 函数也属于对象
- 注意:Function必须首字母大写
// 1. 自定义函数(命名函数) function fn(){ } // 2. 函数的表达式(匿名函数) var fun = function() { } // 3. 利用new Function() var f = new Function('console.log(123)'); f(); var f2 = new Function('a','b','console.log(a+b)'); f2(2,3); // f属不属于Object ,用于判断是否属于对象,true console.log(f instanceof Object);
函数的调用方式
// 1. 普通函数
function fn(){}
fn();
fn.call();
// 2. 对象的方法
var o = {
func: function(){ }
}
o.func();
// 3. 构造函数
function Star(){}
new Star();
// 4. 绑定事件函数
btn.onclick = function(){}
// 点击按钮调用
// 5. 定时器函数
// 每隔1s调用一次
setInterval(function(){},1000);
// 6. 立即执行函数
// 创建即调用
(function(){})()
this
-
这些 this 的指向,是当我们调用函数的时候确定的。 调用方式的不同决定了this 的指向不同 一般指向我们的调用者.
调用方式 this指向 普通函数 window 对象的方法 该方法所属对象 构造函数 实例对象,原型对象里面的方法也指向实例对象 绑定事件函数 绑定事件对象 时器函数 window 立即执行函数 window
- 改变函数内部 this 指向
- JavaScript 为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部 this 的指向问题,常用的有
bind()、 call()、apply()
三种方法。
- JavaScript 为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部 this 的指向问题,常用的有
- call方法,前面讲过(略)
apply 方法
- apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
fun.apply(thisArg, [argsArray])
- thisArg:在fun函数运行时指定的 this 值
- argsArray:传递的值,必须包含在数组里面
- 返回值就是函数的返回值,因为它就是调用函数
- 因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值
-
代码举例:
var o = { name : 'rose' } function fn(a,b){ console.log(this); console.log(a+b); } // 修改fn函数的this指向,以及传参 fn.apply(o,[1,3]); // apply应用,求数组中的最大值 var arr = [23,45,6,55,33,66]; // Math.max只能求数字中的最大值不能求数组中的最大值,但是可以通过apply来实现求数组中的最大值 var maxvalue = Math.max.apply(Math,arr); var minvalue = Math.min.apply(Math,arr); // 66 console.log(maxvalue,minvalue)
bind 方法
- bind() 方法不会调用函数。但是能改变函数内部this 指向
fun.bind(thisArg, arg1, arg2, ...)
- thisArg:在 fun 函数运行时指定的 this 值
- arg1,arg2:传递的其他参数
- 返回由指定的 this 值和初始化参数改造的原函数拷贝
- 因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind
- 注意:bind方法会产生一个改变this指向的新函数,并不会修改原函数的this指向
-
代码举例:
var o = { name : 'rose' } function fn(){ console.log(this); } // 只绑定,不调用,生成一个新的函数 // 返回的是一个改变this之后的新函数,不会影响原函数 var f = fn.bind(o); // 改变了this,为o f(); // this没有改变,仍然为window fn();
- 使用场景:如果有的函数我们不想立即调用,但是又想改变这个函数内部的this指向,此时用bind
- 一个按钮,当点击之后就禁用这个按钮,3s中之后恢复
<button>按钮</button> <script> var btn = document.querySelector('button'); btn.onclick = function() { this.disabled = true; setTimeout(function(){ // 默认this是window,但是如果使用了bind,this就改变指向了 this.disabled = false }.bind(this),3000);// 注意bind()中的this指向的是btn对象 } </script>
call apply bind 总结
- 相同点:
- 都可以改变函数内部的this指向.
- 区别点:
- call 和 apply 会调用函数, 并且改变函数内部this指向.
- call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2..形式;apply 必须数组形式
[arg]
- bind 不会调用函数, 可以改变函数内部this指向.
- 主要应用场景:
- call 经常做继承.
- apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
- bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.
严格模式
- 什么是严格模式
- JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。
- 严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
- 严格模式对正常的 JavaScript 语义做了一些更改:
- 消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
- 消除代码运行的一些不安全之处,保证代码运行的安全。
- 提高编译器效率,增加运行速度。
- 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比 如一些保留字如:class, enum, export, extends, import, super 不能做变量名
- 开启严格模式
- 严格模式可以应用到整个脚本(整个script标签中)或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和 为函数开启严格模式两种情况。
为脚本开启严格模式
- 为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句
“use strict”;(或‘use strict’;)
。 -
因为
"use strict"
加了引号,所以老版本的浏览器会把它当作一行普通字符串而忽略。<script> "use strict"; console.log("这是严格模式。"); </script>
-
有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件 放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。
<script> (function() { 'use strict'; // 下面的代码都是严格模式 })() </script>
为函数开启严格模式
- 要给某个函数开启严格模式,需要把
“use strict”; (或 'use strict'; )
声明放在函数体所有语句之前。 -
将
"use strict"
放在函数体的第一行,则整个函数以 “严格模式” 运行。function fn(){ "use strict"; return "这是严格模式。"; }
严格模式中的变化
- 严格模式对 Javascript 的语法和行为,都做了一些改变。
变量规定
- 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用 var 命令声明,然后再使用。
- 严禁删除已经声明变量。例如,delete x; 语法是错误的。
严格模式下 this 指向问题
- 以前在全局作用域函数中的 this 指向 window 对象。
- 严格模式下全局作用域中函数中的 this 是 undefined。
- 以前构造函数时不加 new也可以 调用,当普通函数,this 指向全局对象
- 严格模式下,如果构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错
- new 实例化的构造函数指向创建的对象实例。
- 定时器 this 还是指向 window 。
- 事件、对象还是指向调用者。
函数变化
- 函数不能有重名的参数。
- 函数必须声明在顶层.新版本的 JavaScript 会引入“块级作用域”( ES6 中已引入)。不允许在非函数的代码块内声明函数。
- 非函数代码块,比如:if、swith等
<script>
// 非严格模式
// 1.变量没有声明就赋值,可以
num = 10;
console.log(num);
// 2. 删除已经声明的变量,可以
var num2 = 10;
delete num2;
// 3. 全局函数this指向window
function fn() {
// window
console.log(this);
}
fn();
// 4. 构造函数不加new也可以调用,当做普通函数调用
function Star() {
this.sex = '男';
}
Star();
console.log(window.sex);
// 6. 函数能有重名的参数。
function test(a,a){
}
// 严格模式
(function() {
'use strict';
// 1. 变量没有声明就赋值,不可以
// num1 = 10;
// console.log(num1);
// 删除已经声明的变量,不可以
// var num21 = 10;
// delete num21;
// 3. 全局函数this指向undefined
// function fn() {
// // undefined
// console.log(this);
// }
// fn();
// 4. 构造函数不加new也可以调用,不能当做普通函数调用
function Star2() {
// this为undefined,不可以赋值
this.sex = '男';
}
// this为undefined,不可以赋值
// Star2();
// 5. 这么可以调用
new Star2();
// 6. 函数不能有重名的参数。
// function test1(a,a){}
})()
</script>
高阶函数
-
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
<script> function fn(callback){ callback&&callback(); } fn(function(){alert('hi')}; </script> <script> function fn(){ return function() {} } fn(); </script>
- 此时fn 就是一个高阶函数
- 函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。 最典型的就是作为回调函数。
- 同理函数也可以作为返回值传递回来
闭包
- 变量作用域
- 变量根据作用域的不同分为两种:全局变量和局部变量。
- 函数内部可以使用全局变量。
- 函数外部不可以使用局部变量。
- 当函数执行完毕,本作用域内的局部变量会销毁。
- 什么是闭包
- 闭包(closure)指有权访问另一个函数作用域中变量的函数。 —– JavaScript 高级程序设计
-
简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
<script> // 闭包:我们fun这个函数作用域访问了另外一个函数fn里面的局部变量num // fn就是闭包函数 function fn() { var num = 10; function fun(){ console.log(num) } fun(); } fn(); </script>
- 在 chrome 中调试闭包
- 打开浏览器,按 F12 键启动 chrome 调试工具。
- 选择sources,设置断点。
- 右边,找到 Scope 选项(Scope 作用域的意思)。
- 当我们重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)。
- 当执行到 fun() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。
- 闭包的作用
-
我们怎么能在 fn() 函数外面访问 fn() 中的局部变量 num 呢 ?
function fn() { var num = 10; // function fun(){ // console.log(num) // } // return fun; return function fun(){ console.log(num); }; } var f = fn(); //这里访问了num成员 f();
-
闭包作用:延伸变量的作用范围。
-
- 案例
-
点击li获取索引号
// 点击li输出当前li的索引号 // 方式1: var lis = document.querySelector('.nav').querySelectorAll('li'); for(var i = 0;i<lis.length;i++){ lis[i].index = i; lis[i].onclick = function (){ console.log(i); } } // 方式2;利用闭包的方式获取索引 for(var i = 0;i<lis.length;i++){ // 利用for循环创建了4个立即执行函数 (function(i){ lis[i].onclick = function (){ // 当前函数用到了外部函数的变量i console.log(i); } })(i) }
-
案例2:3s后打印出li的内容
// 闭包引用,3秒钟之后,打印所有li元素的内容 var lis = document.querySelector('.nav').querySelectorAll('li'); for(var i = 0;i<lis.length;i++){ (function(i){ setTimeout(function(){ console.log(lis[i].innerText); },3000); })(i) }
-
经典案例:
// 思考题 1: var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { return function() { //这个this指向window return this.name; }; } }; //返回:The Window,没有闭包 console.log(object.getNameFunc()()) var f = object.getNameFunc(); // 类似于 var f = function() { return this.name; } f(); // 思考题 2: // var name = "The Window"; // var object = { // name: "My Object", // getNameFunc: function() { // var that = this; // return function() { // return that.name; // }; // } // }; //返回My Object,有闭包 // console.log(object.getNameFunc()())
-
浅拷贝和深拷贝
- 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用
- 深拷贝拷贝多层,每一级别的数据都会拷贝
浅拷贝
- 对于基本类型属性无论是深浅拷贝,都会复制一份
- 对于引用类型属性浅拷贝拷贝的是引用类型的地址
- 简而言之, 浅拷贝修改引用类型属性, 拷贝前拷贝后的对象都会受到影响
-
举例:
var obj1 = { name: "lnj", // 基本类型属性 age: 33, // 基本类型属性 dog: { // 引用类型属性 name: "wc", age: 1, } }; var obj2 = {}; /* for(var key in obj1){ //key是属性名,obj1[key]是属性值 obj2[key] = obj1[key]; } */ function copy(o1, o2){ for(var key in o1){ o2[key] = o1[key]; } } copy(obj1, obj2); console.log(obj1); console.log(obj2); // 修改基本类型属性, 不会影响原有对象 obj2.name = "zs"; console.log(obj1.name); console.log(obj2.name); // 修改引用类型属性, 会影响原有对象 obj2.dog.name = "xq"; console.log(obj1.dog.name); console.log(obj2.dog.name);
-
ES6方法实现浅拷贝
Object.assign(target,...source)
var obj = { id : 1, name : 'rose', msg : { age : 18 } } var o = {}; // ES6浅拷贝方法 Object.assign(o,obj); console.log(o);
深拷贝
- 对于基本类型属性无论是深浅拷贝,都会复制一份
- 对于引用类型属性深拷贝会将引用类型复制一份
- 简而言之, 深拷贝修改引用类型属性, 只会影响当前修改对象
-
举例:
var obj1 = { name: "lnj", // 基本类型属性 age: 33, // 基本类型属性 dog: { // 引用类型属性 name: "wc", age: 1, } }; var obj2 = {}; function copy(o1, o2){ for(var key in o1){ var item = o1[key]; // 判断当前属性是否数组,先判断数组,再判断Object,因为数组也属于Object if(item instanceof Array){ // 创建一个数组属性 var arr = []; o2[key] = arr; // 进一步拷贝数组属性 copy(item, arr); } // 判断当前属性是否是引用类型 else if(item instanceof Object){ // 创建一个新引用类型属性 var o = {}; o2[key] = o; // 进一步拷贝引用类型属性 copy(item, o); } // 如果不是引用类型, 直接拷贝即可 else{ o2[key] = item; } } } copy(obj1, obj2); console.log(obj1); console.log(obj2); // 修改基本类型属性, 不会影响原有对象 obj2.name = "zs"; console.log(obj1.name); console.log(obj2.name); // 修改引用类型属性, 会影响原有对象 obj2.dog.name = "xq"; console.log(obj1.dog.name); console.log(obj2.dog.name);