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 ...
随机推荐
- [BZOJ2243][SDOI2011]染色 解题报告|树链剖分
Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“ ...
- [POJ2954&POJ1265]皮克定理的应用两例
皮克定理: 在一个多边形中.用I表示多边形内部的点数,E来表示多边形边上的点数,S表示多边形的面积. 满足:S:=I+E/2-1; 解决这一类题可能运用到的: 求E,一条边(x1,y1,x2,y2)上 ...
- 转:Python网页解析:BeautifulSoup vs lxml.html
转自:http://www.cnblogs.com/rzhang/archive/2011/12/29/python-html-parsing.html Python里常用的网页解析库有Beautif ...
- tmux下vim颜色不正常问题
在解决了tmux下,make menuconfig颜色不正常问题https://www.cnblogs.com/zqb-all/p/9702582.html后,引入了新的问题,vim颜色错乱. 尝试了 ...
- Mac-item+zsh
$brew cask install iterm2 $ sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/m ...
- jquery发送json请求,给springmvc接收
js var obj = { 'name':name, 'desc':desc, 'scheduleStartTime':scheduleStartTime, 'scheduleEndTime':sc ...
- 1.flume概述
我们的web服务器等等每天会产生大量的日志,我们要把这些日志收集起来,移动到hadoop平台上进行分析. 那么如何移动这些数据呢?一种方法是通过shell cp到hadoop集群上,然后通过hdfs ...
- 在Js或者cess后加版本号 防止浏览器缓存
在Js或者cess后加版本号 防止浏览器缓存 客户端浏览器会缓存css或js文件,从而减少加载次数,减少流量,提高网页的访问速度.为了使得每次修改js或者css能生效,可以通过改变版本号来使得客户端浏 ...
- nginx反向代理部署nodejs配置
将域名abc.com反向代理到127.0.0.1:8888 upstream nodejs { server 127.0.0.1:8888; keepalive 64; } server { list ...
- Workman websocket 握手连接
默认的是TCP连接方式,如果需要WebSocket,则需要更改Gateway方式, 服务端协议要和客户端协议一致才能通讯.客户端是websocket协议,服务端也要设置成websocket协议.默认为 ...