JS高级部分

简介: JS高级部分

JavaScript高级

1.面向对象

0.什么是面向对象

1.ECMAScript有如下两种开发模式:

①面向过程 == 面向过程编程 == POP == Procedure Oriented Programming。

②面向对象 == 面向对象编程 == OOP == Object Oriented Programming。

2.面向过程与面向对象的区别:

比如有一天你想吃鱼香肉丝了,怎么办呢?你有两个选择,

①自己买材料,肉,鱼香肉丝调料,蒜苔,胡萝卜等等然后切菜切肉,开炒,盛到盘子里。【面向过程】

②去饭店,张开嘴:老板!来一份鱼香肉丝! 【面向对象】

再比如

【面向过程】
<button type="button" onclick="javascript:alert('你好,');alert('朋友!');">点一下</button>
【面向对象】
<button type="button" onclick="javascript:firstFunc()">点一下</button>
<script type="text/javascript">
    function firstFunc() {
      alert('你好,');
      alert('朋友!');
  }
</script>

3.面向对象的语言有一个标志,那就是类的概念,类是对象的抽象,对象是类的具体。

例如,人是一个总称,也就是人类,而张三李四王五就是一个具体的人的对象,对象的组成包含了属性和方法。

通过类可以创建任意多个具有相同属性和方法的对象,比如在 Java 中创建一个对象:

Random random = new Random();// 调用Random类的构造函数创建一个Random对象

但是,ECMAScript6之前没有类的概念,因此它的对象与基于类的语言中的对象有所不同。

var obj={}; //1.利用字面量创建对象
var obj2 = new Object(); //2.利用new Object创建对象
var obj3 = new 构造函数名(); //3.利用构造函数创建对象

1.创建对象分析

1.创建一个对象,然后给这个对象新建属性和方法。

var box = new Object();     //创建一个Object对象
box.name = 'Lee';      //创建一个name属性并赋值
box.age = 100;        //创建一个age属性并赋值
box.run = function () {     //创建一个run()方法并返回值
    return this.name + this.age + '运行中...';
};
console.log(box.run());     //输出属性和方法的值  Lee100运行中...

2.上面创建了一个对象,并且创建属性和方法,在run()方法里的this,就是代表box对象本身。这种是JavaScript创建对象最基本的方法,但有个缺点,想创建一个类似的对象,就会产生大量的代码。

/*尝试简单一点方式,发现行不通*/
var box2 = box;          //得到box的引用
box2.name = 'Jack';      //直接改变了name属性
console.log(box2.run());       //用box.run()发现name也改变了
/*按照box的方式又重新来了一遍,产生大量的相似代码*/
var box2 = new Object();
box2.name = 'Jack';
box2.age = 200;
box2.run = function () {
    return this.name + this.age + '运行中...';
};
console.log(box2.run());    //这样才避免和box混淆,从而保持独立。

3.为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复代码的问题。

function createObject(name, age) {  //集中实例化的函数
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.run = function () {
        return this.name + this.age + '运行中...';
    };
    return obj;
}
var box1 = createObject('Lee', 100);  //第一个实例
var box2 = createObject('Jack', 200); //第二个实例
console.log(box1.run());
console.log(box2.run());    //保持独立

4.工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,它没办法解决,因为根本无法搞清楚它们到底是哪个对象实例。

console.log(typeof box1);    //Object
console.log(box1 instanceof Object);  //true

5.为了解决对象识别的问题,ECMAScript中可以采用构造函数(构造方法)来创建特定的对象。

function Box(name, age) {    //构造函数模式
    this.name = name;
    this.age = age;
    this.run = function () {
        return this.name + this.age + '运行中...';
    };
}
var box1 = new Box('Lee', 100);   //new Box()即可
var box2 = new Box('Jack', 200);
console.log(box1.run());
console.log(typeof box1);    //Object
console.log(box1 instanceof Object);    //true
console.log(box1 instanceof Box);  //true,很清晰的识别它从属于Box。

6.使用构造函数的方式,既解决了重复实例化的问题,又解决了对象识别的问题,但这里并没有new Object(),为什么可以实例化Box(),这个是哪里来的呢?

我们先比较一下,构造函数的方式和工厂模式的方式的不同之处:

