【JavaScript高级】修改this,严格模式,闭包,深浅拷贝

 

函数的定义

// 命名函数
function fun() {};

// 匿名函数
var fun = function() {};

// 对象方式
var fun = new Function('参数1', '参数2', '函数体');	// 注意全是字符串形式

// 前两种比较常用;第三种效率较低
// 但从第三种书写方式中,我们要理解这样的思想:所有函数都是Function的实例对象

 

this指向

记住一句话就行:this指向的是函数的调用者

对于普通函数定时器函数立即执行函数,调用者为window
 
JavaScript提供了三种优雅的方法改变this的指向:call()apply()bind()

func.call(新的this指向, 其他参数...)			// 调用函数,改变指向。
func.apply(新的this指向, [其他参数...])		// 调用函数,改变指向。传入的参数是数组(伪数组)
func.bind(新的this指向, 其他参数...)			// 不调用函数,改变指向。返回一个新的函数。


// call的典型用法
function Son(uname) {
	Father.call(this, uname);			// 实现继承
}

// apply的典型用法
Math.max.apply(Math, arr);				// 利用数学内置对象的方法,来求数组的最大值 —— 调用者不改变,但参数改为数组
Math.min.apply(Math, arr);				// 同理

// bind的典型用法
// 使用场景:有的函数我们并不想在改变this指向的同时立即调用
// 比如:有一个按钮,按下后禁用3s后再次可用
btn.addEventListener('click', function() {
    this.disabled = true;
    setTimeout(function() {
        this.disabled = false;			// 提前把this存在that也行,但没有这种写法优雅
    }.bind(this), 3000)					// 将this指针绑定在了btn上,但没有立即调用,而是依旧由计时器控制
})										// 注意bind里的this是在函数之外的,指向的是btn

// 补充:多个按钮的情况
for(var i = 0; i < btns.length; i++) {
	btns[i].addEventListener('click', function() {
		this.disabled = true;
		setTimeout(function() {
			this.disabled = false;		// 这里千万不要企图使用btns[i]——点击btns[2]是开启计时器,2s过后这个btn[i]不一定是btn[2]!!!
		}.bind(this), 3000)
	})
}

tip:通过其英文含义理解它们:call【调用】,apply【应用】, bind【绑定】

 

 

严格模式(strict mode)

严格模式对正常的JavaScript语义做了一些修改

严格模式分为为脚本开启严格模式为函数开启严格模式

<!-- 给整个脚本开启严格模式 -->
<script>
    (function() {
        'use strict';		// 开启严格模式
        // ...
     })()					// 通过立即执行函数开辟一个独立空间,防止变量名冲突(常用写法)	
</script>

<!-- 给函数开启严格模式,就在函数的第一行加上'use strict' -->
// 严格模式('use strict')下的变化

n = 12;				// (×)变量必须先声明
delete n;			// (×)不能随意删除已经声明好的变量


function fun() {
	console.log(this);		// 全局作用域中的this不再指向window,而是undefined
}


function Dog() {
	this.age = 3;
}
Dog();						// 报错。必须加new,否则构造函数被当做普通函数调用,这个age属性显然不能给undedined(非严格模式下给了window)


setTimeout(function() {
     console.log(this);		// 定时器内,this仍旧是指向window的
}, 1000)


function fun(a, a) {
	console.log(a + a);		
}
func(1, 2);					// 非严格模式下,会输出4;这种覆盖显然很奇怪,因此严格模式下禁止形参重名


for(var i = 0; i < 10; i++) {
	function() {};			// 报错
}
if(true) {
	function() {};			// 报错
}

function fun1() {
	function fun2() {};		// 合法。总结来说,就是因为ES6引入了块级作用域,因此严格模式下[禁止在非函数的代码块中声明后函数]
}

 

高阶函数

高阶函数是对其他函数进行操作的函数。

特点是:把函数作为传入的参数返回值

 

闭包(closure)

闭包指有权访问另一个函数作用域中局部变量的函数。

function fun1(){				// fun1是一个典型的闭包——fun1的内部(包括内部的函数的内部),都能使用变量n
    var n = 12;
    function fun2(){
        console.log(n);			// 因为fun2函数的作用域内访问了另一个作用域(fun1)的变量
    }
    fun2();
}
fun1();


function fun() {				// fun函数就是一个闭包
    var n = 10;
    return function() {			// 可以看出,这里的闭包是一个高阶函数
        console.log(n);
    }
}
var f = fun();
f();							// 在fun的作用域外,依旧访问了n

