JavaScript

2020-05-31  本文已影响0人  hellomyshadow

JavaScript是一种专为网页交互设计的脚本语言,国际标准是ECMAScript
由三部分组成:

函数的三种定义方式:

函数定义的对比.jpg

arguments:函数内部的一个伪数组,保存了所有实参;
arguments.length:实参的个数
函数名.length:形参的个数
arguments.callee:指向函数体。函数名也指向函数体。
函数名的指向可能发生变化,所以在函数内部通常使用 arguments.callee 自我访问;
//判断参数个数
function test(a, b) {
if(arguments.length === arguments.callee.length) {
...
} else {
console.log('实参与形参的个数不匹配!');
}
}
//递归调用
function fack(num) {
if(num <= 1) {
return 1;
} else {
return num * arguments.callee(num-1);
}
}
//然而,在严格模式下,是不允许访问arguments中的属性的!

call()、apply() 真正强大的地方是,能够扩充函数赖以运行的作用域,实现对象与方法解耦合

闭包
使用闭包时,必须严格清楚作用域链
本质上,闭包就是一个函数的返回值也是一个函数,且内函数访问了外函数的变量,外函数的作用域不会释放!
特性:封闭性,类似于静态语言中的 private,起到了保护变量的作用
function maker(a) {
return function() {
return a++;
}
}
var counter = maker(1);
counter(); // 1
counter(); // 2
counter(); // 3

JavaScript模拟类的两种简单方式

var p1 = new Person('Mack', 18);
p1.constructor === Person // true

函数内部的 this 指向其调用者,所以把构造函数当作普通函数调用时,其this将指向window
Person('Mack', 18); //相当于把name='Mack',age=18,say()挂载到window对象上

原型:prototype
每个函数都有一个prototype属性,总是指向一个对象,用于存放所有实例共享的属性和方法;
var p2 = new Person('Any', 20);
p2.say === p1.say // false
这是因为每一次通过 new 创建实例时,都会重新执行一次构造函数Person,生成新的属性和方法体;
原型可以解决实例的共享属性和方法
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log(this.name, this.age);
}
var p1 = new Person('Mack', 18);
var p2 = new Person('Any', 20);
p2.say === p1.say // true

构造函数.prototype === 原型对象
原型对象.constructor === 构造函数(模板)
Function.prototype 也指向一个对象,但 Object.prototype 不是指向对象!

Object.getPrototypeOf():ECMA5新特性,获取实例对象的原型对象
Object.getPrototypeOf(p1) === Person.prototype; // true
Person.prototype.getPrototypeOf(p1); // true

in 操作符与hasOwnProperty():
in 会在对象的原型链上查找,而hasOwnProperty()只会在当前对象中查找
Person.prototype.sex = '男';
'sex' in p1 // true
p1.hasOwnProperty('sex'); // false

Object.keys():ECMA5新特性,获取对象的可枚举的属性(方法)名,不包括原型链上的;
Object.getOwnPropertyNames():与Object.keys()功能相同,但不区分是否可枚举;
Object.keys(p1) // ["name", "age"],也不包括不可枚举的(如)
Object.keys(Person.prototype) // ["say"]
Object.getOwnPropertyNames(Person.prototype) // ["contructor", "say"],contructor属性不可枚举

//简单原型
function Person(){ }
Person.prototype = {
constructor: Person, // 手动添加原型对象的构造器
name: 'Mack',
say: function(){ }
}
这种方式修改了原型对象的指针指向,手动添加的构造器constructor与其他属性没有任何区别,也是可以枚举的!
ECMA5新特性提供了添加原型对象的属性的方法:Object.defineProperty()
Person.prototype = {
name: 'Mack',
say: function(){ }
}
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false, //设置添加的属性constructor 不可枚举
value: Person
});

//原型的动态性:修改原型对象的指针与创建实例的顺序性
function Person(){ }
var p = new Person();
Person.prototype.say = function(){
console.log('say');
}
p.say(); //say 原型指针不变,实例通过原型链可以找到原型对象上的属性/方法
与之对比:
function Person(){ }
var p = new Person();
Person.prototype = {
constructor: Person,
say: function(){ }
}
p.say(); // error 原型指针指向发生了变化,但实例仍在旧的原型链上查找

原型对象中的属性能够被所有实例所共享,但实例不能修改共享属性的指向,否则相当于为自己添加同名属性。
function Person(){ }
Person.prototype.name = 'Mack';
var p1 = new Person();
p1.name = 'Any'; // p1 -> { name: 'Any' }
var p2 = new Person();
console.log(p2.name); // Mack
如果共享属性是引用类型,那么一个实例修改了其内容,所有实例也将修改,这也是原型的最大问题。
原型对象中的属性(方法)被所有构造函数的实例所共享,类似于Java中的static,所以通常使用构造函数+原型的模式来模拟类
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype.work = function(){
console.log(this.job);
}