1.构造函数的方式没有显示的创建对象(new Object());
2.构造函数的方式直接将属性和方法赋值给this对象;
3.构造函数的方式没有return语句。

构造函数的方式有一些规范:

1.函数名和实例化构造名相同且大写,(PS:非强制,但这么写有助于区分构造函数和普通函数);
2.通过构造函数创建对象,必须使用new运算符。

既然通过构造函数可以创建对象,那么这个对象是哪里来的,new Object()在什么地方执行了?执行的过程如下:

1.当使用了构造函数时,即new 构造函数()时,后台就会自动执行new Object(),创建出一个新对象;
2.然后将构造函数的作用域(即函数体的范围)给新对象,此时函数体内的this就代表这个新对象;
3.执行构造函数内的代码;
4.返回新对象(后台直接返回)。

7.关于this的使用,this其实就是代表当前作用域对象的引用。如果在全局范围this就代表window对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。

var box = 2;
console.log(this.box);      //全局,代表window

8.构造函数和普通函数的唯一区别,就是他们调用的方式不同。构造函数也是函数,必须用new运算符来调用,否则无效(指的是无法创建对象)。

var box = new Box('Lee', 100);      //构造模式调用
console.log(box.run());
console.log('Lee', 20);         //普通模式调用,无效      

9.探讨构造函数内部的方法(或函数)的问题,首先看下两个实例化后的对象的属性和方法是否相等。

var box1 = new Box('Lee', 100);   //传递一致
var box2 = new Box('Lee', 100);   //同上
alert(box1.name == box2.name);    //true,属性的值相等。
alert(box1.run == box2.run);    //false,方法其实也是一种引用地址。
alert(box1.run() == box2.run());  //true,方法的值相等,因为传参一致。

10.可以把构造函数里的方法(或函数)用new Function()方法来代替,得到一样的效果,更加证明,他们最终判断的是引用地址,唯一性。

function Box(name, age) {    //new Function()唯一性
    this.name = name;
    this.age = age;
    this.run = new Function("return this.name + this.age + '运行中...'");
}

11.我们可以通过构造函数外面绑定同一个函数的方法来保证引用地址的一致性。

function Box(name, age) {
    this.name = name;
    this.age = age;
    this.run = run;
}
function run() {      //通过外面调用,保证引用地址一致。
    return this.name + this.age + '运行中...';
}

12.使用了全局的函数run()来解决了保证引用地址一致的问题。但需要注意的是,全局函数run中的this,在对象调用的时候是Box本身,而当作普通函数调用的时候,this又代表window。


function Box(name, age) {
    this.name = name;
    this.age = age;
    this.run = run;
}
function run() {       //通过外面调用,保证引用地址一致。
    console.log(this);
    return this.name + this.age + '运行中...';
}
var box1 = new Box('Lee', 100); 
box1.run();                           //run函数中打印Box对象
run();                                //run函数中打印Window对象

2.原型

1.认识原型

原型 == 原型对象 == 对象原型。

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,该对象中包含了特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype是通过调用构造函数而创建的那个对象的原型对象。使用原型的好处是,可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。


// 1.Box构造函数
function Box() {}         //声明一个构造函数
/* 2.Box构造函数的prototype属性(即原型对象)*/
Box.prototype.name = 'Lee';       //在原型里添加属性
Box.prototype.age = 100;      
Box.prototype.run = function () {  //在原型里添加方法
    return this.name + this.age + '运行中...';
};

比较一下原型内的方法地址是否一致:

/* 3.通过Box构造函数 创建的 实例对象 */
var box1 = new Box();
var box2 = new Box();
console.log(box1.run == box2.run);  //true,方法的引用地址保持一致。

2.使用原型

0.使用原型模式创建对象:

// 1.Person构造函数
function Person() {}
/* 2.Person构造函数的prototype属性(即原型对象)*/
Person.prototype.name = 'Lee'; //在原型里添加属性name
Person.prototype.age = 100;    //在原型里添加属性age  
Person.prototype.run = function() { //在原型里添加方法run
    return this.name + this.age + '运行中...';
};
/* 3.通过Person构造函数 创建的 实例对象 */
var obj1 = new Person(); //通过Person构造函数 创建 obj1实例对象
var obj2 = new Person(); //通过Person构造函数 创建 obj2实例对象

1.每个构造函数有一个默认属性prototype,该属性返回一个对象,称为原型对象。

