资讯专栏INFORMATION COLUMN

ES5(下)

includecmath / 344人阅读

摘要:命令命令的作用,就是执行构造函数,返回一个实例对象。实例对象相当于创建一个空的构造函数,将其属性指向参数对象,从而实现让该实例继承的属性。

这是ES5的入门篇教程的笔记,网址:JavaScript教程,以下内容中黑体表示大标题,还有一些重点;斜体表示对于自身,还需要下功夫学习的内容。这里面有一些自己的见解,所以若是发现问题,欢迎指出~

实例对象与new命令
面向对象编程是目前主流的编程范式,它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。
对象是单个事物的抽象。
对象是一个容器,封装了属性(property)和方法(method)。
属性是对象的状态,方法是对象的行为。两者最主要的区别在于属性属于对象静态的一面,方法属于对象动态的一面。
构造函数
面向对象编程的第一步,就是要生成对象,通常需要一个模板,表示某一类实物的共同特征,然后对象根据这个模板生成。
典型的面向对象编程语言(比如C++和JAVA),都有“类”(class)这个概念。所谓“类”就是对象的模板,对象就是“类”的实例。但是JavaScript语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。
构造函数,为了与普通函数区别,构造函数名字的第一个字母通常大写。它有两个特点:1)函数体内部使用了this关键字,代表了所要生成的对象实例;2)生成对象的时候,必须使用new命令。
new命令
new命令的作用,就是执行构造函数,返回一个实例对象。
new命令本身就可以执行构造函数,所以后面的构造函数可以带括号,也可以不带括号。但是为了表示这里是函数调用,推荐使用括号。

// 推荐的写法
let v = new Vehicle();
// 不推荐的写法
let v = new Vehicle(); // 这两种写法都是等价的

new命令的原理
使用new命令时,它后面的函数依次执行下面的步骤:

1、创建一个空对象,作为将要返回的对象实例;
2、将这个空对象的原型,指向构造函数的prototype属性;
3、将这个空对象赋值给函数内部的this关键字;
4、开始执行构造函数内部的代码。

如果构造函数内部由return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。(也就是说,new后,只能返回对象,要么是自身,要么是一个新对象。)
构造函数与普通函数最主要的区别,是内部有没有this关键字的函数。
对普通函数使用new命令,会返回一个空对象。

function getMessage() {
    let a = 1;
    return "this is a message";
}
let msg = new getMessage(); // {}
typeof msg // "object"

构造函数作为模板,可以生成实例对象,但是,有时拿不到构造函数,只能拿到一个现有的对象,通过Object.create()方法,可以将这个现有的对象作为模板,生成新的实例对象。

