(78)Wangdao.com第十五天_JavaScript 面向对象
面向对象编程(Object Oriented Programming,缩写为 OOP)
是目前主流的编程范式。
是单个实物的抽象,
是一个容器,封装了属性(property)和方法(method),属性是对象的状态,方法是对象的行为(完成某种任务)。
将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。
每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。
对象可以复用,通过继承机制还可以定制。
具有灵活、代码可复用、高度模块化等特点,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程(procedural programming),更适合多人合作的大型软件项目。
- 构造函数
“类” 就是对象的模板,对象就是 “类” 的实例。
JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。
- 使用构造函数(constructor)作为对象的模板,描述实例对象的基本结构。。
- 所谓 ”构造函数”,就是专门用来生成实例对象的函数。
- 一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。
- 为了与普通函数区别,构造函数名字的第一个字母通常大写。
- 特点:
- 函数体内部使用了
this
关键字,代表所要生成的实例 - 生成对象的时候,必须使用
new
命令
- 函数体内部使用了
- new 命令
- 通过
new
命令,让构造函数生成一个实例对象 new
命令执行时,构造函数内部的this
,就代表了新生成的实例对象- 使用
new
命令时,根据需要,构造函数也可以接受参数var Vehicle = function (p) {
this.price = p;
}; var v = new Vehicle(500);new
命令本身就可以执行构造函数,所以后面的构造函数可以带括号,也可以不带括号。但是为了表示是函数调用,推荐使用括号var Vehicle = function (){
this.price = 1000;
}; var v = Vehicle(); // 将构造函数当成普通函数调用,不抱错
v // undefined // 但是变量v变成了undefined
price // 1000 // price属性变成了全局变量 // 一个解决办法是:
// 构造函数内部使用严格模式,即第一行加上use strict。
// 这样的话,一旦忘了使用new命令,直接调用构造函数就会报错。function Fubar(foo, bar){
'use strict';
this._foo = foo; // 由于 严格模式中,函数内部的this
不能指向全局对象,默认等于undefined
this._bar = bar;
} Fubar(); // TypeError: Cannot set property '_foo' of undefined // 另一个解决办法:
// 造函数内部判断是否使用new
命令,如果发现没有使用,则直接返回一个实例对象。function Fubar(foo, bar) {
if (!(this instanceof Fubar)) {
return new Fubar(foo, bar);
} this._foo = foo;
this._bar = bar;
}
// 此时不管加不加new
命令,都会得到同样的结果
Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1
- 通过
- new 命令的原理
使用new
命令时,它后面的函数依次执行下面的步骤
1. 创建一个空对象,作为将要返回的对象实例
2. 将这个空对象的隐式原型对象 __proto__,指向构造函数的 prototype 属性
3. 将这个空对象赋值给函数内部的 this 关键字
4. 开始执行构造函数内部的代码
- 如果构造函数内部有 return 语句,而且 return 后面跟着一个对象,new 命令会返回 return 语句指定的对象;否则,就会不管 return 语句,返回 this 对象
- 如果对普通函数(内部没有
this
关键字的函数)使用new
命令,则会返回一个空对象 - 因为
new
命令总是返回一个对象,要么是实例对象,要么是return
语句指定的对象function getMessage() {
return 'this is a message';
} var msg = new getMessage(); msg // {}
typeof msg // "object"
- 如果对普通函数(内部没有
new
命令简化的内部流程,可以用下面的代码表示function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
// 将 arguments 对象转为数组
var args = [].slice.call(arguments); // 取出构造函数
var constructor = args.shift(); // 创建一个空对象,继承构造函数的 prototype 属性
var context = Object.create(constructor.prototype); // 执行构造函数
var result = constructor.apply(context, args); // 如果返回结果是对象,就直接返回,否则返回 context 对象
return (typeof result === 'object' && result != null) ? result : context;
} // 实例
var actor = _new(Person, '张三', 28);
- 如果构造函数内部有 return 语句,而且 return 后面跟着一个对象,new 命令会返回 return 语句指定的对象;否则,就会不管 return 语句,返回 this 对象
- 函数内部可以使用 new.target 属性
- 如果当前函数是
new
命令调用,new.target
指向当前函数,否则为undefined
function f() {
console.log(new.target === f);
} f(); // false //new.target
指向undefined
new f(); // true //new.target
指向 当前函数
- 使用这个属性,可以判断函数调用的时候,是否使用
new
命令function f() {
if (!new.target) {
throw new Error('请使用 new 命令调用!');
}
// ...
} f(); // Uncaught Error: 请使用 new 命令调用!
- 如果当前函数是
- Object.create() 创建实例对象
- 有时拿不到构造函数,只能拿到一个现有的对象
- 可以使用 Object.create() 方法,生成新的实例对象
var Person= {
name: '张三',
age: 38,
greeting: function() {
console.log('Hi! I\'m ' + this.name + '.');
}
}; var person2 = Object.create(Person); person2.name; // 张三
person2.greeting(); // Hi! I'm 张三.
- this 关键字
this除了可以在构造函数中表示实例对象,还可以用在其他场合
但是不管什么场合,this 总是返回一个对象
- this 的指向是可变的
- 只要函数被赋给另一个变量,this 的指向就会变
var A = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
}; var name = '李四'; // window.name
var f = A.describe; // window.f
f(); // "姓名:李四"
- 根据据当前所在的对象不同,this 的指向也不同
function f() {
return '姓名:'+ this.name;
} var A = {
name: '张三',
describe: f
}; var B = {
name: '李四',
describe: f
}; A.describe(); // "姓名:张三"
B.describe(); // "姓名:李四"
- 一个网页编程的例子
<input type="text" name="age" size=3 onChange="validate(this, 18, 99);"> <script>
function validate(obj, lowval, hival){
if ((obj.value < lowval) || (obj.value > hival))
console.log('Invalid Value!');
}
</script>
// 一个文本输入框,每当用户输入一个值,就会调用onChange
回调函数,验证这个值是否在指定范围。
// 浏览器会向回调函数传入当前对象,因此this
就代表传入当前对象(即文本框),然后就可以从obj.value
上面读到用户的输入值JavaScript 支持运行环境动态切换,也就是说,
this
的指向是动态的,没有办法事先确定到底指向哪个对象
- 只要函数被赋给另一个变量,this 的指向就会变
- this 实质
var obj = { foo: 5 };
// 原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。
// 举例来说,上面例子的 foo 属性,实际上是以下面的形式保存的 {
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
- 有 this 的设计,跟内存里面的数据结构有关系
- 由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行
- 由于函数可以在 不同的运行环境(上下文) 执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)
- 所以,this 就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。
var f = function () {
console.log(this.x);
} var x = 1;
var obj = {
ff: f,
x: 2,
}; // 单独执行
f(); // // obj 环境执行
obj.ff(); //
- 几个使用场合下,this 的指向
- 全局环境
- 全局环境使用this,它指的就是顶层对象window
- 不管是不是在函数内部,只要是在全局环境下运行,this 就是指顶层对象window
- 构造函数
- 构造函数中的 this ,指的是实例对象构造函数中的 this ,指的是实例对象
- 方法调用时
- this 指向调用该对象的对象。
- call() 和 apply() 时
- 指向指定的对象
- 全局环境
- 有 this 的设计,跟内存里面的数据结构有关系
- 由于 this 的指向是不确定的,所以切勿在函数中包含多层的 this
- 使用一个变量固定 this 的值,然后内层函数调用这个变量,是非常常见的做法,请务必掌握
var o = {
f1: function () {
console.log(this);
var f2 = function () {
console.log(this);
}();
}
} o.f1();
// Object
// Window- 使用一个变量固定 this 的值,然后内层函数调用这个变量
var o = {
f1: function() {
console.log(this);
var that = this;
var f2 = function() {
console.log(that);
}();
}
} o.f1()
// Object
// Object // 以上代码等价于var temp = function () {
console.log(this);
};
var o = {
f1: function () {
console.log(this);
var f2 = temp();
}
} - 严格模式下,如果函数内部的 this 指向顶层对象,就会报错。
- 由于 this 的指向是不确定的,所以切勿在函数中包含多层的 this
- 避免数组处理方法中的 this
- 数组的
map
和foreach
方法,允许提供一个函数作为参数。这个函数内部不应该使用this
- 如果有
- 使用中间变量固定this
- 将 this 当作 foreach( , this) 方法的第二个参数,固定它的运行环境
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
});
}
} o.f()
// undefined a1
// undefined a2 // 解决方法1 使用变量固定 this
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
var that = this;
this.p.forEach(function (item) {
console.log(that.v+' '+item);
});
}
} o.f()
// hello a1
// hello a2 // 解决方法2 forEach( , this)
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
}, this);
}
} o.f()
// hello a1
// hello a2
- 数组的
- 回调函数中的 this 往往会改变指向,最好避免使用
var o = new Object();
o.f = function () {
console.log(this === o);
} // jQuery 的写法
$('#button').on('click', o.f);上面代码中,点击按钮以后,控制台会显示
false
。原因是此时this
不再指向o
对象,而是指向按钮的 DOM 对象,因为f
方法是在按钮对象的环境中被调用的。
- 避免数组处理方法中的 this
- 固定 this 指向
JavaScript 提供了call
、apply
、bind
这三个方法,来切换/固定this
的指向
- Function.prototype.call()
- 函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数
var obj = {}; var f = function () {
return this;
}; f() === window // true
f.call(obj) === obj // true
- call 方法的参数,应该是一个对象。如果参数为空、null和undefined,则默认传入全局对象
- 如果 call 方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入 call 方法
var f = function () {
return this;
}; f.call(5)
// Number {[[PrimitiveValue]]: 5}
- call 方法还可以接受多个参数 func.call(thisObj, arg1, arg2, ...)
- 后面的参数是函数调用时所需的参数
function add(a, b) {
return a + b;
} add.call(this, 1, 2); //
- 应用 之 调用对象的原生方法
var obj = {};
obj.hasOwnProperty('toString'); // false // 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty('toString'); // true
// hasOwnProperty是obj对象继承的方法,如果这个方法一旦被覆盖,就不会得到正确结果 // 将hasOwnProperty方法的原始定义放到obj对象上执行,这样无论obj上有没有同名方法,都不会影响结果
Object.prototype.hasOwnProperty.call(obj, 'toString'); // false
- 如果 call 方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入 call 方法
- Function.prototype.apply()
- 与 call 方法类似,也是改变 this 指向,然后再调用该函数。
- 唯一的区别就是,它只有 2 个参数,第二个参数接收一个数组作为函数执行时的参数 func.call(thisObj, [arg1, arg2, ...])
- 第一个参数也是 this 所要指向的那个对象,如果设为null或undefined,则等同于指定全局对象
- 很多有趣的应用
- 找出数组最大元素
var a = [10, 2, 4, 15, 9]; // 结合使用apply方法和Math.max方法,就可以返回数组的最大元素
Math.max.apply(null, a); //
- 将数组的空元素变为 undefined
// 利用 Array构造函数 和 apply结合
// 将数组的空元素变成undefined
Array.apply(null, ['a', ,'b']); // [ 'a', undefined, 'b' ]空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined
- 转换类似数组的对象
- 利用数组对象的slice方法,将一个类似数组的对象(比如arguments对象)转为真正的数组
// 前提是
//被处理的对象必须有length属性
//以及相对应的数字键
Array.prototype.slice.apply({0: 1, length: 1}); // [1]
Array.prototype.slice.apply({0: 1}); // []
Array.prototype.slice.apply({0: 1, length: 2}); // [1, undefined]
Array.prototype.slice.apply({length: 1}); // [undefined]
- 绑定回调函数的对象
var o = new Object(); o.f = function () {
console.log(this === o);
} var f = function (){
o.f.apply(o); // 或者 o.f.call(o);
}; // jQuery 的写法
$('#button').on('click', f); //点击按钮以后,控制台将会显示true。
// 由于apply方法(或者call方法)不仅绑定函数执行时所在的对象,还会立即执行函数,因此不得不把绑定语句写在一个函数体内。
// 更简洁的写法是采用下面介绍的bind方法。
- 找出数组最大元素
- Function.prototype.bind()
- 用于将函数体内的 this 绑定到某个对象,然后返回一个新函数
var d = new Date();
d.getTime() // var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object. // 因为getTime方法内部的this,绑定Date对象的实例,赋给变量print以后,内部的this已经不指向Date对象的实例了bind方法可以解决这个问题
var print = d.getTime.bind(d);
print(); //bind 方法将 getTime 方法内部的 this 绑定到 d 对象,这时就可以安全地将这个方法赋值给其他变量了
- bind 方法的参数就是所要绑定 this 的对象
- 下面是一个更清晰的例子
var counter = {
count: 0,
inc: function () {
this.count++;
}
}; var func = counter.inc.bind(counter);
func();
counter.count //counter.inc() 方法被赋值给变量 func 。这时必须用 bind 方法将 inc 内部的 this,绑定到 counter,否则就会出错
- this绑定到其他对象也是可以的
var counter = {
count: 0,
inc: function () {
this.count++;
}
}; var obj = {
count: 100
};
var func = counter.inc.bind(obj);
func();
obj.count //
- bind 还可以接受更多的参数,将这些参数绑定原函数的参数
var add = function (x, y) {
return x * this.m + y * this.n;
} var obj = {
m: 2,
n: 2
}; var newAdd = add.bind(obj, 5);
newAdd(5) //bind() 方法除了绑定 this 对象,还将 add 函数的第一个参数 x 绑定成 5,然后返回一个新函数 newAdd(),这个函数只要再接受一个参数 y 就能运行了
- 如果bind() 方法的第一个参数是 null 或 undefined,等于将 this 绑定到全局对象,函数运行时 this 指向顶层对象(浏览器为window)
function add(x, y) {
return x + y;
} var plus5 = add.bind(null, 5);
plus5(10); //上面代码中,函数 add 内部并没有 this,使用 bind() 方法的主要目的是绑定参数 x,以后每次运行新函数 plus5,就只需要提供另一个参数 y 就够了。
而且因为 add 内部没有 this,所以 bind() 的第一个参数是 null,不过这里如果是其他对象,也没有影响
- bind() 使用注意
- 每一次返回一个新函数
- bind方法每运行一次,就返回一个新函数,这会产生一些问题。
- 比如,监听事件的时候,不能写成下面这样
element.addEventListener('click', o.m.bind(o));
// 上面代码中,click事件绑定bind方法生成的一个匿名函数。 // 这样会导致无法取消绑定,所以,下面的代码是无效的
element.removeEventListener('click', o.m.bind(o)); // 正确写法
var listener = o.m.bind(o);
element.addEventListener('click', listener);
// ...
element.removeEventListener('click', listener);
- 结合回调函数使用
- 回调函数是 JavaScript 最常用的模式之一,
- 但是一个常见的错误是,将包含 this 的方法直接当作回调函数
var counter = {
count: 0,
inc: function () {
'use strict';
this.count++;
}
}; function callIt(callback) {
callback();
}
// 上面代码中,callIt方法会调用回调函数。
// 这时如果直接把counter.inc传入,调用时counter.inc内部的this就会指向全局对象。
callIt(counter.inc.bind(counter)); // 使用bind方法将counter.inc绑定counter以后,就不会有这个问题,this总是指向counter
counter.count //
- 某些数组方法可以接受一个函数当作参数。这些函数内部的
this
指向,很可能也会出错var obj = {
name: '张三',
times: [1, 2, 3],
print: function () {
this.times.forEach(function (n) {
console.log(this.name);
});
}
}; obj.print(); // 没有任何输出
// obj.print内部this.times的this是指向obj的,这个没有问题。 // 但是,forEach方法的回调函数内部的this.name却是指向全局对象,导致没有办法取到值。
obj.print = function () {
this.times.forEach(function (n) {
console.log(this === window);
});
}; obj.print()
// true
// true
// true解决方法
// 通过bind方法绑定this
obj.print = function () {
this.times.forEach(function (n) {
console.log(this.name);
}.bind(this));
}; obj.print()
// 张三
// 张三
// 张三
- 结合call方法使用
- 利用bind方法,可以改写一些 JavaScript 原生方法的使用形式,以数组的slice方法为例
[1, 2, 3].slice(0, 1); // [1]
// 等同于
Array.prototype.slice.call([1, 2, 3], 0, 1); // [1]call方法实质上是调用Function.prototype.call方法,因此上面的表达式可以用bind方法改写
var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1); // [1]var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop); var a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4] pop(a)
a // [1, 2, 3]
- 如果再进一步,将 Function.prototype.call 方法绑定到 Function.prototype.bind 对象,就意味着 bind 的调用形式也可以被改写
function f() {
console.log(this.v);
} var o = { v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)(); //所以
bind
方法就可以直接使用,不需要在函数实例上使用
- 利用bind方法,可以改写一些 JavaScript 原生方法的使用形式,以数组的slice方法为例
- 每一次返回一个新函数
- bind() 使用注意
(78)Wangdao.com第十五天_JavaScript 面向对象的更多相关文章
- (79)Wangdao.com第十五天_JavaScript 对象的继承_prototype原型对象_封装_函数式编程
javascript 内置了许多 function 函数(){...} js 执行首先就会执行自己内置的函数定义 (function Function.function Object) 对象的继承 大 ...
- (77)Wangdao.com第十五天_JavaScript 用于数据交换的文本格式 JSON 对象
JSON 对象 JSON (JavaScript Object Notation 的缩写) 也是一种数据,是 JavaScript 的原生对象,用来处理 JSON 格式数据.它有两个静态方法:JSON ...
- “全栈2019”Java第三十五章:面向对象
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- (76)Wangdao.com第十四天_JavaScript 正则表达式对象 RegExp
RegExp Regular Expression,正则表达式是一种表达 文本模式(字符串结构) 的式子. 常常用来按照“给定模式”匹配文本.比如,正则表达式给出一个 Email 地址的模式, ...
- (92)Wangdao.com_第二十五天_线程机制_H5 Web Workers 分线程任务_事件 Event
浏览器内核 支撑浏览器运行的最核心的程序 IE 浏览器内核 Trident内核,也是俗称的IE内核Chrome 浏览器内核 统称为 Chromium 内核或 ...
- 第十五节 JS面向对象实例及高级
实例:面向对象的选项卡 把面向过程的程序,改写成面向对象的形式 原则:不能有函数套函数,但可以有全局变量 过程: onload —— 改写成 构造函数,其中window.onload的功能是在页面加载 ...
- 学习python第十五天,面向对象
Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的. 面向对象技术简介 类(Class): 用来描述具有相同的属性和方法的对象的集合.它定义了该集 ...
- java 面向对象(十五):面向对象的特征三:多态性
1.多态性的理解:可以理解为一个事物的多种形态.2.何为多态性:对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)举例:Person p = new Man();Object obj ...
- 无废话ExtJs 入门教程十五[员工信息表Demo:AddUser]
无废话ExtJs 入门教程十五[员工信息表Demo:AddUser] extjs技术交流,欢迎加群(201926085) 前面我们共介绍过10种表单组件,这些组件是我们在开发过程中最经常用到的,所以一 ...
随机推荐
- Java IO流操作汇总: inputStream 和 outputStream【转】
我们在进行Android java 开发的时候,经常会遇到各种IO流操作.IO流操作一般分为两类:字符流和字节流.以“Reader”结尾都是字符流,操作的都是字符型的数据:以“Stream”结尾的都是 ...
- [物理学与PDEs]第3章习题3电磁场的矢势在 Lorentz 规范下满足的方程
设 $\phi$ 及 ${\bf A}$ 分别为电磁场的标势及矢势 (见第一章 $\S$ 6). 试证明: 若 $\phi$ 及 ${\bf A}$ 满足条件 $$\bex \phi+\cfrac{1 ...
- 深入浅出UE4网络
UE4中的官方文档结构比较混乱,且有部分错误,不方便学习.笔者试图通过本文,整理出一篇关于UE4网络的文章,方便朋友们对UE4中的网络同步部分的认识,并有进一步理解.如有讲得不清楚明白的地方,还望批评 ...
- python之使用单元测试框架unittest执行自动化测试
Python中有一个自带的单元测试框架是unittest模块,用它来做单元测试,它里面封装好了一些校验返回的结果方法和一些用例执行前的初始化操作. 单元测试框架即一堆工具的集合. 在说unittest ...
- js高级知识---词法分析和AO 链
转载自https://www.cnblogs.com/OceanHeaven/p/4957704.html 上面一篇文章说了js的作用域链,这一节算是对上面的延申,有一个典型的例子,首先看原来的一段代 ...
- pythonのdjango
Django(醤糕) 是基于MTV的框架 安装: pip3 install django 重要性:相当于出门带不带腿 创建工程 django-admin startproject [工程名称] 工程目 ...
- 一些日期的计算方式 PHP
一些日期的计算 某个月内的所有天数: public function getMonthDay ($date) { $stattime = strtotime(date('Ym01',strtotime ...
- JMeter学习笔记01-安装环境
记录关于JMeter学习的内容.方便复习. 1)JMeter运行环境 因为JMeter是用纯java写的.所以运行的时候需要java的运行环境. 去java官网下载,点免费下载或所有Java下载都行, ...
- SqlCommand.Parameters的使用
在c#中执行sql语句时,避免会遇到传参的问题.Parameters就是用来做参数化查询,不然很容易被黑客拿到数据. 一.简介 引用自:https://msdn.microsoft.com/ZH-C ...
- 巧用Win+R
calc 启动计算器 charmap 启动字符映射表 chkdsk Chkdsk磁盘检查 cleanmgr 磁盘清理 clipbrd 剪贴板查看器 cmd CMD命令提示符 dvdplay DVD播放 ...