console.log(Person.prototype);

2.每个原型对象有一个默认属性constructor,该属性指向原型对象对应的那个构造函数。

console.log(Person.prototype.constructor == Person); //true

3.每个实例对象有一个默认属性__proto__,该属性指向创建它的那个构造函数的prototype属性(即原型对象)。

console.log(obj1.__proto__ == Person.prototype); //true
console.log(obj2.__proto__ == Person.prototype); //true

4.原型对象有一个默认属性__proto__,该属性指向Object构造函数的prototype属性(即Object原型对象)。

console.log(Person.prototype.__proto__ == Object.prototype); //true

5.Object原型对象有一个默认属性__proto__,该属性指向null。

console.log(Object.prototype.__proto__ == null); //true

6.所有函数都是Function构造函数创建出来的对象 。比如下图中的Object构造函数、Person构造函数。

因此,Object构造函数 的__proto__属性 指向 Function构造函数的prototype属性(即Function原型对象)。

因此,Person构造函数 的__proto__属性 指向 Function构造函数的prototype属性(即Function原型对象)。

// Object构造函数 的__proto__属性
console.log(Object.__proto__ == Function.prototype); //true
// Person构造函数 的__proto__属性
console.log(Person.__proto__ == Function.prototype); //true

7.所有对象都是Object的实例 ,即可以认为所有的对象都是由Object构造函数创建的。比如下图中的Function原型对象、Person原型对象。原型对象也是一个对象,所以它也有一个__proto__属性。

因此,Function原型对象 的__proto__属性 指向 Object构造函数的prototype属性(即Object原型对象)。

因此,Person原型对象 的__proto__属性 指向 Object构造函数的prototype属性(即Object原型对象)。

/* Function原型对象 的__proto__属性 */
console.log(Function.prototype.__proto__ == Object.prototype); //true
var obj3 = new Function();
console.log(obj3.__proto__.__proto__ == Object.prototype); //true
/* Person原型对象 的__proto__属性 */
console.log(Person.prototype.__proto__ == Object.prototype); //true
console.log(obj1.__proto__.__proto__ == Object.prototype); //true

3.原型链

原型模式的执行流程:

1.先查找构造函数实例(对象)里的属性或方法,如果有,立刻返回;
2.如果构造函数实例(对象)里没有,则去它的原型对象里找,如果有,就返回。
总结以上两点,也就是说,当试图得到一个对象的属性或方法时,如果这个对象本身不存在这个属性,那么就会去该对象的__proto__属性(也就是该对象的构造函数的prototype属性,也就是原型对象)中寻找。

案例1:如下案例,先看obj1对象本身有没有say方法,有则调用自己本身的方法,没有则去原型对象中寻找。

/* 1.Person构造函数 */
function Person(myName, myAge) {
    this.name = myName;
    this.age = myAge;
    this.say = function () {
        console.log(111);
    }
}
/* 2.Person构造函数的prototype属性(即Person原型对象)*/
Person.prototype = { //使用字面量的方式重写原型,会覆盖之前的原型。
    constructor:Person, //原型对象的属性constructor,指向原型对象对应的那个构造函数。 
    name: "xiaoMing",
    age: 20,
    say: function () {
        console.log(222);
    }
}
/* 3.通过Person构造函数 创建的 实例对象 */
var obj1 = new Person("liLei", 18);
console.log(obj1);
console.log(obj1.name); //liLei(倘若把上面的Person构造函数中的name属性注释掉,此处将打印xiaoMing)
console.log(obj1.age); //18(倘若把上面的Person构造函数中的name属性注释掉,此处将打印20)
obj1.say();  //111(倘若把上面的Person构造函数中的say方法注释掉,此处将打印222)

案例2:如下案例,先看fn对象本身有没有say方法,有则调用自己本身的方法,没有则去原型对象中寻找。。。