let person1 = {
    name: "张三",
    greeting: function() {
        console.log("Hi! I"m " + this.name + ".");
    }
}
let person2 = Obejct.create(person1); // person2继承了person1的属性和方法。
person2.name // 张三
person2.greeting() // Hi! I"m 张三.

this关键字
如果this所在的方法不在对象的第一层,这时this只是指向当前一层的对象,而不会继承更上面一层。
由于this的指向是不确定的,所以切勿在函数中包含多层的this。

let a = {
    p: "Hello",
    b: {
        m: function() {
            console.log(this.p);
        }
    }
};
a.b.m() // undefined a.b.m方法在a对象的第二层,该方法内部的this不是指向a,而是指向a.b。
let o = {
    f1: function () {
        console.log(this);
        let f2 = function () {
            console.log(this);
        }(); // 这里是执行函数了,就变成了值
    }
}
o.f1()
// Object 第一层指向对象o
// Window 第二层指向全局对象

// 实际执行的如下
let temp = function () {
    console.log(this);
};
let o = {
    f1: function() {
        console.log(this);
        let f2 = temp();
    }
}

数组中的map和foreach方法,允许提供一个函数作为参数,这个函数内部不应该使用this。因为两者回调的this,是指向window对象的。(内层的this不指向外部,而是指向顶层对象)解决这种方法可以用中间变量,也可以将this当作foreach方法的第二个参数,固定运行环境。
JavaScript提供了call、apply、bind三个方法,来切换/固定this的指向。
函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。
call的第一个参数就是this所要指向的那个对象,后面饿参数则是函数调用时所需的函数。
apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数。
如果两个方法没有参数,或者参数为null或undefined,则等同于指向全局对象。

let obj = {};
let f = function () {
    return this;
};
f() === window // true
f.call(obj) === obj // true

func.call(thisVlaue, arg1, arg2, ...)
func.apply(thisValue, [arg1, arg2, ...])
function f(x, y) {
    console.log(x + y);
}
f.call(null, 1, 1) //2
f.apply(null, [1, 1]) //2

// JS不提供找出数组最大元素的函数,结合apply方法和Math.max方法,就可以返回数组的最大元素
let a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15

bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。

let d = new Date();
d.getTime() // 1561974996108
let print = d.getTime; // 赋值后,内部的this已经不指向Date对象的实例了
print() // Uncaught TypeError: this is not a Date object.

// 使用bind
let print = d.getTime.bind(d);
print() // 1561974996108

空元素(null)与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined。

对象的继承
大部分面向对象的编程语言,都是通过“类(class)”实现对象的继承。传统上,JavaSCript语言的继承不通过class,而是通过“原型对象(prototype)”实现。
构造函数的缺点:同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。

function Cat(name, color) {
    this.name = name;
    this.color = color;
    this.meow = function () {
        console.log("喵喵");
    };
}
let cat1 = new Cat("大毛", "白色");
let cat2 = new Cat("二毛", "黑色");
cat1.meow === cat2.meow // false cat1和cat2是同一个构造函数的两个实例,它们都具有meow方法。由于meow方法是生成在每个实例对象上面的,所以两个实例就生成了两次,没有必要,也浪费了系统资源,需要共享,也就是JavaScript的原型对象(prototype)。

prototype属性的作用
JavaScript继承机制的涉及思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。(感觉像是Java类中的公共属性和公共方法一样。。。)
JavaScript规定,每个函数都有一个prototype属性,指向一个对象。对于普通函数来说,该属性基本无用,但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。

function f() {}
typeof f.prototype // "object"

function Animal(name) {
    this.name = name;
}
Animal.prototype.color = "white"; // 原型对象上添加一个color属性,下面的实例对象都共享了该属性。
let cat1 = new Animal("大毛");
let cat2 = new Animal("二毛");
cat1.color // "white"
cat2.color // "white"
// 原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上
Animal.prototype.color = "yellow";
cat1.color // "yellow"
cat2.color // "yellow"
// 如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
cat1.color = "black";
cat1.color // "black"
cat2.color // "yellow"

综上,原型对象的作用,就是定义所有实例对象共享的属性和方法,而实例对象可以视作从原型对象衍生出来的子对象。
原型链
JavaScript规定,所有对象都有自己的原型对象(prototype)。一方面,任何我一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOf和toString方法的原因,因为这是从Object.prototype继承的。
其实,Object.prototype对象也有他的原型,Object.prototype的原型是null。null没有任何属性和方法,也米有自己的原型。因此,原型链的尽头就是null。

Object.getPrototypeOf(Object.prototype) // null

如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。举例来说,如果让构造函数的prototype属性指向一个数组,就意味着实例对象可以调用数组方法。
constructor属性
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。

function P() {}
P.prototype.constructor === P // true constructor属性定义在prototype对象上,意味着可以被所有实例对象继承。

let p = new P();
p.constructor === P // true p是构造函数P的实例对象
p.constructor === P.prototype.constructor // true
p.hasOwnPrototype("constructor") // false p自身没有constructor属性,该属性是读取原型链上面的P.prototype.constructor属性

instanceof运算符
instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。(左边是实例对象,右边是构造函数。)

let v = new Vehicle();
v instanceof Vehicle // true 实际检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上。
// 等同于
Vehicle.prototype.isPrototypeOf(v)

// instanceOf检查的是整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true
let d = new Date();
d instanceof Date // true
d instanceof Object // true d同时是Date和Object的实例

// 任意对象(除了null)都是Object的实例,所以instanceof运算符可以判断一个值是否为非null的对象
typeof null // Object 是为了防止这种情况的发生
null instanceof Object // false

// 但是需要注意的是,instanceof运算符只能用于对象,不适用原始类型的值
let s = "hello";
"s" instanceof String // false
new String("s") instanceof String // true

// 对于undefined和null,instanceof运算符总是返回false
undefined instanceof Object // false
null instanceof Object // false

emm,对象的继承中的模块内容还需要下狠功夫
Object对象的相关方法
Object.getPrototypeOf()方法返回参数对象的原型,这是获取原型对象的标准方法。

let F = function () {};
let f = new F();
Object.getPrototypeOf(f) === F.prototype // true

// 特殊对象的原型
// 空对象的原型是 Object.prototype
Object.getPrototypeOf({}) === Object.prototype // true
// Object.prototype 的原型是 null
Object.getPrototypeOf(Objectprototype) === null // true
// 函数的原型是 Function.prototype
function f() {}
Object.getPrototypeOf(f) === Function.prototype // true

Object.create()可以从一个实例对象,生成另一个实例对象。该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象,该实例完全继承原型对象的属性。

// 原型对象 没有this,所以不是构造函数!!!不能用new来创建,这是一个实例对象。
let A = {
    print: function () {
        console.log("hello");
    }
}
// 实例对象
let B = Object.create(A); // 相当于创建一个空的构造函数,将其.prototype属性指向参数对象A,从而实现让该实例继承A的属性。

Object.getPrototypeOf(B) === A // true 以A对象为原型,生成了B对象,B继承了A的所有属性和方法
B.print() // hello
B.print === A.print // true

// 以下三种方式生成的新对象是等价的
let obj1 = Object.create({});
let obj2 = Object.create(Object.prototype);
let obj3 = new Object();

//Object.create方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上。
let obj1 = { p: 1 };
let obj2 = Object.create(obj1);
obj1.p = 2;
obj2.p // 2 修改对象原型obj1会影响到实例对象obj2
obj2.p = 3;
obj1.p // 1 修实例对象obj2并不会影响到原型对象obj1
obj2.p // 3

length用来截断长度,只对数组有效,对字符串无效。
emm 面向对象的编程也是云里雾里的,fighting!
异步操作概述
JavaScript只在一个线程上运行,也就是说,JavaScript同时只能执行一个任务,其他任务都必须在后面排队等待。
程序里面所有的任务,可以分成两类:同步任务(synchronous)和异步任务(asynchronous)。同步任务是那些没有被引擎挂起、在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务,只有引擎认为某个异步任务可以执行了(比如Ajax操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。也就是说,异步任务不具有“堵塞”效应。
JavaScript运行时,除了一个正在运行的主线程,引擎还提供了一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。(实际上,根据异步任务的类型,存在多个任务队列。为了方便理解,这里假设只存在一个队列。)
引擎如何确定异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做时间循环(Event Loop)。维基百科的定义是:“事件循环是一个程序结构,用于等待和发送消息和事件”(a programming construct that waits for and dispatches events or messages in a program)。
异步操作的模式:
1、回调函数 回调函数是异步操作最基本的方法。(包括Promise)

function f1() {
    // ...
}
function f2() {
    // ...
}

f1();
f2(); // 这样编程的意图是f2必须等到f1执行完成,才能执行。
// 但是如果f1是异步操作,f2会立即执行,不会等到f1结束再执行。这种情况下,可以考虑改写f1,把f2写成f1的回调函数。
function f1(callback) {
    // ...
    callback();
}
function f2() {
    // ...
}
f1(f2); // 回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(coupling),使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。

2、事件监听
以前只记着是定时器,现在才知道,应该是事件监听!!!还是需要多看!!!
另一种思路是采用事件驱动模式。异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

f1.on("done", f2); // 当f1发生done事件,就执行f2。接着,对f1进行改写
function f1() {
    setTimeout(function () {
        // ...
        f1.trigger("done"); // 表示执行完成后,立即触发done事件,从而开始执行f2
    }, 1000);
}

这种方法比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,并且可以“去耦合”(decoupling),有利于实现模块化。缺点是整个程序都要编程事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。
3、发布/订阅
事件完全可以理解成“信号”,如果存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)整个信号,从而知道什么时候自己可以开始执行。这就叫做“发布/订阅模式”(publish-subscribe parttern),又称“观察者模式”(observer pattern)。
观察者模式还不是了解,还需要学习!!

定时器
JavaScript提供定时执行代码的能力,叫做定时器(timer),主要由setTimeout()和setInterval()这两个函数来完成。
1、setTimeout()
该函数用来指定某个函数或某段代码,再多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。
setTimeout函数接受两个参数,第一个参数func|code是将要推迟执行的函数名或者一段代码,第二个参数是推迟执行的毫秒数。

let timerId = setTimeout(func|code, delay);

console.log(1);
setTimeout("console.log(2)", 1000); // 需要注意的是,console.log(2)必须以字符串的形式,作为setTimeout的参数
console.log(3);
// 1
// 3
// 2

function f() {
    console.log(2);
}
setTimeout(f, 1000); // 如果推迟执行的是函数,就直接将函数名,作为setTimeout的参数。

2、setInterval()
该函数的用法与setTimeout完全一样,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。

// 清除定时器
var id1 = setTimeout(f, 1000);
var id2 = setInterval(f, 1000);
clearTimeout(id1);
clearInterval(id2);

3、实例:debounce函数
debounce防抖动,这是个好东西,以后要好好看看!!感觉可以用到小程序以及h5的输入框输入!!
4、setTimeout(f, 0)
setTimeout的作用是将代码推迟到指定时间执行,如果指定时间为0,即setTimeout(f, 0),那么会立刻执行吗?答案是不会,因为它必须要等到当前脚本的同步任务,全部处理完以后,才会执行setTimeout指定的回调函数f。也就是说,setTimeout(f, 0)会在下一轮事件循环一开始就执行。

setTimeout(function () {
    console.log(1);
}, 0);
console.log(2);
// 2
// 1

Promise对象
Promise对象是JavaScript的异步操作解决方案,为异步操作提供统一接口。它起到代理作用(proxy),充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。
Promise对象的状态:Promise对象通过自身的状态,来控制异步操作。
Promise实例具有三种状态:
1)异步操作未成功(pending)
2)异步操作成功(fulfilled)
3)异步操作失败(rejected)
三种状态里面,fulfilled和rejected合在一起称为resolved(已定型)。
这三种状态的变化途径只有两种:从“未完成”到“成功”;从“未完成”到“失败”。
一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是Promise这个名字的又来,它的英文意思是“承若”,一旦承诺成效,就不得再改变了。这也意味着,Promise实例的状态变化只可能发生一次。因此,Promise的最终结果只有两种:
1)异步操作成功,Promise实例传回一个值(value),状态变为fulfilled;
2)异步操作失败,Promise实例抛出一个错误(error),状态变为rejected。
JavaScript提供原生的Promise构造函数,用来生成Promise实例。
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,它们是两个函数,由JavaScript引擎提供,不用自己实现。
resolve函数的作用是,将Promise实例的状态从“未完成”变为“成功”(即从pending变为fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
reject函数的作用是,将Promise实例的状态从“未完成”变为“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

DOM概述
DOM是JavaScript操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个JavaScript对象,从而可以用脚本进行各种操作(比如增删内容)。
DOM的最小组成单位叫做节点(node)。文档的属性结构(DOM树),就是由各种不同类型的节点组成,每个节点可以看作是文档树的一片叶子。
节点的类型有七种:
Document:整个文档树的顶层节点
DocumentType:doctype标签(比如)
Element:网页的各种HTML标签(比如、等)
Attribute:网页元素的属性(比如class="right")
Text:标签之间或标签包含的文本
Comment:注释
DocumentFragment:文档的片段
浏览器提供一个原生的节点对象Node,上面这七种节点都继承了Node,因此具有一些共同的属性和方法。
emm 后面的看不下去了,再见!!!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105283.html

相关文章

  • 为什么都说js 里面任何对象最终都继承了Object对象

    摘要:今天闲来无事,看见几行小字。又说所有对象,继承终是。强行押韵一波这首诗的意思就是说的我今天没有什么事情,然后无意中又在网上看到了任何对象都是从对象继承而来的这句话。一时兴起,便去验证这句话。 今天闲来无事,看见几行小字。又说所有对象,继承终是Obj。—— 强行押韵一波 这首诗的意思就是说的我今天没有什么事情,然后无意中又在网上看到了任何对象都是从Object对象继承而来的这句话。一时兴...

    Gemini 评论0 收藏0
  • React Native填坑之旅--class(番外篇)

    摘要:构造函数定义侦探类作为例子。里的既是类的定义,也是构造函数。在构造函数中定义的实例方法和属性在每一个实例中都会保留一份,而在原型中定义的实例方法和属性是全部实例只有一份。 无论React还是RN都已经迈入了ES6的时代,甚至凭借Babel的支持都进入了ES7。ES6内容很多,本文主要讲解类相关的内容。 构造函数 定义侦探类作为例子。 ES5的类是如何定义的。 function ES5D...

    TwIStOy 评论0 收藏0
  • 如何在ES5与ES6环境处理函数默认参数

    摘要:函数默认值是一个很提高鲁棒性的东西就是让程序更健壮关于函数默认参数的描述函数默认参数允许在没有值或被传入时使用默认形参。也就实现了上边三元运算符的功能。直接使用这种方式,省去了在函数内部进行默认值的检查,能够让函数专注的做它应该做的事情。 函数默认值是一个很提高鲁棒性的东西(就是让程序更健壮)MDN关于函数默认参数的描述:函数默认参数允许在没有值或undefined被传入时使用默认形参...

    oliverhuang 评论0 收藏0
  • 给React初学者的10分钟ES6教程

    摘要:但是在中,可以通过关键字来实现类的继承的使用可以使得继承意义更加明确并且值得一提的是,如果你使用来定义的组件,那么可以在类的构造器里面,用简单的的声明方式来替代方法。 原文:The 10 min ES6 course for the beginner React Developer译者:Jim Xiao 著名的80/20定律可以用来解释React和ES6的关系。因为ES6增加了超过75...

    Awbeci 评论0 收藏0
  • ES6-开发环境搭建(1)

    摘要:全局安装本地安装转码规则安装完成后,我们可以看一下我们的文件,已经多了选项。直接在线编译提供一个在线编译器,可以在线将代码转为代码。 古语有云:君子生非异也,善假于物;工欲善其事,必先利其器。 由于有些低版本的浏览器还是不支持ES6语法,学习ES6,首先要学会搭建一个基本的ES6开发环境,利用工具,把ES6的语法转变成ES5的语法。 1、使用Babel把ES6编译成ES5 1...

    android_c 评论0 收藏0
  • 如何在 ES5 环境实现一个const ?

    作者:陈大鱼头 github: KRISACHAN 前言 刚刚看了掘金上一篇文章《作为技术面试官,为什么把你pass了》,里面第一题就是用es5实现const,据作者反馈 这一题所有的面试者都没有回答出来,感觉挺可惜的,其实这是一道比较简单的题目,但是由于涉及到了一些Object对象属性描述符的知识,这些描述符往往用到的场景不多,所以不容易记住。 属性描述符: 对象里目前的属性描述符有两...

    Muninn 评论0 收藏0

发表评论

0条评论

includecmath

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<