文章目录
new
普通函数执行
function Func(x, y) {
let total = x + y;
this.res = total
}
let f = Func(10, 20);
console.log(res) // 30
构造函数执行
函数执行的时候通过 new 函数()
叫做构造函数执行
- 此时这个函数被称为一个自定义类(构造函数)
- 返回值被称为当前自定义类的一个实例(有特殊情况)
function Func(x, y) {
let total = x + y;
this.res = total
}
let f = new Func(10, 20);
console.log(f)
分析new所做的事
构造函数也具备普通函数执行的规则(形成私有上下文等)
- 初始化作用域链
- 初始化this: window
- 初始化arguments
- 形参赋值
- 变量提升
- 代码执行
new 函数执行的特殊性
- 私有上下文形成后的第一步是创建实例对象
- 初始化this,让上下文中的this指向创建的实例对象
- 代码执行中遇到的this.xxx都是给实例对象设置属性
- 即使函数中没有return,也会默认把创建的实例对象返回
- 如果有return,return的是基本类型的值,默认返回的还是实例
- 如果return的是引用类型值,则以return的为主
f就是当前Func这个类的一个实例
new 不加小括号时
function Func(x, y) {
let total = x + y;
this.res = total
}
let f2 = new Func;
console.log(f2)
属于不带参数new,依然会执行函数,只是不传递实参
验证某个属性是否为当前对象的属性
- in:无论是私有还是公有,只要是它的属性,结果就为
true
- hasOwnProperty:只有是私有属性时结果才为
true
验证是否为类的实例
当手动返回一个对象时,实例将会
function Func(x, y) {
let total = x + y;
this.res = total;
return {
name: "test"
}
}
let f = new Func(10, 20);
console.log(f)
let f2 = new Func;
console.log(f2)
console.log(f2 instanceof Func) // false
原型prototype
和原型链__proto__
- 每一个类(构造函数)都具备
prototype
,并且属性值是一个对象,存储当前类供实例调用的公共属性和方法 - 在原型对象上具备一个内置属性:
constructor
,存储的值是当前类本身,所以我们把类称为构造函数 - 每一个对象(普通对象、prototype、实例、函数等)都具备:
__proto__
原型链属性(隐式属性),属性值是当前实例所属类的原型实例.__proto__===所属类.prototype
函数类型
- 普通函数
- 构造函数(类)
- 内置类
对象类型
- 普通对象、数组、正则、日期…
- prototype、
__proto__
- 类的实例(排除基本数据类型值的特殊性)
- 函数也是对象
- 万物皆对象…
内置类原型链机制
- Object作为所有原型对象的基类,他的
__proto__
只能指向自己,但是没有意义,所以赋值为null
arr1.length 私有
arr1.push() 私有里没有,会通过__porto__
找所属类原型上有没有
arr1.hasOwnProperty 所属原型类上也没有,就找基类Object原型对象里找
原型链机制
例如arr1.push() :
- 首先找私有方法,
- 没有就通过
__proto__
往上找Array.prototype
上的属性方法, - 找到了,并且让其执行,
- 方法中的
this
是arr1,从而实现给arr1末尾设置新的内容 - 等价于
Array.prototype.push.call(arr1)
或者arr1.__proto__.push.call(arr1)
原型中的属性方法:
- 相对于实例来说是公有的
- 相对于自己来说是私有的
自定义类原型机制
- 先确定执行的是哪的方法
- 方法执确定方法中的this
- 执行代码结果确定
function Fn() {
this.x = 100;
this.y = 200;
this.getX = function () {
console.log(this.x);
}
}
Fn.prototype.getX = function () {
console.log(this.x);
};
Fn.prototype.getY = function () {
console.log(this.y);
};
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.getX === f2.getX); // false
console.log(f1.getY === f2.getY); // true
console.log(f1.__proto__.getY === Fn.prototype.getY); // true
console.log(f1.__proto__.getX === f2.getX); // false
console.log(f1.getX === Fn.prototype.getX); // false
console.log(f1.constructor); // 指向类本身 Fn(){}
console.log(Fn.prototype.__proto__.constructor); // 指向基类本身
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();
类原型的重定向
重定向的问题
- 重定向的对象中没有
constructor
- 原始的原型对象上存放的属性方法不会被放到新对象上,导致实例不能调用
- 原始的原型对象不被占用后会被释放
重定向的好处
向内置的原型上扩展方法
添加方法很麻烦
function Func() {
}
Func.prototype.XX = function () {};
Func.prototype.A = function () {};
Func.prototype.B = function () {};
设置别名(治标不治本)
let proto = Func.prototype;
proto.A = function () {};
proto.B = function () {};
向原型上批量设置属性方法都是基于重定向的方式
Func.prototype = {
A: function () {},
B: function () {}
};
解决重定向问题
- 缺失了constructor
- 缺失了原始的原型对象上存放的属性方法
方法一:只需要constructor,手动加上
Func.prototype = {
constructor: Func,
A: function () { },
B: function () { }
}
方法二:通过Object.assign 合并,新的原型对象替换原始的
但是如果新老有个属性方法相同,则会替换
Func.prototype.Xxx = function () { };
Func.prototype = Object.assign(Func.prototype, {
A: function () { },
B: function () { }
})
方法三:把老原型对象作为新原型对象的上级原型
function Func() {
}
Func.prototype.Xxx = function () { };
let protoNew = Object.create(Func.prototype);
protoNew = Object.assign(protoNew, {
A: function () { },
B: function () { }
})
console.log(protoNew)
Func.prototype = protoNew;
let f = new Func();
console.log(f)
内置new 和 Object.create的实现
手写一个new使得以下代码成立
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function () {
console.log('wangwang');
}
Dog.prototype.sayName = function () {
console.log('my name is ' + this.name);
}
function _new(Func, ...args) {};
let sanmao = _new(Dog, '三毛');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true
new 的实现
- 创建一个FUNC的实力对象
- 初始化一个this指向这个实例对象
- 分析函数执行的返回值(没有返回值或者返回的是原始值都默认返回创建的实例,否则以函数自身返回的为主)
两个参数
- Func 要操作的这个类
- args 要传递给Func的参数
创建一个FUNC的实例对象
let obj = {};
obj.__proto__ = Func.prototype;
初始化一个this指向这个实例对象
let result = Func.call(obj, ...args);
分析函数执行的返回值
if (result !== null && /^(object|function)$/.test(typeof result)) {
return result;
}
return obj;
测试
function _new(Func, ...args) {
let obj = {};
obj.__proto__ = Func.prototype;
let result = Func.call(obj, ...args);
if (result !== null && /^(object|function)$/.test(typeof result)) {
return result;
}
return obj;
}
let sanmao = _new(Dog, '三毛');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true
考虑兼容性 Object.create()
谷歌实现了原型机制,完成原型查找
但是在IE浏览器中禁止使用__proto__
,为了防止改变原型指向
Object.create()
-
Object.create([OBJECT])
创建一个空对象x,并且把[OBJECT](这个值需要是一个对象)作为新对象的原型指向x.__proto__=[OBJECT]
-
Object.create(null):创建一个没有原型和原型链的空对象(不是任何类的实例)
但是低版本浏览器也会有不兼容这个方法的情况
手写Object.create
- 我们写的这个方法不支持null的处理
- 创建一个类,让
类.prototype
等于传递的prototype
返回这个类的实例,使得实例.__proto__ === 类.prototype
;
这样就实现了原型链的改变
Object.create = function create(prototype) {
if (prototype === null || typeof prototype !== "object") {
throw new TypeError(`Object prototype may only be an Object: ${prototype}`);
}
// 创建一个类,创建这个类的实例,实例.__proto__=类.prototype;而我们让类.prototype等于传递的prototype;
function Temp() { }
Temp.prototype = prototype;
return new Temp;
};
完整代码
Object.create = function create(prototype) {
if (prototype === null || typeof prototype !== "object") {
throw new TypeError(`Object prototype may only be an Object: ${prototype}`);
}
function Temp() { }
Temp.prototype = prototype;
return new Temp;
};
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function () {
console.log('wangwang');
}
Dog.prototype.sayName = function () {
console.log('my name is ' + this.name);
}
function _new(Func, ...args) {
// let obj = {};
// obj.__proto__ = Func.prototype;
let obj = Object.create(Func.prototype);
let result = Func.call(obj, ...args);
if (result !== null && /^(object|function)$/.test(typeof result)) {
return result;
}
return obj;
}
let sanmao = _new(Dog, '三毛');
// let sanmao = new Dog("三毛")
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true
基于内置类原型扩展方法
- 为了防止自己的方法覆盖内置方法,自己的方法需要加前缀
- 优势:以后使用方便,和内置方法类似,直接让实例调用
- 方法中的this一般是当前要操作的实例(不需要基于形参传递实例进来了)
- 只要保证方法的返回结果还是当前类的实例,那么我们就可以基于链式方法调用当前类中提供的其他方法
Array.prototype.myDistinct = function () {
console.log(this);
}
let arr = [1, 2, 2, 3, 3, 4];
arr.myDistinct();
扩展一个数组去重方法
Array.prototype.myDistinct = function () {
// console.log(new Set(this));
let newArr = [...new Set(this)];
return newArr;
}
let arr = [1, 2, 2, 3, 3, 4];
console.log(arr.myDistinct())
for in 与 for of
- for in
遍历对象,所有可被枚举的属性都可以遍历到(大部分私有属性和自己向内置类原型扩展的属性)
Object.prototype.myXx = function () { };
let obj = {
name: "xxx",
age: 20
}
for (let key in obj) {
console.log(key)
}
name
age
myXx
所以使用for in
一般会加上一个判断,除去非私有的属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key)
}
}
或者使用基类中的方法Object.keys
Object.keys(obj).forEach(key => {
console.log(key, obj[key])
})
扩展技巧
- 可以直接让实例调用,而此时执行方法,方法中的this是调用的实例(调取方便)
- 需要注意的是,自己扩展的方法不要覆盖内置的方法(最好带个命名前缀
my、_
) - 可以实现链式写法:上一个方法返回的结果可以调取本结果所属类上的原型方法
原型属性扩展相关题目
let n = 10;
let m = n.plus(10).minus(5);
console.log(m);//=>15(10+10-5)
- this -> 永远是对象数据类型的值(除了null/undefined)
- 对象+数字:不一定都是字符串拼接
- 对象本身是想转换为数字再进行运算的
- 对象转换为数字,并不是先toString,他需要先调用valueOf()获取原始值
[[PrimitiveValue]]
(原始值是基本类型的值)
如果有原始值,直接基于原始值处理,没有原始值的才去toString
Object.prototype.minus = function (num) {
return this - num;
}
Object.prototype.plus = function (num) {
return this + num;
}
考虑不符合数字转换的情况
function init(num) {
num = Number(num);
return isNaN(num) ? 0 : num;
}
Object.prototype.minus = function (num) {
num = init(num);
return this - num;
}
Object.prototype.plus = function (num) {
num = init(num);
return this + num;
}
函数与类与对象
- 普通函数(作用域和作用域链)
- 构造函数(类、实例、原型和原型链)
- 普通对象(键值对)
- 所有函数都是类的实例
- 函数都是对象,任何一个函数都能调用
hasOwnProperty/call/aplly/bind
- 但是对象不是函数
typeof Array // "function"
typeof Object // "function"
Object instanceof Function // true
Function.prototype === Function.__proto__ // true
Object作为函数(类)一定是Function 的实例
Object.__proto__ === Function.prototype
Function既是一个类也是一个对象,也是Object的实例
Function.__proto__ === Function.prototype
true
Function.prototype.__proto__ === Object.prototype
true
补充:运算符优先级
谁优先级大就先运算谁
- 成员访问
obj.xxx
19 - 带参数的new new Fun() 19
- 不带参数的new new Fun 19
new Foo.getName();
先执行成员访问–普通函数执行
然后执行new创建实例
new Foo().getName()
先创建实例,再成员访问执行普通函数Foo实例.getName()
new new Foo().getName()
相当于new 实例.getName()
这时候是无参数new,所以下一步执行成员访问
题目
1
function Foo() {
// 注意这一步是操作变量,而不是实例属性
getName = function () {
console.log(1);
};
return this;
}
Foo.getName = function () {
console.log(2);
};
Foo.prototype.getName = function () {
console.log(3);
};
var getName = function () {
console.log(4);
};
function getName() {
console.log(5);
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
2
let url = "http://www.zhufengpeixun.cn/?lx=1&from=wx#video";
console.log(url.queryURLParams("from")); //=>"wx"
console.log(url.queryURLParams("_HASH")); //=>"video"
解法一,利用URL方法
String.prototype.queryURLParams = function queryURLParams(type) {
let obj = {
// lx: "1",
// from: "weixin",
// _HASH: "video"
};
let { search, hash } = new URL(this);
// 处理HASH
if (hash) {
obj["_HASH"] = hash.substring(1);
}
// params
if (search) {
search = search.substring(1).split("&");
// [key=value, key=value]
search.forEach(item => {
let [key, value] = item.split("=");
obj[key] = value;
})
// [[key, value], [key, value]]
}
console.log(obj)
}
解法二:正则处理
String.prototype.queryURLParams = function queryURLParams(key) {
let obj = {};
this.replace(/([^?=&#]+)=([^?=&#]+)/g, (_, key, value) => obj[key] = value);
this.replace(/#([^?=&#]+)/g, (_,hash) => obj["_HASH"] = hash)
return key ? obj[key] : obj;
}
let url = "http://www.zhufengpeixun.cn/?lx=1&from=wx#video";
console.log(url.queryURLParams("from")); //=>"wx"
console.log(url.queryURLParams("_HASH")); //=>"video"
console.log(url.queryURLParams());
面向对象的变相运用
push的原理
题目
let obj = {
2: 3,
3: 4,
length: 2,
push: Array.prototype.push
}
obj.push(1);
obj.push(2);
console.log(obj);
先理解 push所要做的事情
Array.prototype.push = function push(item) {
this //-> 需要处理的实例
// 数组长度需要加一,最后一项是this.length-1,加一项就是length - 1 + 1
this[this.length] = item;
// 给改变length属性
this.length++;
return this.length;
};
所以分析结果
let obj = {
2: 3, //1
3: 4, //2
length: 2, // ->3 -> 4
push: Array.prototype.push
}
obj.push(1);
// item->1 this->obj
// obj[obj.length] = 1 -> obj[2] = 1
// obj.length++
obj.push(2);
// item->2 this->obj
// obj[3] = 2
// obj.length++
console.log(obj);
数据转换的技巧
题目使打印OK
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
方案1:重写覆盖valueOf/toString方法
对象转换为数字,需要调取valueOf/toString
var a = {
i: 0,
toString() {
// this->a
return ++this.i;
}
};
或者
let a = [1, 2, 3];
a.toString = a.shift;
方法2:数据劫持
- Object.defineProperty/ Proxy
- 获取a,如果a不是变量,就是window的一个属性
let i = 0;
Object.defineProperty(window, 'a', {
get() {
return ++i;
}
});
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}