动态原型模式:把代码都封装到一起
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
//动态原型方法
if(typeof this.work != 'function') {
Person.prototype.work = function() {
console.log(this.job);
}
}
}
//稳妥构造函数式:durable object(稳妥对象)
最适合在安全环境中使用,常用于对安全级别要求很高的程序中
特点:没有公共属性,方法内不能引用this
function Person(name, age) {
var obj = new Object();
//定义私有变量和函数(private),通过方法去访问
var durableName = name;
obj.getName = function() {
return durableName;
}
return obj;
}

隐式原型:proto
prototype 称为显示原型
在创建实例对象时,JS会为其添加一个 proto 属性,指向构造函数的原型对象
实例对象.proto === 构造函数.prototype === 原型对象
函数也有隐式原型,而且所有函数(包括Object和Function)都是Function的实例;
Person/Object/Function.proto === Function.prototype
原型链
显示原型对象中也有隐式原型 proto,指向当前构造函数的父构造函数的原型对象;
原型链由 proto 构成,在访问对象的属性/方式时,会沿着原型链逐层查找
而原型链的尽头就是Object.prototype,因为Object是JS的顶层父类,所有的对象/构造函数,包括Function,都直接或间接派生自Object
Object.prototype.proto === null
p.show() -> Person -> p.proto -> Person.prototype -> Person.prototype.proto -> ... -> Object.prototype
A instanceof B 的判断的标准:如果B的显示原型在A的原型链上,则返回true.

JS的继承
function Sup(name) {
this.name = name;
}
Sup.prototype.show = function() { console.log('Sup show') }

原型继承:让一个类型的原型指向另一个类的实例,就能实现简单的JS继承
function Ted(age) {
this.age = age;
}
Ted.prototype = new Sup('Father');
//1. 此时,Ted的原型对象中将包含一个指向另一个原型的指针
//2. 相应地,另一个原型中也包含指向另一个构造函数Sup的指针,那么Ted的原型对象的构造器也就变成了Sup的构造器
Ted.prototype.constructor // Sup
var t = new Ted(20)
t.name // Father
t.show() // Sup show
// t.proto -> Ted -> Ted.prototype -> new Sup('Father')
//原型继承的特点:即继承了父类的模板,又继承了父类的原型对象
问题在于:父类的属性不能通过 new 子类进行初始化赋值

类继承:借助构造函数的方式继承,只继承模板,不继承原型对象
function Ted(name, age) {
// 绑定父类的构造函数(模板)
Person.call(this, name)
this.age = age;
}
var t = new Ted('Mack', 22)
t.name // Mack
但是,因为没有继承父类Sup的原型链,所以不能访问其原型链上的属性/方法
t.show() // error

混合继承
function Ted(name, age) {
// 1.借助构造函数继承
Sup.call(this, name);
this.age = age;
}
// 2.原型继承
Ted.prototype = new Sup();
// 3. 修正 Ted 原型对象的构造器
Ted.prototype.constructor = Ted;
// 隐式原型构成的原型链:Ted.prototype.proto = new Sup().proto = Sup.prototype

混合继承的缺点:继承了两次父类模板(Sup.call(),new Sup())

如何做到只继承一次父类模板
function extends(ted, sup) {
//参数sup、ted分别表示父类和子类
// 1.创建一个空函数,目的是中转
var F = new Function();
// 2. 空函数的原型对象a指向父类sup的原型对象
F.prototype = sup.prototype;
// 3. 原型继承F,间接继承了父类sup的原型对象
ted.prototype = new F();
// 4. 修正子类ted的构造器指向
ted.prototype.constructor = ted;
// 5. 自定义一个子类ted的静态属性,保存父类sup的原型对象,便于解耦,也便于获取父类的原型对象
ted.super = sup.prototype;
// 6. 加保险:判断父类原型对象中的构造器
if(sup.prototype.constructor == Object.prototype.constructor) {
sup.prototype.constructor = sup;
}
}
function Sup(name, age) {
this.name = name;
this.age = age;
}
Sup.prototype.show = function() { console.log('Sup show') }

function Ted(name, age, sex) {
// 通过自定义的静态属性获取父类的原型对象,进而获取其构造器,实现模板继承
Ted.super.constructor.call(this, name, age);
this.sex = sex;
}

//让 Ted 继承 Sup
extends(Ted, Sup);

// 在子类的原型对象上添加与父类的原型上同名的方法
Ted.prototype.show = function() { console.log('Ted show') }

var t = new Ted('Jack', 22, '男')
t.age // 22
t.show() // Ted show
Ted.super.show() // Sup show

上一篇下一篇

猜你喜欢

热点阅读