/* 1.Foo构造函数 */
function Foo(name, age) {
    this.name = name;
    this.age = age;
}
/* 2.Foo构造函数的prototype属性(即Foo原型对象)*/
// Foo.prototype.toString = function() {
//  console.log("我是Foo原型对象");
// }
/* 3.Object构造函数prototype属性(即Object原型对象)*/
Object.prototype.toString = function() {
    console.log("I'm " + this.name + " And I'm " + this.age); //this代表调用该函数的那个对象。
}
/* 4.通过Foo构造函数 创建的 实例对象 */
var fn = new Foo('小明', 19);
fn.toString(); //I'm 小明 And I'm 19 (倘若把上面的Foo原型对象注释放开,此处将打印"我是Foo原型对象")
console.log(fn.__proto__ === Foo.prototype); //true (见上面2中3)
console.log(Foo.prototype.__proto__ === Object.prototype) //true (见上面2中4)
console.log(Object.prototype.__proto__ === null) //true (见上面2中5)
console.log(fn.toString === Foo.prototype.__proto__.toString); //true (原型模式的执行流程)
/*************************************不能重写Object原型**********************************/
/* 1.在Object原型对象中添加say方法,可行。 */
Object.prototype.say = function() {
    console.log(111); //this代表调用该函数的那个对象。
}
fn.say();
/* 2.重写Object原型,不可行。 */
Object.prototype = { //使用字面量的方式重写原型,会覆盖之前的原型。
    constructor: Object, //原型对象的属性constructor,指向原型对象对应的那个构造函数。 
    say: function() {
        console.log(222);
    }
}
fn.say(); //UncaughtTypeError: fn.say is not a function

首先,fn的构造函数是Foo()。所以:fn.proto === Foo.prototype。又因为Foo.prototype是一个普通的对象,它的构造函数是Object,所以:Foo.prototype.proto === Object.prototype。

通过上面的代码,我们知道这个toString()方法是在Object.prototype里面的,当调用这个对象的本身并不存在的方法时,它会一层一层地往上去找,一直到null为止。

所以当fn调用toString()时,JavaScript发现fn中没有这个方法,于是它就去Foo.prototype中去找,发现还是没有这个方法,然后就去Object.prototype中去找,找到了,就调用Object.prototype中的toString()方法。

这就是原型链,fn能够调用Object.prototype中的方法正是因为存在原型链的机制。

4.原型分析

0.使用原型模式创建对象:

// 1.Box构造函数
function Box() {} //声明一个构造函数
/* 2.Box构造函数的prototype属性(即原型对象)*/
Box.prototype.name = 'Lee'; //在原型里添加属性
Box.prototype.age = 100;
Box.prototype.run = function() { //在原型里添加方法
    return this.name + this.age + '运行中...';
};
/* 3.通过Box构造函数 创建的 实例对象 */
var box1 = new Box();
var box2 = new Box();

1.判断一个对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试。

console.log(Box.prototype.isPrototypeOf(box1)); //true,只要实例化对象,就都会指向。

2.虽然我们可以通过对象实例,访问保存在原型中的值,但却不能通过对象实例直接修改原型中的值。

var box1 = new Box();
console.log(box1.name); //Lee,原型里的值。
box1.name = 'Jack'; //此行是针对构造函数进行的操作,而非原型。
console.log(box1.name); //Jack,就近原则。
var box2 = new Box();
console.log(box2.name); //Lee,原型里的值,没有被box1修改。
// 可通过如下两种方式修改原型中的值:
// 方式1:box1.__proto__.name = 'Jack';
// 方式2:Box.prototype.name = 'Jack';

3.如果想要box1也能在后面继续访问到原型里的值,可以把构造函数里的属性删除即可,具体如下:

console.log(box1);
delete box1.name; //删除属性
console.log(box1);
console.log(box1.name); //Lee,原型里的值。

4.使用hasOwnProperty()函数,判断构造函数的实例里是否包含给定属性,包含返回true,否则返回false。

console.log(box1.hasOwnProperty('name')); //false

5.使用in操作符判断对象是否包含给定属性,包含返回true,否则返回false。但无法确定该属性,是存在于构造函数的实例中还是原型中。

console.log('name' in box1); //true,存在实例中或原型中。

6.结合以上4、5两种方法,可以判断原型中是否存在给定属性。

function isProperty(object, property) { //判断原型中是否存在属性
    return !object.hasOwnProperty(property) && (property in object);
}
console.log(isProperty(box1, 'name')) //true,如果原型有。

7.为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,使用字面量的方式重写原型:

function Box() {};
Box.prototype = { //使用字面量的方式重写原型
    name: 'Lee',
    age: 100,
    run: function() {
        return this.name + this.age + '运行中...';
    }
};

