Open tomoya06 opened 4 years ago
面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是一种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。
Java代码示例参考菜鸟教程
prototype
属性,该属性指向原型。特例 Function.prototype.bind()
也是函数,但没有prototype属性。__proto__
属性,指向了创建该对象的构造函数的原型。其实这个属性指向了 [[prototype]]
,但是 [[prototype]]
是内部属性,我们并不能访问到,所以使用 __proto__
来访问。下面总结来自这里
Object
是所有对象的爸爸,所有对象都可以通过 __proto__
找到它Function
是所有函数的爸爸,所有函数都可以通过 __proto__
找到它Function.prototype
和 Object.prototype
是两个特殊的对象,他们由引擎来创建prototype
是一个对象,也就是原型__proto__
指向原型, __proto__
将对象和原型连接起来组成了原型链function Foo() {}
foo = new Foo()
foo.__proto__ === Foo.prototype // => true
过程
__proto__
链接到构造函数的prototype
this
到这个空对象并执行构造函数模拟实现:
function create() {
// 创建一个空的对象
let obj = new Object()
// 获得构造函数。别忘了arguments是类数组
let Con = [].shift.call(arguments)
// 链接到原型
obj.__proto__ = Con.prototype
// 绑定 this,执行构造函数
let result = Con.apply(obj, arguments)
// 确保 new 出来的是个对象
return typeof result === 'object' ? result : obj
}
由此可见,如果在new函数中加了return:如果return值类型,那么对构造函数没有影响,实例化对象返回空对象;如果return引用类型(数组,函数,对象),那么实例化对象就会返回该引用类型;
instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype
模拟实现:
function instanceof(left, right) {
// 获得类型的原型
let prototype = right.prototype
// 获得对象的原型
left = left.__proto__
// 判断对象的类型是否等于类型的原型
while (true) {
if (left === null)
return false
if (prototype === left)
return true
left = left.__proto__
}
}
优先级为:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定;
都是可以用来改变方法执行时this的指向。call和apply立即执行,但取参数的方法不同;bind只是改变指向并返回新的方法。
//在非严格模式下
var obj={ name: "new name" };
function fn(num1, num2){
console.log(num1+num2);
console.log(this);
}
fn.call(100,200); //this->100 num1=200 num2=undefined
fn.call(obj,100,200); //this->obj num1=100 num2=200
fn.call(); //this->window
fn.call(null); //this->window
fn.call(undefined); //this->window
//严格模式下
fn.call(); //在严格模式下this->undefined
fn.call(null); // 在严格模式 下this->null
fn.call(undefined); //在严格模式下this->undefined
// call() vs apply() 传参方式不同
fn.call(obj, 1, 2);
fn.apply(obj, [1, 2]);
fn.call(obj,1,2); //->改变this和执行fn函数是一起都完成了
fn.bind(obj,1,2);
var tempFn = fn.bind(obj,1,2); //->只是改变了fn中的this为obj并传递参数,但是此时并没有把fn这个函数执行
tempFn(); //这样才把fn这个函数执行
模拟实现:有点底层了,这里先忽略。参考这里
听说有六种继承方式。优缺点参考这里
初始化:
//父类型
function Person(name, age) {
this.name = name,
this.age = age,
this.play = [1, 2, 3]
this.setName = function () {}
}
Person.prototype.setAge = function () {}
//子类型
function Student(price) {
this.price = price
this.setScore = function () {}
}
Student.prototype = new Person() // 子类型的原型为父类型的一个实例对象
Student.prototype.sayHello = function () { }
Person.prototype.setAge = function () {}
function Student(name, age, price) {
Person.call(this, name, age) // 相当于: this.Person(name, age)
// 其他初始化...
}
Student.prototype.sayHello = function () { }
下面几种关键差异在于如何延长原型链:
// begin
// 方法1:
Student.prototype = new Person()
Student.prototype.constructor = Student //组合继承也是需要修复构造函数指向的
// 方法2:
Student.prototype = Person.prototype
// 方法3:
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
// end
Student.prototype.sayHello = function () { }
class Person {
//调用类的构造方法
constructor(name, age) {
this.name = name
this.age = age
}
//定义一般的方法
showName () {
console.log("调用父类的方法")
console.log(this.name, this.age);
}
}
let p1 = new Person('kobe', 39)
console.log(p1)
//定义一个子类
class Student extends Person {
constructor(name, age, salary) {
super(name, age)//通过super调用父类的构造方法
this.salary = salary
}
showName () {//在子类自身定义方法
console.log("调用子类的方法")
console.log(this.name, this.age, this.salary);
}
}
定义:执行上下文(execution context)是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。
每个执行上下文中都有三个重要的属性
this
当执行流进入一个函数时,执行上下文就会被推入一个上下文栈中,而在函数执行结束之后,栈将其上下文弹出,把控制权返回给之前的上下文。【来自红宝书】
参考栈溢出网友的回答,每个函数都会创建一个自己的作用域,并且会连接父级作用域。
function foo(a, b) {
var c;
c = a + b;
function bar(d) {
alert("d * c = " + (d * c));
}
return bar;
}
var b = foo(1, 2);
b(3); // alerts "d * c = 9"
作用域链示意图:
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| global binding object |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| .... |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
^
| chain
|
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| `foo` call binding object |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| a = 1 |
| b = 2 |
| c = 3 |
| bar = (function) |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
^
| chain
|
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| `bar` call binding object |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| d = 3 |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
注意js没有块级作用域,即如同类C语言中,用花括号封闭的代码块有自己的作用域,即js中所谓的执行上下文。
定义:函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。
深浅拷贝都是已经创建了一个新的对象,区别在于对深层级对象的拷贝效果
Object.assign
JSON.parse(JSON.stringify(object))
,但注意json不支持的变量类型会被忽略,如undefined/symbol/function,有循环引用的变量会报错;完整实现则需要使用递归拷贝。-- | 和原数据是否指向同一对象 | 第一层数据为基本数据类型 | 原数据中包含子对象 |
---|---|---|---|
赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 |
浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原数据一同改变 |
深拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会使原数据一同改变 |
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
类数组。有下面的特性:
arguments[Symbol.iterator]
方法arguments.callee()
。严格模式禁用(好像也没用过立即调用函数表达式。形式如下。这个匿名函数拥有独立的词法作用域。好处是不会污染全局作用域,且表达式中的变量不能从外部访问。
(function () {
statements
})();
Promise 是 ES6 新增的语法,解决了回调地狱的问题。
可以把 Promise 看成一个状态机。初始是 pending 状态,可以通过函数 resolve 和 reject ,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化。
then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then 调用就失去意义了。
Generator 是 ES6 中新增的语法,和 Promise 一样,都可以用来异步编程
// 使用 * 表示这是一个 Generator 函数
// 内部可以通过 yield 暂停代码
// 通过调用 next 恢复执行
function* test() {
let a = 1 + 2;
yield 2;
yield 3;
}
let b = test();
console.log(b.next()); // > { value: 2, done: false }
console.log(b.next()); // > { value: 3, done: false }
console.log(b.next()); // > { value: undefined, done: true }
从以上代码可以发现,加上 * 的函数执行后拥有了 next 函数,也就是说函数执行后返回了一个对象。每次调用 next 函数可以继续执行被暂停的代码。
用得少,暂时先不手动实现。参考这里
一个函数如果加上 async ,那么该函数就会返回一个 Promise。可以把 async 看成将函数返回值使用 Promise.resolve() 包裹了下。
async function test() {
return "1";
}
console.log(test()); // -> Promise {<resolved>: "1"}
参考MDN
const target = {
message1: "hello",
message2: "everyone"
};
const handler2 = {
get: function(target, prop, receiver) {
return "world";
}
};
const proxy2 = new Proxy(target, handler2);
定义:ECMAScript 5的严格模式是采用具有限制性JavaScript变体的一种方式,从而使代码显示地 脱离“马虎模式/稀松模式/懒散模式“(sloppy)模式。
特性:
use strict;
var / let / const
而直接声明并赋值eval / arguments
不能被绑定或赋值;在未来版本会被使用的关键字如implements/interface/let/package
等,不能作为变量名或形参名function(){}
限制住;let是块级作用域,可以被{}
限制住ReferenceError
,这就是所谓的暂时性锁区参考壹题回答
参考壹题解答
具体实现:
对象可迭代,必须实现@@iterator
方法,也就是obj[Symbol.iterator] = someFunction
。
js内置的可迭代对象:String、Array、TypedArray、Map 和 Set
需要传入可迭代对象的语法: for...of 循环、展开语法、yield*,和结构赋值
对象要成为迭代器,必须实现next()
方法,next()
方法要能返回一个对象,包含done[boolean], value
两个属性
生成器函数使用function*
语法编写。 最初调用时,生成器函数不执行任何代码,而是返回Generator
迭代器。然后要调用generator.next()
方法,执行到下一个yield
为止。
async / await 是 Generator / yield 加上 Promise的语法糖。
语法:Object.defineProperty(obj, prop, descriptor)
。其中descriptor
包括:
另外,object实例、class定义中也可以使用get/set关键字来定义getter/setter。object实例中使用与defineProperty的效果类似,属性都将被定义在实例自身上;而在class中使用时,属性将被定义在实例的原型上。参考MDN
for...in语句以任意顺序遍历一个对象的除Symbol以外的可枚举属性,不应该用于数组。
for...of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环
for...in循环出的是key,for...of循环出的是value
// 遍历数组:
for(let index in aArray){
console.log(`${aArray[index]}`); // 注意,除了数组元素外还会把aArray的自定义属性也打印出来
}
// 几乎等价于
for(var value of aArray){
console.log(value);
}
语法基础
数据类型
参考MDN文档
typeof
类型转换
转Boolean
转自MDN:对Symbol.toPrimitive的更多用法
四则运算
one more thing...
双等号比较
分析:
[] == ![] // => true