结合这个典例,理解闭包的主要作用:在时间和空间上延伸了变量的作用范围
 
闭包案例1(点击小li后,输出该小li的索引号)

var lis = document.querySelector('.vtb').querySelectorAll('li');

// 传统写法
for (var i = 0; i < lis.length; i++) {
    lis[i].index = i;
    lis[i].addEventListener('click', function() {
        console.log(this.index);
        // 因为for循环是同步任务(仅仅立即循环一次),function是异步任务(任务队列),因此不能直接打印i(i是循环一次后的最终值)
    })
}

// 闭包写法
for (var i = 0; i < lis.length; i++) {
    // 利用for循环创建了4个立即执行函数
    // 这些立即执行函数都是闭包——因为立即函数内部都能使用传入的i
    (function(i) {
        lis[i].addEventListener('click', function() {
            console.log(i); 	// 与上面的写法相比,可以直接打印i(通过闭包延长了i的作用范围)
        })
    })(i); 		// 立即把i传入每个立即函数的独立作用域内
}

闭包案例2(3s后,打印所有li元素的内容)

// 错误写法
for (var i = 0; i < lis.length; i++) {
    setTimeout(function() {
        console.log(lis[i].innerHTML);	
    }, 3000)					
}
// 分析:
// for是同步任务,计时器内的function是异步任务,进入任务队列;此时for循环在主线程内已经执行完毕,i = 4,此时打印lis[i]只能得到undefined


// 正确写法(闭包)
for (var i = 0; i < lis.length; i++) {
    (function(i) {
        setTimeout(function() {
            console.log(lis[i].innerHTML);
        }, 3000)
    })(i);
}
// 分析:仍旧是典型的、利用立即执行函数传入i形成闭包,来延长i的作用范围的思路

闭包案例3

// 计算打车价格
// 打车起步价13块钱(3公里内),之后每多一公里增加5块钱
// 如果遇到拥堵,总价格多收取10元拥堵费
var car = (function() {
    var start = 13;
    var total = 0; 			// 这个立即函数内都可以使用这两个变量,因此是闭包
    return { 				// 返回值是一个包含两个方法的对象
        getPrice: function(n) {
            if (n <= 3) {
                total = start;
            } else {
                total = start + (n - 3) * 5;
            }
            return total;
        },
        isCrowed: function(flag) {
            return flag ? total + 10 : total;
        }
    }
})();
console.log(car.getPrice(5));			// 这样去调用	
console.log(car.isCrowed(true));		// 因为car返回是个[包含两个方法的对象]

 

浅拷贝和深拷贝

var oldObj = {
    id: 233,						// 值为简单数据类型
    name: 'loli',
    color: ['pink', 'skyblue'],		// 值为数组
    msg: {							// 值为对象
        age: 12
    }
}

var newObj = {};	// 先声明出一个空的新对象,之后作为参数传入

// 浅拷贝
function shallowCpoy(newObj, oldObj) {
	for (var k in oldObj) {
    newObj[k] = oldObj[k];
	}
}

shallowCopy(newObj, oldObj);
oldObj.id = 255;					// newObj.id不受影响
oldObj.color[0] = 'deeppink';		// newObj.color[0]受到影响
oldObj.msg.age = 13;				// newObj.mag.age受到影响
// 分析:浅拷贝只拷贝一层(即简单数据类型这一层);而对象级别的(数组,对象)只是拷贝引用,也就是说,oldObj和newObj的color和msg指向同一个对象
// 其实,浅拷贝仅仅在键值对的层面上,拷贝了"值",而这个值不一定就是简单数据类型,还有可以又是一个键值对(数组或对象),也即引用

function deepCopy(newObj, oldObj) {		// 通过递归实现
    for (var k in oldObj) {
        var item = oldObj[k];
        if (item instanceof Array) {	// 先判断是否为数组 (因为数组也是对象)
            newObj[k] = [];
            deepCopy(newObj[k], item);
        } else if (item instanceof Object) {	//判断是否为对象
            newObj[k] = {};
            deepCopy(newObj[k], item);
        } else {						// 简单数据类型
            newObj[k] = item;
        }
    }
}

deepCopy(newObj, oldObj);
oldObj.id = 255;					// newObj.id不受影响
oldObj.color[0] = 'deeppink';		// newObj.color[0]不受影响
oldObj.msg.age = 13;				// newObj.msg.age不受影响
// 深拷贝拷贝多层,每一级都被拷贝,都是独立的



// 补充
Object.assign(newObj, oldObj);		// 浅拷贝有语法糖

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

☀ Loli & JS

♫ Suki

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值