8.使用构造函数方式重写原型对象和使用字面量方式重写原型对象,在使用上基本相同,但还是有一些区别。使用构造函数方式重写的原型对象的constructor属性,仍然指向构造函数实例(对象);使用字面量方式重写的原型对象的constructor属性,指向Object。

var box3 = new Box();
console.log(box3.__proto__.constructor == Box); //false
console.log(box3.__proto__.constructor == Object); //true

9.如果想让使用字面量方式重写的原型对象的constructor属性,指向构造函数实例对象,那么可以这么做:

Box.prototype = {
    constructor: Box, //直接强制指向即可
};
console.log(box3.__proto__.constructor == Box); //true
console.log(box3.__proto__.constructor == Object); //false

PS:使用字面量方式重写的原型对象,为什么它的constructor属性会指向Object?我们说,每创建一个函数,就会同时创建它的prototype,这个原型对象也会自动获取constructor属性。因而当写出function Box() {};之后,原型对象的constructor属性,是指向构造函数实例(对象)的。而Box.prototype={};这种写法其实就是创建了一个新的原型对象,这个新的原型对象覆盖了Box原来的原型对象,又因为这个新的原型对象没有指定构造函数(即没有设置constructor属性),那么就默认为Object(即constructor属性值为Object)。

10.原型可以被多次重写,后面重写的原型会覆盖之前的原型。

function Box() {};
Box.prototype = { //原型被第一次重写了
    constructor: Box,
    name: 'Lee',
    age: 100,
    run: function() {
        return this.name + this.age + '运行中...';
    }
};
Box.prototype = { //原型被第二次重写了
    age: 200
};
var box4 = new Box();
console.log(box4.run()); //UncaughtTypeError: box4.run is not a function

11.原型对象不仅仅可以在自定义对象的情况下使用,ECMAScript内置的引用类型都可以使用这种方式,并且内置的引用类型本身也使用了原型。

引用类型统称为object类型,细分的话有:
1.Object类型;
2.自定义对象类型,如之前写的Person类型、Foo类型、Box类型等;
3.内置对象类型,如Array类型、Function类型、String类型、Number类型、Date类型、RegExp类型等。
console.log(Array.prototype.sort); //sort就是Array类型的原型方法
console.log(String.prototype.substring); //substring就是String类型的原型方法
String.prototype.addstring = function() { //给String类型添加一个方法
    return this + ',被添加了!'; //this代表调用的字符串
};
console.log('Lee'.addstring()); //使用这个方法

PS:尽管给原生的内置引用类型添加方法,使用起来特别方便,但我们不推荐使用这种方法。因为它可能会导致命名冲突,不利于代码维护。

12.原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题:

function Box() {};
Box.prototype = {
    constructor: Box,
    name: 'Lee',
    age: 100,
    family: ['父亲', '母亲', '妹妹'], //添加了一个数组属性
    run: function() {
        return this.name + this.age + this.family;
    }
};
var box1 = new Box();
box1.family.push('哥哥'); //在实例中添加'哥哥'
console.log(box1.run());
var box2 = new Box();
console.log(box2.run()); //共享带来的麻烦,也有'哥哥'了。

PS:数据共享的缘故,导致很多开发者放弃使用原型,因为每次实例化出的数据需要保留自己的特性,而不能共享。

13.为了解决构造传参和共享问题,可以使用“构造函数+原型模式”组合:

function Box(name, age) { //不共享的使用构造函数
    this.name = name;
    this.age = age;
    this.family = ['父亲', '母亲', '妹妹'];
};
Box.prototype = { //共享的使用原型模式
    constructor: Box, //直接强制指向即可
    run: function() {
        return this.name + this.age + this.family;
    }
};

PS:这种混合模式很好的解决了传参和引用共享的大难题。是创建对象比较好的方法。

14.原型模式,不管你是否调用了原型中的共享方法,它都会初始化原型中的方法,并且在声明一个对象时,“构造函数+原型部分”让人感觉又很怪异,最好就是把构造函数和原型封装到一起。为了解决这个问题,我们可以使用动态原型模式。

function Box(name, age) { //将所有信息封装到函数体内
    this.name = name;
    this.age = age;
    if (typeof this.run != 'function') { //仅在第一次调用的时候初始化
        Box.prototype.run = function() {
            return this.name + this.age + '运行中...';
        };
    }
}
var box = new Box('Lee', 100);
console.log(box.run());

