JavaScript面向对象编程指南

150 阅读5分钟

一、基本数据类型以及循环

二、函数

1. 回调函数

  • 定义
  • 示例

2. 变量作用域(使用var时,变量作用域和变量提升的关系?写个体现这种关系的demo)

  1. 闭包函数
  • 实现闭包的集中方式? 将函数内部的变量或者函数直接赋值给一个全局变量; 将函数内部的函数return出去,即将其返回值赋值给一个全局变量。
  • 定义?特点?循环中使用闭包解决了什么问题? 问题思考:
Demo:1
function F() {
    var arr = [], i;
    for (i = 0; i < 3; i++) {
        arr[i] = function () {
            return i;
        };
    }
    return arr;
}
let arr = F();
arr[0]() // 3
arr[2]() // 3
arr[3]() // 3
期望打印是0,1,2,为什么每次都是3 ?

demo:2
如何改进?
  • 示例(包含getter/setter的安全函数怎么实现?迭代器next怎么实现?)

三、对象

(1)对象中 this 定义和使用场景

(2)场景对象的方式

方式1:使用new操作符

  1. 对象中 构造函数 的定义和使用场景,使用构造函数创建对象的好处(能够传参使用,创建多个对象)?
  2. 构造器属性:一个对象的construction函数的定义?使用场景?
  3. instanceof 操作符 ? 通过 instanceof 操作符,我们可以测试一个对象是不是由某个指定的构造器函数所创建的。
> 
> h instanceof Hero;faxdfasdfasdf
true

(3)两个对象比较的问题

let a = {age: 2};
let b = {age: 2};
a === b // false 解释为什么是false

(4)内建对象

  • valueOf()
    valueOf()方法也是为所有对象共有的一个方法。对于简单对象(即以 Object() 为构造器的对象)来说,valueOf()方法所返回的就是对象自己。
var a = {};
a.valueOf() === a // true

(5)Function

每个函数都有call()、apply() 方法等,也有length、constructor、prototype属性

  • 5.1 prototype 属性

每个函数的 prototype 属性中都指向了一个对象;
它只有在该函数是构造器时才会发挥作用;
该函数创建的所有对象都会持有一个该 prototype 属性的引用,并可以将其当做自身的属性来使用。

Demo:
var ninja = {
    name: 'Ninja',
    say: function () {
        return 'I am a ' + this.name;
    }
};

> F.prototype = ninja;
> function F(){}
> var baby_ninja = new F(); 
> baby_ninja.name;  // "Ninja" 
> baby_ninja.say();  // "I am a Ninja" 
  • 5.2 call()apply() 这两个方法还有另外一个功能,它可以让一个对象去“借用”另一个对象的方 法,并为己所用。也就是改变this指向。
Demo:
var some_obj = {
    name: 'Ninja',
    say: function (who) {
        return 'Haya ' + who + ', I am a ' + this.name;
    }
};
> some_obj.say('Dude'); // "Haya Dude, I am a Ninja" 

> var my_obj = {name: 'Scripting guru'}; 
下面期望my_obj调用some_obj的say方法
some_obj.say.call(my_obj, 'jack') // "Haya jack, I am a Scripting guru" 

apply()的工作方式与 call()基本相同,唯一的不同之处在于参数的传递形式。一个传字符串,一个传数组。
理解并举例说明apply()、call()、bind()的区别?

(6)String

String对象的一些方法:

  • indexOf()、includes(),可用于字符的模糊查询。

(7)Date

ES5 还为 Date 构造器新增了 now()方法,以用于返回当前 timestamp。

> Date.now();  // 1606362656105
  1. 计算生日: 计算一个自己2030年的生日(6 月 20 日)是星期几,就可以这样:
> var d = new Date(2030, 5, 20);
> d.getDay(); // 4 生日是星期四
  1. 计算从2020至2030之间周一至周日各多少天
var stats = [0,0,0,0,0,0,0]; 
for (var i = 2016; i < 3016; i++) {
 stats[new Date(i, 5, 20).getDay()]++;
}
[140, 146, 140, 145, 142, 142, 145] // 周一140天,周二146天等等

三、原型

明白此图,你可以不用往下看了。还有更多事情等着你去做~~传送门

(1)函数

每个函数都有call()、apply()等方法,也有length、constructor、prototype等属性。
但是,prototype 一般是用在构造函数中。

