一个原型链练习题
function fun(n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m, n);
}
};
}
var c = fun(0).fun(1);
c.fun(2);
c.fun(3);
THIS
每一个函数(普通函数/构造函数/内置类)都是Function这个内置类的实例,所以:函数._proto_===Function.prototype,函数可以直接调取Function原型上的方法
//Function.prototype => function anonymous(){}
/*
* call / apply / bind
* 原型上提供的三个公有属性方法
* 每一个函数都可以调用这个方法执行
* 这些方法都是用来改变函数中的THIS指向的
*/
function fn(){}
fn.call(); //=>fn函数基于原型链找到Function.prototype上的call方法,并且让其执行(执行的是call方法:方法中的this是fn)
fn.call.call(); //=>fn.call就是Function.prototype上的call方法,也是一个函数,只要是函数就能用原型上的方法,所以可以继续调用call来执行
/*
Function.prototype.call = function $1(){
//...
}
fn.call => $1
fn.call() => $1() this:fn
fn.call.call() => $1.call() => 继续让call执行,this:$1
实例.方法():都是找到原型上的内置方法,让内置方法先执行(只不过执行的时候做了一些事情会对实例产生改变,而这也是这些内置方法的作用),内置方法中的THIS一般都是当前操作的实例
*/
call方法
语法:函数.call([context],[params1],....)
函数基于原型链找到Function.prototype.call这个方法,并且把它执行,在call方法执行的时候完成了一些功能
- 让当前函数执行
- 把函数中的THIS指向改为第一个传递给CALL的实参
- 把传递给CALL其余的实参,当做参数信息传递给当前函数
如果执行CALL一个实参都没有传递,非严格模式下是让函数中的THIS指向WINDOW,严格模式下指向的是UNDEFINED
window.name = 'WINDOW';
let obj = {name: 'OBJ'};
function fn(n,m) {
console.log(this.name);
}
fn(10,20); //=>this:window 严格下是undefined
fn.call(obj); //=>this:obj n/m=undefined
fn.call(obj,10,20); //=>this:obj n=10 m=20
fn.call(10,20); //=>this:10 n=20 m=undefined
fn.call(); //=>this:window 严格下是undefined
fn.call(null); //=>this:window 严格下是null(第一个参数传递的是null/undefined/不传,非严格模式下this指向window,严格模式下传递的是谁this就是谁,不传this是undefined)
/*
//=>我们的需求是想让FN执行的时候,方法中的THIS指向OBJ
obj.fn(); //=>Uncaught TypeError: obj.fn is not a function
//因为此时obj中并没有fn这个属性
-------解决办法---------
obj.fn = fn;
obj.fn(); //=>this:obj //=>'OBJ'
delete obj.fn;
*/
我们自己基于原生的JS实现内置的call方法
~ function () {
/*
* call:改变函数中的THIS指向
* @params
* context 可以不传递,传递必须是引用类型值(因为后面要给它加$fn的属性)
*/
function call(context) {
//this:sum 也就是当前要操作的这个函数实例
context = context || window;
let args = [], //=>除第一个参数外剩余传递的信息值
result;
for (let i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
context.$fn = this;
result = context.$fn(...args); //=>args=[10,20] ...是ES6中的展开运算符,把数组中的每一项分别的展开传递给函数 //=>context.$fn(10,20)
delete context.$fn;
return result;
}
/* 扩展到内置类的原型上 */
Function.prototype.call = call;
}();
apply方法
和call方法一样,都是把函数执行,并且改变里面的this关键字的,唯一的区别就是传递给函数参数的方式不同
- call是一个个传参
- apply是按照数组传参
let obj={name:'OBJ'};
let fn=function(n,m){
console.log(this.name);
}
//=>让fn方法执行,让方法中的this变为obj,并且传递10/20
fn.call(obj,10,20);
fn.apply(obj,[10,20]);
bind方法
和call/apply一样,也是用来改变函数中的this关键字的,只不过基于bind改变this,当前方法并没有被执行,类似于预先改变this
let obj={name:'OBJ'};
function fn(){
console.log(this.name);
}
document.body.onclick=fn; //=>当事件触发,fn中的this:BODY
//=>点击BODY,让FN中的THIS指向OBJ
//document.body.onclick=fn.call(obj); //=>基于call/apply这样处理,不是把fn绑定给事件,而是把fn执行后的结果绑定给事件
document.body.onclick=function(){
//this:BODY
fn.call(obj);
}
document.body.onclick=fn.bind(obj); //=>bind的好处是:通过bind方法只是预先把fn中的this修改为obj,此时fn并没有执行呢,当点击事件触发才会执行fn(call/apply都是改变this的同时立即把方法执行) =>在IE6~8中不支持bind方法 预先做啥事情的思想被称为“柯理化函数”
ES6的基础语法
1. let / const
ES6中新增的用来创建变量和常量的
let a = 12;
a = 13;
console.log(a); //=>13
const b = 12;
b = 13; //=>Uncaught TypeError: Assignment to constant variable. 基于CONST创建变量,变量存储的值不能被修改(常量)
console.log(b);
let 和 var 的区别
- let 不存在变量提升(当前作用域中,不能在let声明前使用变量)
- 同一个作用域中,let 不允许重复声明
- let解决了typeof的一个暂时性死区问题
- 全局作用域中,使用let声明的变量并没有给window加上对应的属性
- let会存在块作用域(除对象以外的大括号都可被看做块级私有作用域)
2. 箭头函数及THIS问题
ES6中新增了创建函数的方式:“箭头函数”
真实项目中是箭头函数和FUNCTION这种普通函数混合使用
箭头函数简化了创建函数的代码
//=>箭头函数的创建都是函数表达式方式(变量=函数),这种模式下,不存在变量提升,函数只能在创建完成后被执行(也就是创建的代码之后执行)
const fn=([形参])=>{
//函数体 (return)
};
fn([实参]);
//=>形参只有一个,小括号可以不加
const fn=n=>{};
//=>函数体中只有一句话,并且是return xxx的,可以省略大括号和return等
const fn=n=>n*10;
/*
function fn(n){
return function(m){
return m+(++n);
}
}
*/
const fn=n=>m=>m+(++n);
箭头函数中没有ARGUMENTS,但是可以基于剩余运算符获取实参集合,而且ES6中是支持给形参设置默认值的
let obj = {};
let fn = (context = window, ...args) => {
// console.log(arguments);//=>Uncaught ReferenceError: arguments is not defined 箭头函数中没有arguments
// ...args:剩余运算符(把除第一项外的,其它传递的实参信息都存储到ARGS这个数组集合中)
console.log(args);
};
fn(obj, 10, 20, 30); //=>context:obj arg:[10,20,30]
fn(); //=>context:window arg:[]
箭头函数中没有自己的THIS,它里面用到的THIS,都是自己所处上下文中的THIS(真实项目中,一但涉及THIS问题,箭头函数慎用)
window.name = "WINDOW";
let fn = n => {
console.log(this.name);
};
let obj = {
name: 'OBJ',
fn: fn
};
// FN所处的执行上下文中的THIS是WINDOW
fn(10); //=>this:window
fn.call(obj, 10); //=>this:window 不是我们预期的OBJ
document.body.onclick = fn; //=>this:window 不是我们预期的BODY
obj.fn(10); //=>this:window
let obj = {
name: 'OBJ',
fn: function () {
//=>this:obj 普通函数是有自己的THIS的
let f = () => {
console.log(this);
};
f(); //=>this:obj
return f;
}
};
let f = obj.fn();
f(); //=>this:obj
真实项目中的一个应用
let obj = {
name: 'OBJ',
fn: function () {
//=>this:obj
//=>原本期望的需求是:1S后把OBJ中的NAME改为'aaa'
setTimeout(() => {
console.log(this); //=>OBJ
this.name = 'aaa';
}, 1000);
/* setTimeout(function () {
console.log(this);//=>WINODOW
this.name = 'aaa';
}, 1000); */
/* let _this = this;//=>把需要的THIS提前用变量存储起来
setTimeout(function () {
_this.name = 'aaa'; //=>需要使用的时候拿出来用即可
}, 1000); */
}
};
obj.fn();
解构赋值
让左侧出现和右侧值相同的结构,以此快速获取到我们需要的内容