JS面向对象函数的四种调用模式
函数的四种调用模式
概念
- 在 js 中,无论是函数, 还是方法, 还是事件, 还是构造器,...这些东西的本质都是函数
- 函数, 方法, 事件, 构造器,...只是所处的位置不同
- 这四种模式分别是
- 函数模式
- 方法模式
- 构造器模式
- 上下文模式
函数模式
特征: 简单的函数调用, 函数名前面没有任何引导内容
function foo(){}
var fn = function(){};
...
foo();
fn();
(function(){})();
// 上面的三种都是简单的函数调用
this的含义
- 在函数中 this 表示全局对象
- 在浏览器中 this 表示 window(js 中的 global)
方法模式
特征: 方法一定是依附于一个对象, 将函数赋值给对象的一个属性, 那么就成为方法.就是函数前面必须有引导对象
function foo(){
this.method = function(){};
}
var o = {
method : function(){}
}
this的含义
- 这个依附的对象(引导函数的对象)
注意点
在 arguments 这种伪数组, 或者 [] 数组这样的对象中, 这样调用函数也是方法调用, this 会只指向对象
构造器模式
构造函数在创建对象的时候, 做了些什么
- 使用 new 引导构造函数, 创建了一个实例对象
- 在创建对象的同时, 将this指向这个刚刚创建的对象
- 在构造函数中, 不需要 return , 会默认的 return this
分析:
由于构造函数只是给 this 添加成员, 而方法也可以完成这个操作,对与 this 来说, 构造函数和方法没有本质区别
关于return的补充, 在构造函数中
普通情况, 可以理解为构造函数已经默认进行了 return this, 添加在后面的都不会执行
- 如果手动的添加 return ,就相当于 return this.
- 如果手动的添加 return 基本类型(字符串, 数字, 布尔), 无效, 还是 return this
- 如果手动的添加 return null 或 return undefined, 无效, 还是 return this
特殊情况, return 对象, 最终返回对象
- 手动添加 return 对象类型, 那么原来创建的 this 会被丢掉, 返回 return 后面的对象
上下文模式
概念: 上下文就是环境, 就是自定义this的含义
语法:
- 函数名.apply( 对象, [参数]);
- 这个参数可以是数组, 也可以是伪数组
- 函数名.call( 对象, 参数);
- 多个参数可以通过
,进行隔离
- 多个参数可以通过
描述:
- 函数名表示的是函数本身, 使用函数进行调用的时候,默认this指的是全局变量
- 函数名也可以是方法提供, 使用方法调用的时候, this指的是当前对象
- 使用 apply 或者 call 进行调用后, 无论是函数, 还是方法的 this 指向全部无效了, this 的指向由 apply 或者 call 的第一个参数决定
注意:
- 如果函数或方法中没有this的操作, 那么无论是哪一种函数调用模式, 其实都一样
- 如果是函数调用 foo(), 其实和 foo.apply(window) 类似
- 如果是方法调用 o.method(), 其实和 o.method.apply(o)
无论是 call 还是 apply 在没有后面参数的情况下(函数无参数, 方法无参数), 两者一致
function foo(){
console.log(this); // this => window
}
var obj = {};
foo.apply( obj ); // this => obj
foo.call( obj ); // this => obj
apply 和 call 第一个参数的使用规则
- 如果传入的是一个对象, 就相当于设置该函数中的this为参数
- 如果不传参数, 或者传入 null, undefined 等,那么this就默认是 window
foo();
foo.apply();
foo.apply(null);
foo.apply(undefined);
foo.call();
foo.call(null);
foo.call(undefined);
// 上面都this都指向window
- 如果传入的是基本类型, 那么this指向的就是基本类型的包装类型的引用
- number => Number
- boolean => Boolean
- string => String
除 this 外的其他参数
再使用上下文调用的时候, 原函数(方法)可能会带有参数, 那么要让这些参数在上下文中调用, 就需要这个第二, ( n )个参数来表示
function foo(num){
console.log(num);
}
foo.apply(null, [123]);
// 相当于
foo(123);
应用
上下文调用只是修改this, 但是使用最多的地方是借用函数调用
- 将伪数组转换为数组
- 传统方法
var a = {};
a[0] = 'a';
a[1] = 'b';
a.length = 2;
// 使用数组自带方法 concat();
// 不修改原数组
var arr = [];
var newArr = arr.concat(a);
分析
由于 a 是伪数组, 并不是真正的数组, 不能使用数组的方法, concat 会将 a 作为一个整体 Object 加入数组
apply 方法有一个特性, 可以将数组或者伪数组作为参数foo.apply( obj, 伪数组 ); // IE8 不支持
将 a 作为 apply 的第二个参数
var arr = [];
var newArr = Array.prototype.concat.apply( arr, a);
由上面的数组转换, 我们可以得到结论, 应该涉及到数组操作的方法理论上都应该可以
push, pop, unshift, shift
slice
splice - 让伪数组使用 push 方法
小技巧, 伪数组添加元素var a = {length : 0}; // 设置伪数组的长度
a[ a.length++ ] = 'a';
a[ a.length++ ] = 'b';
// 在给伪数组的元素赋值时, 同时修改伪数组的 length 属性
// a[0] = 'a'; a.length++;
伪数组使用 push 方法
var arr = [];
arr.push.apply( arr, a );
// 利用 apply 可以处理伪数组的特性, 进行传参
// 相当于 arr.push(a[0], a[1])
- 让伪数组使用 slice 方法
数组的 slice 语法- arr.slice( index, endIndex ), 不包含 endIndex
- 如果第二个参数不传参, 那么截取从 index 一直到数组结尾
- slice 方法不会修改原数组
通过 apply 实现伪数组使用 slice 方法
var a = { length : 0 };
a[a.length++] = 'a';
a[a.length++] = 'b';
var arr =[];
var newArr = arr.slice.apply(a ,[0])
获取数组中的最大值
传统方法
var arr = [1,2,3,4,5,6,7,8,9];
var max = arr[0];
for(var i = 0;i < arr.length;i++){
if(max < arr[i]){
max = arr[i]
}
}
使用 apply 借用 Math.max 获取数组中最大值
利用 apply 可以传入参数可以是数组或是伪数组的特性
var arr = [1,2,3,4,5,6,7,8,9];
Math.max.apply(null, arr);
创建对象的几种模式
了解了四种函数调用模式, 我们可以深一步了解创建对象的几种方式, 对象是通过构造函数创建的
- 工厂模式
特点:- 大量重复执行的代码, 解决重复实例化的问题
- 函数创建对象并返回
- 最典型的工厂模式就是
document.createElement() - 无法知道是谁创建了这个实例对象
function createPerson(name, age, gender){
var o = {};
o.name = name;
o.age = age;
o.gender = gender;
return o;
}
- 构造方法
特点:- 解决了重复实例化问题
- 能够知道是谁创建了这个对象(constructor)
- 需要通过 new 运算符穿件对象
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
}
- 寄生式构造函数创建对象
特点:- 外表看起来就是构造犯法, 但本质不是通过构造方法创建对象
- 工厂模式 + 构造函数模式
- 不能确定对象的关系, 不推荐使用
function createPerson(name,age,gender){
var o = {};
o.name = name;
o.age = age;
o.gender = gender;
return o;
}
var p = new createPerson('Bob',19,'male')
- 混合式创建
- 构造函数 + 原型
- 解决了构造函数传参和共享的问题
- 不共享的参数使用构造函数
- 共享的使用原型
- 这种混合模式很好的解决了传参和引用共享的难题
function createPerson(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
}
createPerson.prototype = {
constructor : createPerson,
wife : '高圆圆'
}
- 借用构造函数继承(对象冒充)
特点:- 借用构造函数(对象冒充)只能继承构造函数的成员, 无法继承原型的成员
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
}
function Studner(name,age,gender,course){
// 借用构造函数Person, 创建 Student 对象
Person.call(this,name,age,gender);
this.course = course;
}
var boy = new Student('Bob',19,'male',;Math);
- 寄生式继承
特点:- 原型 + 工厂模式
- 通过临时中转
// 临时中转
function person(o){
function Foo(){};
F.prototype = o;
return new F();
}
// 寄生函数
function create(o){
var fn = person(o);
fn.move = function(){};
fn.eat = function(){};
fn.sleep = function(){};
return fn;
}
var boy = {
name : 'Bob',
age : 19,
famliy : ['father','mother','wife']
}
var boy1 = create(boy);
console.log(boy1.name);
console.log(boy1.family);
// 此时 boy1 有了 boy 的成员
经典例题
例题 1
function Foo(){
getName = function(){ alert(1); };
return this;
}
function getName(){
alert(5);
}
Foo.getName = function(){ alert(2); };
Foo.prototype.getName = function(){ alert(3); };
getName = function(){ alert(4); };
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 4 1
getName(); // 4 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3
分析
- 预解析
- 函数名 Foo 和函数名 getName 声明提升,函数名和函数体绑定
- 执行代码
- 执行 Foo.getName();
- 输出 2
- 执行 getName();
- 此时 getName 是在全局中, 未被修改, 输出4
- Foo().getName();
- 此事 Foo() 只是一个函数, 执行完成后 getName 被重新赋值
- getName 因为被重新赋值为 1, 输出1
- getName()
- 由于 getName 被重新赋值, 所以输出 1
- new Foo.getName();
- Foo.getName 并未被修改
- new 没有起任何作用
- 输出2
- new Foo().getName();
- 构造函数创建了实例对象
- 对象中没有 getName 方法, 要从对象的构造函数中的原型中寻找
- 在 Foo.prototype 中得到 getName 输出为 3
- new new Foo().getName();
- 构造函数创建了实例对象
- 对象中没有 getName 方法, 要从对象的构造函数中的原型中寻找
- new 没有起作用
- 在 Foo.prototype 中得到 getName 输出为 3
例题 2
var length = 10;
function fn() {
console.log( this.length );
}
var obj = {
length: 5,
method: function ( fn ) {
fn();
arguments[ 0 ](); // [ fn, 1, 2, 3, length: 4]
}
};
obj.method( fn, 1, 2, 3 );
分析
- 预解析
- 变量名 length, obj 和 函数名fn 声明提升, 函数名和函数体绑定
- 执行代码
- length = 10, 此时 length 可以看作是 window 下的属性
- obj = {}, 进行赋值
- 执行 obj 中的 method 方法
- 将 函数体 fn 进行传参
- 跳进函数 fn,执行函数 fn(), 函数中的 this 指的是 window 下的 length, 为10
- 明确argument是一个对象, argument[0] 指的是 fn
- 使用对象调用函数, 这里的 this 指的是 argument 这个对象
- 得到 argument.length 为 4
- 最后输出结果为 10 4
例题 3
var o = {
method : function(){
console.log(this);
}
};
o.method();
var f = o.method;
f();
(o.method)();
var obj = {};
(obj.fn = o.method)();
(obj.fn)();
分析
- 预解析
- 变量名 o, f ,obj 声明提升
- 执行函数
- o = {},进行赋值
- 执行o.method方法, 这里的 this 指的是 o 这个对象
- 将 o.method 函数体,赋值给f
- 执行 f(), 这里的 this 指的是window
- (o.method)是一个函数表达式, 和 o.method() 的结果一致
- obj = {}, obj进行赋值
- o.method 的函数体, 赋值给obj.fn, 执行之后, 这里的 this 指的是window
- (obj.fn)是一个函数表达式, 和 obj.fn() 的结果一致, tshi 指向 obj
JS面向对象函数的四种调用模式的更多相关文章
- js高级-函数的四种调用模式
1.对象方法调用模式 方法内部的this指向当前调用者的对象d 定义类 (构造函数) function Dog (dogName){ //创建一个空对象 让空对象==this this.name ...
- 函数的四种调用模式.上下文调用.call.apply
闭包:函数就是一个闭包,一个封闭的作用域; 返回函数,要返回多个函数就用一个对象封装一下, 立即执行函数+return 回调函数 JS动态创建的DOM,不会被搜索引 ...
- js中this的四种调用模式
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...
- javascript中函数的四种调用模式详解
介绍函数四种调用模式前,我们先来了解一下函数和方法的概念,其实函数和方法本质是一样,就是称呼不一样而已.函数:如果一个函数与任何对象关系,就称该函数为函数.方法:如果一个函数作为一个对象属性存在,我们 ...
- JS函数的四种调用模式
函数在js中具有四种身份,分别为函数.方法.构造函数.apply或call调用 函数调用 函数调用模式中this指全局对象(window) var f1 = function() { alert ...
- javascript函数的四种调用模式及其this关键字的区别
方法调用模式: 当一个函数被保存为对象的一个属性时,我们称它为一个方法.当一个方法被调用时,this被绑定到该对象. //方法调用模式 var myObject = { value: 0 , incr ...
- JavaScript (JS) 函数补充 (含arguments、eval()、四种调用模式)
1. 程序异常 ① try-catch语法 测试异常 try-catch语法代码如下: try { 异常代码; try中可以承重异常代码, console.log(“try”) 出现异 ...
- JS高级. 06 缓存、分析解决递归斐波那契数列、jQuery缓存、沙箱、函数的四种调用方式、call和apply修改函数调用方法
缓存 cache 作用就是将一些常用的数据存储起来 提升性能 cdn //-----------------分析解决递归斐波那契数列<script> //定义一个缓存数组,存储已经计算出来 ...
- JavaScript高级之函数的四种调用形式
主要内容 分析函数的四种调用形式 弄清楚函数中this的意义 明确构造函对象的过程 学会使用上下文调用函数 了解函数的调用过程有助于深入学习与分析JavaScript代码. 本文是JavaScript ...
随机推荐
- struts2学习问题(一)
一.struts2 Unknown tag (s:property). 解释:不识别标签 解决:这是sturts2的标签,导入相应的包<%@taglib prefix="s" ...
- SpringMVC异常报406 (Not Acceptable)的解决办法
使用SpsringMVC,使用restEasy调试,controller请求设置如下: @RequestMapping(value="/list",method=RequestMe ...
- 【poj3260-最少找零】多重背包+完全背包
多重背包+完全背包. 买家:多重背包:售货员:完全背包: 开两个数组,分别计算出买家,售货员每个面额的最少张数. 最重要的是上界的处理:上界为maxw*maxw+m(maxw最大面额的纸币). (网上 ...
- codechef September Challenge 2017 Sereja and Commands
———————————————————————————— 这道题维护一下原序列的差分以及操作的差分就可以了 记得倒着差分操作 因为题目保证操作2的l r 小与当前位置 #include<cstd ...
- [BZOJ1026][SCOI2009]windy数 解题报告|数位dp
Description windy定义了一种windy数.不含前导零且相邻两个数字之差至少为2的正整数被称为windy数. windy想知道,在A和B之间,包括A和B,总共有多少个windy数? 一直 ...
- python学习笔记 可变参数关键字参数**kw相关学习
在Python中可以定义可变参数,顾名思义,可变参数就是传入参数是可变的.可以是任意个,以一个简单的数学编程为例,计算 sum = a * a + b * b + .....z * z 函数定义可以如 ...
- 【MT8382/8121】使用绝对路径编译模块会导致recourse_overlay无法应用的问题
之前为了方便mm模块编译,写了个脚本,实现了在任意模块其子目录下执行脚本即可编译的功能. 其实原理就是一层一层目录地往上寻找Android.mk文件,找到存放Android.mk目录后,就把该目录当作 ...
- ubuntu 18.04下安装编译的KMS,依赖库
libboost-system1.65.1 libglib2.0-0 libgstreamer-plugins-base1.0-0 libgstreamer1.0-0 libnice10 libsig ...
- [xunsearch] 在thinkphp中使用xunsearch
file: XunSearchController.class.php <?php namespace Home\Controller; include '/opt/xunsearch/sdk/ ...
- php获取rl完整地址
/** * 获取url完整地址 * @author 梁景 * @date 2017-04-27 * @return */ function getUrlInfor() { $sys_protocal ...