function Gadget(name, color) {
    this.name = name;
    this.color = color;
    this.whatAreYou = function () {
        return 'I am a ' + this.color + ' ' + this.name;
    };
} 
Gadget.prototype.price = 100;
> var newtoy = new Gadget('webcam', 'black'); 

// 继续操作
Gadget.prototype.get = function(what) {
 return this[what];
};

// 即便 newtoy 对象在 get()方法定义之前就已经被创建了,但我们
依然可以在该对象中访问新增的方法:
> newtoy.get('color'); 
"black"

(2)对象

每个实例对象都有constructor等属性。返回实例对象的构造函数。

// 语法:
object.constructor

constructor 属性返回对创建此对象的数组函数的引用。

// 情况1:
> let a = {age: 213}
> a.constructor
  ƒ Object() { [native code] }  // 指向最高级Object

// 情况2:
> function J (name) {this.name = name;}
> const p = new J('jack');
> p.constructor
  ƒ J (name) {this.name = name;} // 指向构造函数J
> p.constructor === J
  true

(3)hasOwnProperty

  • hasOwnProperty方法来判断一个属性是自身属性还是原型属性。
    如果遇上对象的自身属性与原型属性同名,则对象自身属性的优先级高于原型属性。
> function N(name) {this.name = name};
> const j = new N('jack');
> N.prototype.name = 'tom';
> N.prototype.age = 12;
> j.name
  'jack'
> j.age
  12

// 通过 hasOwnProperty()方法来判断一个属性是自身属性还是原型属性。
> j.hasOwnProperty('name')
  true
> j.hasOwnProperty('age')
  false
  • 仍然可以使用 hasOwnProperty()属性,判断一个对象的某个原型属性到底是原型链中的哪个原型的属性。 判断 toString 属性来自于哪里?
> toy.hasOwnProperty('toString'); 
  false
> toy.constructor.hasOwnProperty('toString'); 
  false
> Object.prototype.hasOwnProperty('toString'); // 此方法终于找到
  true 

(4)isPrototypeOf()方法

每个对象中都会有一个 isPrototypeOf() 方法,这个方法会告诉我们当前对象是否是另一个对象的原型。

var monkey = {
    hair: true,
    feeds: 'bananas',
    breathes: 'air'
}; 
function Human(name) {
    this.name = name;
}
Human.prototype = monkey;

> var george = new Human('George'); 
> monkey.isPrototypeOf(george); 
  true // “monkey 是 george的原型

需要注意的是,我们在这里是预先知道了 monkey 可能是 george 的原型,才提出了 问题“monkey 是你的原型吗?”,然后获得一个布尔值作为回应。那么,是否能在不知道 某个对象原型是什么的情况下,获得对象的原型呢?
答案是:大多数浏览器可以。因为大 多数浏览器都实现了 ES5Object.getPrototypeOf()方法。

> Object.getPrototypeOf(george) === monkey;
  true

而对于另一部分实现了 ES5 部分功能,却没有实现 getPrototypeOf()方法的浏览 器,我们可以使用特殊属性__proto__

(5)__proto__

因为该属性在 Internet Explorer 之类的浏览器中是不存在的,因此脚本就不能实现跨平台了。
另外,__proto__与 prototype 并不是等价的。__proto__实际上 是某个实例对象的属性,而 prototype 则是属于构造器函数的属性。
具体理解如下图:传送门

注意:__proto__只能在学习或调试的环境下使用。或者如果你的代码碰巧只需 要在符合 ES5 标准的环境中使用的话,你也可以使用 Object.getPrototypeOf()方法。

(6)更好的使用实例对象

  • 枚举对象属性for-in 遍历对象,最好使用for-in

(7)使用原型注意点

在处理原型问题时,我们需要特别注意以下两种行为:

  • 当我们对原型对象执行完全替换时,可能会触发原型链中某种异常(exception)
  • prototype.constructor 属性是不可靠的。 当我们重写某对象的 prototype 时,需要重置相 应的constructor 属性。

四、继承

继承中用到的检测语法:传送门

  • instanceof // 运算符用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个 要检测对象的原型链上
  • isPrototypeOf // 判断的是一个对象是否存在于另一个对象的原型链之中

JS中继承的几种方式:

  • 原型链继承 // 控制构造函数的prototype属性,进而赋值并修改原型