当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会再初始化了。这样既得到了封装,又实现了原型方法共享,并且属性都保持独立。

function Box(name, age) { //将所有信息封装到函数体内
    this.name = name;
    this.age = age;
    if (typeof this.run != 'function') { //仅在第一次调用的时候初始化
        alert('第一次初始化'); //测试用
        Box.prototype.run = function() {
            return this.name + this.age + '运行中...';
        };
        // 注意,不可以使用下面这种方式。
        // Box.prototype = { //使用字面量的方式重写原型
        //  constructor: Box, //直接强制指向即可
        //  run: function() {
        //    return this.name + this.age + '运行中...';
        //  }
        // };
    }
}
var box1 = new Box('Lee', 100); //第一次创建对象
alert(box1.run()); //第一次调用
alert(box1.run()); //第二次调用
var box2 = new Box('Jack', 200); //第二次创建对象
alert(box2.run());
alert(box2.run());

PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断构造函数实例和新原型之间的联系。

*15. 在JS里面,有一种类似工厂模式的定义对象方法——寄生构造函数模式, 寄生模式比工厂模式要怪异的地方是其使用new操作符来定义新对象并把包装函数叫做构造函数,其他和工厂模式定义一模一样。如下所示:

function Box(name, age) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.run = function() {
        return this.name + this.age + '运行中...';
    };
    return obj;
}
var box = new Box('Lee', 100); //调用寄生构造函数创建对象
console.log(box.run());

寄生构造函数模式返回的对象,与构造函数或构造函数的原型属性之间没有关系。也就是说,构造函数返回的对象与在构造函数外部创建 对象没有什么不同。为此,不能依赖instranceof操作符来确定对象类型。由于 存在 上述问题,我们建议在可以使用其他模式的情况下,不要使用这种模式。

console.log(typeof box); //object
console.log(box instanceof Box); //false
console.log(box instanceof Object); //true

那么在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类型。由于之前说明不建议直接String.prototype.addstring,可以通过寄生构造的方式添加。

function MyString(string) {
    var str = new String(string);
    str.addstring = function() {
        return this + ',被添加了!';
    };
    return str;
}
var box = new myString('Lee'); //比直接在引用原型添加要繁琐好多
console.log(box.addstring()); //Lee,被添加了!
console.log(typeof box); //object
console.log(box instanceof MyString); //false
console.log(box instanceof Object); //true
console.log(box instanceof String); //true

再比如, 我们定义一个Array的引用类型,并且初始化:

var colors = new Array("red", "blue", "yellow");
console.log(colors); //red,blue,yellow

有时候我们并不想数组输出元素之间用“,”分割,于是我们采用join()方法。但是每定义一个引用类型都使用一次join()方法有点麻烦,那么解决这个问题的办法,就是直接改变Array构造函数默认定义的输出方式,类似Object、Array、Date等等的拥有原生构造函数的引用类型,并不能直接修改其原生构造函数,那么此时寄生构造函数就派上用场了。于是我们可以定义如下一个特殊的Array引用类型:

function MyArray() {
    var arr = new Array(); //创建数组
    arr.push.apply(arr, arguments); //添加值
    arr.toPipedString = function() { //添加方法
        return this.join('|');
    };
    return arr; //返回数组
}
var colors = new MyArray('red', 'blue', 'green'); //调用寄生构造函数创建对象
console.log(colors.toPipedString()); //red|blue|green;

但其实,我们用工厂模式也能实现这个目标:

function myArray() {
    var arr = new Array(); //创建数组
    arr.push.apply(arr, arguments); //添加值
    arr.toPipedString = function() { //添加方法
        return this.join('|');
    };
    return arr; //返回数组
}
var colors = myArray('red', 'blue', 'green'); //调用函数创建对象
console.log(colors.toPipedString()); //red|blue|green;

*16.在JS里面,有一种类似寄生构造函数模式的定义对象方法—— 稳妥构造函数模式。为什么叫做稳妥呢,指的是没有公共的属性,而且其方法也不引用this的对象。 稳妥对象最适合在一些安全的环境中使用,这些环境中会禁止使用this和new。它与寄生构造函数遵循类似的模式,但是有两点不同,一是新创建的实例的方法不引用this,二是不使用new操作符调用构造函数。如下例子:

function Box(name, age) {
    var obj = new Object();
    obj.run = function() {
        return name + age + '运行中...'; //直接打印参数即可
    };
    return obj;
}
var box = Box('Lee', 100); //直接调用函数
console.log(box.run()); //Lee100运行中...

再比如,下面这个稳妥构造函数模式:

function Person(name, age, job) {
    var obj = new Object()
    obj.sayName = function() {
        console.log(name);
    }
    return obj
}

可以看到,在这种模式创建的对象中,只可以使用sayName方法,没有其他办法访问name的值,稳妥构造函数的使用方式如下:

var f = Person("bob", 10, "Programmer");
f.sayName();

这样除了调用sayName()方法,没有别的方式可以访问其数据成员,即使有其他代码给这个对象添加方法或者数据成员,但也不可能有别的方法访问传到构造函数中的原始数据。


相关文章
|
7月前
|
前端开发 JavaScript 开发者
深入理解JavaScript:从基础到高级应用
深入理解JavaScript:从基础到高级应用
116 0
|
7月前
|
JavaScript 前端开发 Java
掌握 JavaScript:从初学者到高级开发者的完整指南(一)
掌握 JavaScript:从初学者到高级开发者的完整指南(一)
|
7月前
|
JSON JavaScript 前端开发
掌握 JavaScript:从初学者到高级开发者的完整指南之JavaScript对象(二)
掌握 JavaScript:从初学者到高级开发者的完整指南之JavaScript对象(二)
|
7月前
|
JavaScript 前端开发 索引
JavaScript字符串检查:从基础到高级
【2月更文挑战第26天】
76 0
JavaScript字符串检查:从基础到高级
|
6月前
|
前端开发 JavaScript 安全
高级前端开发需要知道的 25 个 JavaScript 单行代码
1. 不使用临时变量来交换变量的值 2. 对象解构,让数据访问更便捷 3. 浅克隆对象 4. 合并对象 5. 清理数组 6. 将 NodeList 转换为数组 7. 检查数组是否满足指定条件 8. 将文本复制到剪贴板 9. 删除数组重复项 10. 取两个数组的交集 11. 求数组元素的总和 12. 根据指定条件判断,是否给对象的属性赋值 13. 使用变量作为对象的键 14. 离线状态检查器 15. 离开页面弹出确认对话框 16. 对象数组,根据对象的某个key求对应值的总和 17. 将 url 问号后面的查询字符串转为对象 18. 将秒数转换为时间格式的字符串 19.
64 3
高级前端开发需要知道的 25 个 JavaScript 单行代码
|
7月前
|
JavaScript 前端开发
web前端JS高阶面试题(1),高级开发工程师面试
web前端JS高阶面试题(1),高级开发工程师面试
|
7月前
|
JavaScript 前端开发
JavaScript高级主题:什么是 ES6 的解构赋值?
【4月更文挑战第13天】ES6的解构赋值语法简化了从数组和对象中提取值的过程,提高代码可读性。例如,可以从数组`[1, 2, 3]`中分别赋值给`a`, `b`, `c`,或者从对象`{x: 1, y: 2, z: 3}`中提取属性值给同名变量。
38 6
|
7月前
|
存储 JavaScript 前端开发
JavaScript高级主题:JavaScript 中的 Map 和 Set 是什么?它们有什么区别?
JavaScript的ES6引入了Map和Set数据结构。Map用于存储键值对,适合通过键进行查找,而Set则存储唯一值,无键且不支持键查找。两者在性能上表现出色,尤其在频繁的写入删除操作中。选择使用哪个取决于具体应用场景:键值对需求选Map,独特值集合则选Set。
54 2
|
7月前
|
前端开发 JavaScript
JavaScript新科技:PostCSS的安装和使用,2024年最新2024网易Web前端高级面试题总结
JavaScript新科技:PostCSS的安装和使用,2024年最新2024网易Web前端高级面试题总结
|
7月前
|
存储 JSON JavaScript
JavaScript高级进阶(更新中)-javascript-gao-ji-jin-jie--geng-xin-zhong-(三)
JavaScript高级进阶(更新中)-javascript-gao-ji-jin-jie--geng-xin-zhong-
55 1